From c13b1c1dbcab52a125bcbff418650763b477412c Mon Sep 17 00:00:00 2001 From: Filippo Ledda Date: Wed, 24 Jan 2024 09:31:51 +0100 Subject: [PATCH 1/6] CH-47 commit hash configuration --- .../common/server/common/openapi/openapi.yaml | 30 +++++++++++++++++++ docs/model/HarnessMainConfig.md | 1 + .../deployment-cli-tools/ch_cli_tools/helm.py | 5 +++- .../ch_cli_tools/utils.py | 10 +++++++ 4 files changed, 45 insertions(+), 1 deletion(-) diff --git a/applications/common/server/common/openapi/openapi.yaml b/applications/common/server/common/openapi/openapi.yaml index 97fb8e8b..02e7b77f 100644 --- a/applications/common/server/common/openapi/openapi.yaml +++ b/applications/common/server/common/openapi/openapi.yaml @@ -68,8 +68,38 @@ paths: tags: - Sentry x-openapi-router-controller: common.controllers.sentry_controller + /version: + get: + operationId: get_version + responses: + "200": + content: + application/json: + examples: + version: + value: "{\r\n \"build\": \"63498f19146bae1a6ae7e354\"\r\n \"tag\"\ + : \"v1.2.0\"\r\n}" + schema: + $ref: '#/components/schemas/AppVersion' + description: Deployment version GET + tags: + - config + x-openapi-router-controller: common.controllers.config_controller + summary: Get the version for this deployment components: schemas: + AppVersion: + description: "" + example: + build: 63498f19146bae1a6ae7e354 + tag: v1.2.0 + properties: + build: + type: string + tag: + type: string + title: Root Type for AppVersion + type: object get_config_200_response: example: clientId: clientId diff --git a/docs/model/HarnessMainConfig.md b/docs/model/HarnessMainConfig.md index 9f00bc2b..fc0f29da 100644 --- a/docs/model/HarnessMainConfig.md +++ b/docs/model/HarnessMainConfig.md @@ -21,6 +21,7 @@ Key | Input Type | Accessed Type | Description | Notes **backup** | [**BackupConfig**](BackupConfig.md) | [**BackupConfig**](BackupConfig.md) | | [optional] **name** | str, | str, | Base name | [optional] **task-images** | [**SimpleMap**](SimpleMap.md) | [**SimpleMap**](SimpleMap.md) | | [optional] +**build_hash** | str, | str, | | [optional] **any_string_name** | dict, frozendict.frozendict, str, date, datetime, uuid.UUID, int, float, decimal.Decimal, bool, None, list, tuple, bytes, io.FileIO, io.BufferedReader, | frozendict.frozendict, str, decimal.Decimal, BoolClass, NoneClass, tuple, bytes, FileIO | any string name can be used but the value must be the correct type | [optional] # env diff --git a/tools/deployment-cli-tools/ch_cli_tools/helm.py b/tools/deployment-cli-tools/ch_cli_tools/helm.py index 4c75a909..dc4a6cba 100644 --- a/tools/deployment-cli-tools/ch_cli_tools/helm.py +++ b/tools/deployment-cli-tools/ch_cli_tools/helm.py @@ -15,7 +15,7 @@ from . import HERE, CH_ROOT from cloudharness_utils.constants import TEST_IMAGES_PATH, VALUES_MANUAL_PATH, HELM_CHART_PATH, APPS_PATH, HELM_PATH, \ DEPLOYMENT_CONFIGURATION_PATH, BASE_IMAGES_PATH, STATIC_IMAGES_PATH -from .utils import get_cluster_ip, get_image_name, env_variable, get_sub_paths, guess_build_dependencies_from_dockerfile, image_name_from_dockerfile_path, \ +from .utils import get_cluster_ip, get_git_commit_hash, get_image_name, env_variable, get_sub_paths, guess_build_dependencies_from_dockerfile, image_name_from_dockerfile_path, \ get_template, merge_configuration_directories, merge_to_yaml_file, dict_merge, app_name_from_path, \ find_dockerfiles_paths @@ -323,6 +323,8 @@ def __finish_helm_values(self, values): """ if self.registry: logging.info(f"Registry set: {self.registry}") + + if self.local: values['registry']['secret'] = '' if self.registry_secret: @@ -330,6 +332,7 @@ def __finish_helm_values(self, values): values['registry']['name'] = self.registry values['registry']['secret'] = self.registry_secret values['tag'] = self.tag + values['build_hash'] = get_git_commit_hash(self.root_paths[-1]) # Fix: Call the defined function to get the git commit hash if self.namespace: values['namespace'] = self.namespace values['secured_gatekeepers'] = self.secured diff --git a/tools/deployment-cli-tools/ch_cli_tools/utils.py b/tools/deployment-cli-tools/ch_cli_tools/utils.py index 41b96d87..983b6a55 100644 --- a/tools/deployment-cli-tools/ch_cli_tools/utils.py +++ b/tools/deployment-cli-tools/ch_cli_tools/utils.py @@ -376,3 +376,13 @@ def check_docker_manifest_exists(registry, image_name, tag, registry_secret=None api_url = f"https://{registry}/v2/{image_name}/manifests/{tag}" resp = requests.get(api_url) return resp.status_code == 200 + +def get_git_commit_hash(path): + # return the short git commit hash in that path + # if the path is not a git repo, return None + + try: + return subprocess.check_output( + ['git', 'rev-parse', '--short', 'HEAD'], cwd=path).decode("utf-8").strip() + except: + return None From cca2aa2f37ca3e345ed8efcbba5355e14ed48f73 Mon Sep 17 00:00:00 2001 From: Filippo Ledda Date: Wed, 24 Jan 2024 09:32:21 +0100 Subject: [PATCH 2/6] CH-47 common api --- applications/common/api/openapi.yaml | 175 +++++++++++------- .../common/controllers/config_controller.py | 25 +++ .../common/server/common/models/__init__.py | 1 + .../server/common/models/app_version.py | 90 +++++++++ 4 files changed, 220 insertions(+), 71 deletions(-) create mode 100644 applications/common/server/common/controllers/config_controller.py create mode 100644 applications/common/server/common/models/app_version.py diff --git a/applications/common/api/openapi.yaml b/applications/common/api/openapi.yaml index c5072eb5..33c2b91a 100644 --- a/applications/common/api/openapi.yaml +++ b/applications/common/api/openapi.yaml @@ -1,78 +1,111 @@ openapi: 3.0.0 info: - description: Cloud Harness Platform - Reference CH service API - license: - name: UNLICENSED - title: CH common service API - version: 0.1.0 + title: CH common service API + version: 0.1.0 + description: Cloud Harness Platform - Reference CH service API + license: + name: UNLICENSED servers: -- description: SwaggerHub API Auto Mocking - url: /api -tags: -- description: Sentry - name: Sentry + - + url: /api + description: SwaggerHub API Auto Mocking paths: - /sentry/getdsn/{appname}: - parameters: - - in: path - name: appname - schema: - type: string - required: true - get: - tags: - - Sentry - description: Gets the Sentry DSN for a given application - operationId: getdsn - responses: - '200': - description: Sentry DSN for the given application - content: - application/json: - schema: - type: object - '400': - description: Sentry not configured for the given application - content: - application/json: - schema: - type: object - text/html: - schema: - type: string - '404': - description: Sentry not configured for the given application - content: - application/problem+json: - schema: - type: object - text/html: - schema: - type: string - summary: Gets the Sentry DSN for a given application - x-openapi-router-controller: common.controllers.sentry_controller - /accounts/config: - get: - tags: - - Accounts - description: Gets the config for logging in into accounts - operationId: get_config - responses: - '200': - description: Config for accounts log in - content: - application/json: - schema: - type: object - properties: - url: + '/sentry/getdsn/{appname}': + get: + tags: + - Sentry + responses: + '200': + content: + application/json: + schema: + type: object + description: Sentry DSN for the given application + '400': + content: + application/json: + schema: + type: object + text/html: + schema: + type: string + description: Sentry not configured for the given application + '404': + content: + application/problem+json: + schema: + type: object + text/html: + schema: + type: string + description: Sentry not configured for the given application + operationId: getdsn + summary: Gets the Sentry DSN for a given application + description: Gets the Sentry DSN for a given application + x-openapi-router-controller: common.controllers.sentry_controller + parameters: + - + name: appname + schema: type: string - description: The auth URL. - realm: + in: path + required: true + /accounts/config: + get: + tags: + - Accounts + responses: + '200': + content: + application/json: + schema: + type: object + properties: + url: + description: The auth URL. + type: string + realm: + description: The realm. + type: string + clientId: + description: The clientID. + type: string + description: Config for accounts log in + operationId: get_config + summary: Gets the config for logging in into accounts + description: Gets the config for logging in into accounts + x-openapi-router-controller: common.controllers.accounts_controller + /version: + summary: Get the version for this deployment + get: + tags: + - config + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/AppVersion' + examples: + version: + value: "{\r\n \"build\": \"63498f19146bae1a6ae7e354\"\r\n \"tag\": \"v1.2.0\"\r\n}" + description: Deployment version GET + operationId: getVersion +components: + schemas: + AppVersion: + title: Root Type for AppVersion + description: '' + type: object + properties: + build: type: string - description: The realm. - clientId: + tag: type: string - description: The clientID. - summary: Gets the config for logging in into accounts - x-openapi-router-controller: common.controllers.accounts_controller + example: + build: 63498f19146bae1a6ae7e354 + tag: v1.2.0 +tags: + - + name: Sentry + description: Sentry diff --git a/applications/common/server/common/controllers/config_controller.py b/applications/common/server/common/controllers/config_controller.py new file mode 100644 index 00000000..169a0478 --- /dev/null +++ b/applications/common/server/common/controllers/config_controller.py @@ -0,0 +1,25 @@ + +import connexion +import six +from typing import Dict +from typing import Tuple +from typing import Union + +from common.models.app_version import AppVersion # noqa: E501 +from common import util + +from cloudharness.utils.config import CloudharnessConfig +from cloudharness_model.models import HarnessMainConfig + +def get_version(): # noqa: E501 + """get_version + + # noqa: E501 + + + :rtype: Union[AppVersion, Tuple[AppVersion, int], Tuple[AppVersion, int, Dict[str, str]] + """ + + config: HarnessMainConfig = HarnessMainConfig.from_dict(CloudharnessConfig.get_configuration()) + + return AppVersion(tag=config.tag, build=config.build_hash) diff --git a/applications/common/server/common/models/__init__.py b/applications/common/server/common/models/__init__.py index 1a083de0..6baf6676 100644 --- a/applications/common/server/common/models/__init__.py +++ b/applications/common/server/common/models/__init__.py @@ -3,4 +3,5 @@ # flake8: noqa from __future__ import absolute_import # import models into model package +from common.models.app_version import AppVersion from common.models.get_config200_response import GetConfig200Response diff --git a/applications/common/server/common/models/app_version.py b/applications/common/server/common/models/app_version.py new file mode 100644 index 00000000..629b4546 --- /dev/null +++ b/applications/common/server/common/models/app_version.py @@ -0,0 +1,90 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from common.models.base_model_ import Model +from common import util + + +class AppVersion(Model): + """NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + + Do not edit the class manually. + """ + + def __init__(self, build=None, tag=None): # noqa: E501 + """AppVersion - a model defined in OpenAPI + + :param build: The build of this AppVersion. # noqa: E501 + :type build: str + :param tag: The tag of this AppVersion. # noqa: E501 + :type tag: str + """ + self.openapi_types = { + 'build': str, + 'tag': str + } + + self.attribute_map = { + 'build': 'build', + 'tag': 'tag' + } + + self._build = build + self._tag = tag + + @classmethod + def from_dict(cls, dikt) -> 'AppVersion': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The AppVersion of this AppVersion. # noqa: E501 + :rtype: AppVersion + """ + return util.deserialize_model(dikt, cls) + + @property + def build(self): + """Gets the build of this AppVersion. + + + :return: The build of this AppVersion. + :rtype: str + """ + return self._build + + @build.setter + def build(self, build): + """Sets the build of this AppVersion. + + + :param build: The build of this AppVersion. + :type build: str + """ + + self._build = build + + @property + def tag(self): + """Gets the tag of this AppVersion. + + + :return: The tag of this AppVersion. + :rtype: str + """ + return self._tag + + @tag.setter + def tag(self, tag): + """Sets the tag of this AppVersion. + + + :param tag: The tag of this AppVersion. + :type tag: str + """ + + self._tag = tag From 0e71bfec0181690f9f702a68d36a537a4a02121d Mon Sep 17 00:00:00 2001 From: Filippo Ledda Date: Wed, 24 Jan 2024 09:32:43 +0100 Subject: [PATCH 3/6] CH-47 usage example --- applications/samples/deploy/values.yaml | 3 +++ applications/samples/frontend/src/App.tsx | 3 ++- .../frontend/src/components/Version.tsx | 19 +++++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 applications/samples/frontend/src/components/Version.tsx diff --git a/applications/samples/deploy/values.yaml b/applications/samples/deploy/values.yaml index 6ba035a4..6265c681 100644 --- a/applications/samples/deploy/values.yaml +++ b/applications/samples/deploy/values.yaml @@ -6,6 +6,8 @@ harness: service: port: 8080 auto: true + use_services: + - name: common deployment: volume: name: my-shared-volume @@ -47,6 +49,7 @@ harness: build: - cloudharness-flask - cloudharness-frontend-build + resources: - name: my-config src: "myConfig.json" diff --git a/applications/samples/frontend/src/App.tsx b/applications/samples/frontend/src/App.tsx index a9c75b60..81836267 100644 --- a/applications/samples/frontend/src/App.tsx +++ b/applications/samples/frontend/src/App.tsx @@ -2,12 +2,13 @@ import React from 'react'; import './styles/style.less'; import RestTest from './components/RestTest'; - +import Version from './components/Version'; const Main = () => ( <>

Sample React application is working!

+

See api documentation here

diff --git a/applications/samples/frontend/src/components/Version.tsx b/applications/samples/frontend/src/components/Version.tsx new file mode 100644 index 00000000..2b5f5eb3 --- /dev/null +++ b/applications/samples/frontend/src/components/Version.tsx @@ -0,0 +1,19 @@ +import React, { useState, useEffect } from 'react'; + + + +const Version = () => { + const [result, setResult] = useState(null); + useEffect(() => { + fetch("/proxy/common/api/version", { + headers: { + 'Accept': 'application/json' + } + }).then(r => r.json().then(j => setResult(j)), () => setResult({ data: "API error" })); + }, []); + + + return result ?

Tag: { result?.tag } - Build: {result?.build}

:

Backend did not answer

+} + +export default Version; \ No newline at end of file From aef5ef5dbc651dcf75bf4fda57fb2a8d7cb5e796 Mon Sep 17 00:00:00 2001 From: Filippo Ledda Date: Wed, 24 Jan 2024 14:07:04 +0100 Subject: [PATCH 4/6] CH-47 add docs --- .../jupyterhub/zero-to-jupyterhub-k8s | 1 + docs/common-api.md | 37 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 160000 applications/jupyterhub/zero-to-jupyterhub-k8s create mode 100644 docs/common-api.md diff --git a/applications/jupyterhub/zero-to-jupyterhub-k8s b/applications/jupyterhub/zero-to-jupyterhub-k8s new file mode 160000 index 00000000..c92c1237 --- /dev/null +++ b/applications/jupyterhub/zero-to-jupyterhub-k8s @@ -0,0 +1 @@ +Subproject commit c92c12374795e84f36f5f16c4e8b8a448ad2f230 diff --git a/docs/common-api.md b/docs/common-api.md new file mode 100644 index 00000000..9d3b79dc --- /dev/null +++ b/docs/common-api.md @@ -0,0 +1,37 @@ +# Common API microservice + +The common microservice is intended to provide utility information about the +deployment and its configuration to the frontends. + +## Functionality +The main functionalities of the common microservice are: +- Information about the current version/build +- Accounts endpoint and configuration information +- Sentry endpoint + +## How to use it in your application + +First of all, have to configure your application deployment to include +the common microservice on the dependencies and used services. + +`myapp/deploy/values.yaml` +```yaml +harness: + ... + dependencies: + soft: + - common + ... + ... + use_services: + - name: common +``` + +The common api will be available at `/proxy/common/api` path from your app + +> the `use_services` sets up the reverse proxy in your app subdomain +> to avoid cross-origin requests from the frontend + +See a usage example [here](../applications/samples/frontend/src/components/Version.tsx). + + From 269b493d507baa9f18b657877c5efef630fada89 Mon Sep 17 00:00:00 2001 From: Filippo Ledda Date: Thu, 25 Jan 2024 12:38:10 +0100 Subject: [PATCH 5/6] Fix js path for gatekeeper rules --- applications/samples/frontend/webpack.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/samples/frontend/webpack.config.js b/applications/samples/frontend/webpack.config.js index ad5ee556..69f80cb9 100644 --- a/applications/samples/frontend/webpack.config.js +++ b/applications/samples/frontend/webpack.config.js @@ -29,7 +29,7 @@ module.exports = function webpacking(envVariables) { const output = { path: path.resolve(__dirname, "dist"), - filename: "[name].[contenthash].js", + filename: "js/[name].[contenthash].js", publicPath: "/" }; From 3343f8b37d4e8360ab76ba894c386e24d3ea11d4 Mon Sep 17 00:00:00 2001 From: Filippo Ledda Date: Thu, 25 Jan 2024 16:25:37 +0100 Subject: [PATCH 6/6] CH-47 model update --- libraries/models/api/openapi.yaml | 3 ++ .../models/harness_main_config.py | 34 +++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/libraries/models/api/openapi.yaml b/libraries/models/api/openapi.yaml index 577bff53..96a3e480 100644 --- a/libraries/models/api/openapi.yaml +++ b/libraries/models/api/openapi.yaml @@ -834,6 +834,9 @@ components: task-images: $ref: '#/components/schemas/SimpleMap' description: '' + build_hash: + description: '' + type: string additionalProperties: true SimpleMap: description: '' diff --git a/libraries/models/cloudharness_model/models/harness_main_config.py b/libraries/models/cloudharness_model/models/harness_main_config.py index c75db6d3..7f18e82d 100644 --- a/libraries/models/cloudharness_model/models/harness_main_config.py +++ b/libraries/models/cloudharness_model/models/harness_main_config.py @@ -23,7 +23,7 @@ class HarnessMainConfig(Model): Do not edit the class manually. """ - def __init__(self, local=None, secured_gatekeepers=None, domain=None, namespace=None, mainapp=None, registry=None, tag=None, apps=None, env=None, privenv=None, backup=None, name=None, task_images=None): # noqa: E501 + def __init__(self, local=None, secured_gatekeepers=None, domain=None, namespace=None, mainapp=None, registry=None, tag=None, apps=None, env=None, privenv=None, backup=None, name=None, task_images=None, build_hash=None): # noqa: E501 """HarnessMainConfig - a model defined in OpenAPI :param local: The local of this HarnessMainConfig. # noqa: E501 @@ -52,6 +52,8 @@ def __init__(self, local=None, secured_gatekeepers=None, domain=None, namespace= :type name: str :param task_images: The task_images of this HarnessMainConfig. # noqa: E501 :type task_images: Dict[str, object] + :param build_hash: The build_hash of this HarnessMainConfig. # noqa: E501 + :type build_hash: str """ self.openapi_types = { 'local': bool, @@ -66,7 +68,8 @@ def __init__(self, local=None, secured_gatekeepers=None, domain=None, namespace= 'privenv': NameValue, 'backup': BackupConfig, 'name': str, - 'task_images': Dict[str, object] + 'task_images': Dict[str, object], + 'build_hash': str } self.attribute_map = { @@ -82,7 +85,8 @@ def __init__(self, local=None, secured_gatekeepers=None, domain=None, namespace= 'privenv': 'privenv', 'backup': 'backup', 'name': 'name', - 'task_images': 'task-images' + 'task_images': 'task-images', + 'build_hash': 'build_hash' } self._local = local @@ -98,6 +102,7 @@ def __init__(self, local=None, secured_gatekeepers=None, domain=None, namespace= self._backup = backup self._name = name self._task_images = task_images + self._build_hash = build_hash @classmethod def from_dict(cls, dikt) -> 'HarnessMainConfig': @@ -414,3 +419,26 @@ def task_images(self, task_images): """ self._task_images = task_images + + @property + def build_hash(self): + """Gets the build_hash of this HarnessMainConfig. + + # noqa: E501 + + :return: The build_hash of this HarnessMainConfig. + :rtype: str + """ + return self._build_hash + + @build_hash.setter + def build_hash(self, build_hash): + """Sets the build_hash of this HarnessMainConfig. + + # noqa: E501 + + :param build_hash: The build_hash of this HarnessMainConfig. + :type build_hash: str + """ + + self._build_hash = build_hash