diff --git a/.github/workflows/lint-check.yml b/.github/workflows/lint-check.yml new file mode 100644 index 000000000..efee9f91a --- /dev/null +++ b/.github/workflows/lint-check.yml @@ -0,0 +1,23 @@ +name: Lint Check + +on: + push + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + + - name: Install autopep8 + run: pip install autopep8 + + - name: Run lint-check.sh + run: bash lint-check.sh \ No newline at end of file diff --git a/application-templates/base/test/api/test_st.py b/application-templates/base/test/api/test_st.py index cb4e97994..902e2bd5e 100644 --- a/application-templates/base/test/api/test_st.py +++ b/application-templates/base/test/api/test_st.py @@ -3,7 +3,7 @@ import schemathesis as st from schemathesis.checks import response_schema_conformance, not_a_server_error -from cloudharness_test import apitest_init # include to perform default authorization +from cloudharness_test import apitest_init # include to perform default authorization app_url = os.environ.get("APP_URL", "http://samples.ch.local/api") @@ -15,4 +15,3 @@ def test_ping(case): response = case.call() pprint(response.__dict__) assert response.status_code == 200, "this api errors on purpose" - diff --git a/application-templates/django-app/api/test_st.py b/application-templates/django-app/api/test_st.py index fb852433d..a7527b804 100644 --- a/application-templates/django-app/api/test_st.py +++ b/application-templates/django-app/api/test_st.py @@ -3,7 +3,7 @@ import schemathesis as st from schemathesis.checks import response_schema_conformance, not_a_server_error -from cloudharness_test import apitest_init # include to perform default authorization +from cloudharness_test import apitest_init # include to perform default authorization app_url = os.environ.get("APP_URL", "http://samples.ch.local/api") @@ -20,8 +20,9 @@ def test_ping(case): pprint(response.__dict__) assert response.status_code == 200, "this api errors on purpose" + def test_state_machine(): schema.as_state_machine().run() # APIWorkflow = schema.as_state_machine() # APIWorkflow.run() -# TestAPI = APIWorkflow.TestCase \ No newline at end of file +# TestAPI = APIWorkflow.TestCase diff --git a/application-templates/django-app/backend/__APP_NAME__/asgi.py b/application-templates/django-app/backend/__APP_NAME__/asgi.py index 43321f43d..6d1bf4d96 100644 --- a/application-templates/django-app/backend/__APP_NAME__/asgi.py +++ b/application-templates/django-app/backend/__APP_NAME__/asgi.py @@ -16,9 +16,9 @@ application = get_asgi_application() # init the auth service -from cloudharness_django.services import init_services +from cloudharness_django.services import init_services # noqa E402 init_services() # start the kafka event listener -import cloudharness_django.services.events +import cloudharness_django.services.events # noqa E402 diff --git a/application-templates/django-app/backend/__APP_NAME__/settings.py b/application-templates/django-app/backend/__APP_NAME__/settings.py index e9ad5dc58..9b3c6d221 100644 --- a/application-templates/django-app/backend/__APP_NAME__/settings.py +++ b/application-templates/django-app/backend/__APP_NAME__/settings.py @@ -120,13 +120,13 @@ # *********************************************************************** # * __APP_NAME__ settings # *********************************************************************** -from cloudharness.applications import get_configuration -from cloudharness.utils.config import ALLVALUES_PATH, CloudharnessConfig +from cloudharness.applications import get_configuration # noqa E402 +from cloudharness.utils.config import ALLVALUES_PATH, CloudharnessConfig # noqa E402 # *********************************************************************** # * import base CloudHarness Django settings # *********************************************************************** -from cloudharness_django.settings import * +from cloudharness_django.settings import * # noqa E402 # add the local apps INSTALLED_APPS += [ diff --git a/application-templates/django-app/backend/__APP_NAME__/urls.py b/application-templates/django-app/backend/__APP_NAME__/urls.py index 185efb49a..ce1b9629e 100644 --- a/application-templates/django-app/backend/__APP_NAME__/urls.py +++ b/application-templates/django-app/backend/__APP_NAME__/urls.py @@ -24,7 +24,7 @@ urlpatterns = [path("admin/", admin.site.urls)] urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + \ - static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) urlpatterns += [re_path(r"^(?P.*)$", index, name="index")] admin.site.site_header = "__APP_NAME__ Admin" diff --git a/application-templates/django-app/backend/__APP_NAME__/wsgi.py b/application-templates/django-app/backend/__APP_NAME__/wsgi.py index 34560de81..678932f0f 100644 --- a/application-templates/django-app/backend/__APP_NAME__/wsgi.py +++ b/application-templates/django-app/backend/__APP_NAME__/wsgi.py @@ -16,9 +16,9 @@ application = get_wsgi_application() # init the auth service -from cloudharness_django.services import init_services +from cloudharness_django.services import init_services # noqa E402 init_services() # start the kafka event listener -import cloudharness_django.services.events +import cloudharness_django.services.events # noqa E402 diff --git a/application-templates/django-app/backend/api/controllers/__init__.py b/application-templates/django-app/backend/api/controllers/__init__.py index d716607ef..1344f5cc9 100644 --- a/application-templates/django-app/backend/api/controllers/__init__.py +++ b/application-templates/django-app/backend/api/controllers/__init__.py @@ -1 +1 @@ -import api.controllers.test as test_controller \ No newline at end of file +import api.controllers.test as test_controller diff --git a/application-templates/flask-server/backend/__APP_NAME__/__main__.py b/application-templates/flask-server/backend/__APP_NAME__/__main__.py index 1ce1af97d..f2f198da0 100644 --- a/application-templates/flask-server/backend/__APP_NAME__/__main__.py +++ b/application-templates/flask-server/backend/__APP_NAME__/__main__.py @@ -7,4 +7,3 @@ if __name__ == '__main__': main() - diff --git a/application-templates/flask-server/backend/setup.py b/application-templates/flask-server/backend/setup.py index e3caeb8c3..e7700bac9 100644 --- a/application-templates/flask-server/backend/setup.py +++ b/application-templates/flask-server/backend/setup.py @@ -38,4 +38,3 @@ __APP_NAME__ """ ) - diff --git a/application-templates/webapp/backend/__APP_NAME__/__main__.py b/application-templates/webapp/backend/__APP_NAME__/__main__.py index c6becb111..a4b264cb9 100644 --- a/application-templates/webapp/backend/__APP_NAME__/__main__.py +++ b/application-templates/webapp/backend/__APP_NAME__/__main__.py @@ -7,4 +7,3 @@ if __name__ == '__main__': main() - diff --git a/application-templates/webapp/frontend/.eslintrc.cjs b/application-templates/webapp/frontend/.eslintrc.cjs deleted file mode 100644 index c73cf24e4..000000000 --- a/application-templates/webapp/frontend/.eslintrc.cjs +++ /dev/null @@ -1,19 +0,0 @@ -module.exports = { - root: true, - env: { browser: true, es2020: true }, - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:react-hooks/recommended', - ], - ignorePatterns: ['dist', '.eslintrc.cjs', 'src/rest/*'], - parser: '@typescript-eslint/parser', - plugins: ['react-refresh'], - rules: { - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], - '@typescript-eslint/no-explicit-any': 'off', - } -} diff --git a/application-templates/webapp/frontend/eslint.config.js b/application-templates/webapp/frontend/eslint.config.js new file mode 100644 index 000000000..5fbd4636c --- /dev/null +++ b/application-templates/webapp/frontend/eslint.config.js @@ -0,0 +1,56 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' + + +export default tseslint.config( + { + ignores: [ + 'dist', + 'node_modules', + '.yalc', + 'src/rest/*' // do not lint generated code + ] + }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx,js,jsx}'], + languageOptions: { + ecmaVersion: "latest", + globals: globals.browser, + sourceType: "module" + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + indent: ["error", 2, { + SwitchCase: 1, + }], + curly: "error", // enforce braces for one-line blocks + "no-tabs": "error", // enforce no tabs + "no-console": ["warn", { + allow: ["warn", "error", "debug"], + }], + "@typescript-eslint/no-explicit-any": "off", // No strict typing (annoying especially with React elements and events callbacks) + "consistent-return": "warn", // https://eslint.org/docs/latest/rules/consistent-return + "prefer-arrow-callback": ["warn"], + "object-curly-spacing": ["warn", "always"], // enforce consistent spacing inside braces + "func-style": "off", // function expressions or arrow functions are equally valid + "no-unneeded-ternary": "warn", // disallow unnecessary ternary expressions + // React rules: https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules + "react/prop-types": "off", // PropTypes are not forced + "react/forbid-prop-types": "off", // all PropTypes are allowed + "react-hooks/rules-of-hooks": "error", // https://react.dev/reference/rules/rules-of-hooks + "react-hooks/exhaustive-deps": "warn", // Hooks dependency array, sometimes it's better to ignore + }, + } +) \ No newline at end of file diff --git a/application-templates/webapp/frontend/package.json b/application-templates/webapp/frontend/package.json index b8d452789..4d852c343 100644 --- a/application-templates/webapp/frontend/package.json +++ b/application-templates/webapp/frontend/package.json @@ -6,6 +6,7 @@ "start:dev": "DOMAIN=https://test.ch.metacell.us vite", "start:local": "DOMAIN=http://samples.ch vite", "prebuild": "eslint .", - "build": "vite build" + "build": "vite build", + "lint": "eslint src --report-unused-disable-directives --fix" } } diff --git a/applications/common/server/common/__main__.py b/applications/common/server/common/__main__.py index 4bd6ef172..0a81a3f80 100644 --- a/applications/common/server/common/__main__.py +++ b/applications/common/server/common/__main__.py @@ -7,13 +7,13 @@ from common.controllers.sentry_controller import global_dsn - def init_fn(app): log.info("initializing database from app") cors = CORS(app, resources={r"/api/*": {"origins": "*"}}) if not global_dsn: open_db(app) + app = init_flask(init_app_fn=init_fn) if __name__ == '__main__': diff --git a/applications/common/server/common/config.py b/applications/common/server/common/config.py index 23d0636f3..6e29d747a 100644 --- a/applications/common/server/common/config.py +++ b/applications/common/server/common/config.py @@ -22,7 +22,6 @@ class Config(object): log.error("Cannot configure SENTRY") - class ProductionConfig(Config): DEBUG = False diff --git a/applications/common/server/common/controllers/accounts_controller.py b/applications/common/server/common/controllers/accounts_controller.py index 90202ef36..b09613be9 100644 --- a/applications/common/server/common/controllers/accounts_controller.py +++ b/applications/common/server/common/controllers/accounts_controller.py @@ -3,6 +3,7 @@ from cloudharness import applications from cloudharness.utils.config import CloudharnessConfig + def get_config(): # noqa: E501 """ Gets the config for logging in into accounts diff --git a/applications/common/server/common/controllers/config_controller.py b/applications/common/server/common/controllers/config_controller.py index 169a04788..72f4fdd38 100644 --- a/applications/common/server/common/controllers/config_controller.py +++ b/applications/common/server/common/controllers/config_controller.py @@ -11,6 +11,7 @@ from cloudharness.utils.config import CloudharnessConfig from cloudharness_model.models import HarnessMainConfig + def get_version(): # noqa: E501 """get_version diff --git a/applications/common/server/common/controllers/security_controller_.py b/applications/common/server/common/controllers/security_controller_.py index ecac40558..7d686cba7 100644 --- a/applications/common/server/common/controllers/security_controller_.py +++ b/applications/common/server/common/controllers/security_controller_.py @@ -1,3 +1 @@ from typing import List - - diff --git a/applications/common/server/common/controllers/sentry_controller.py b/applications/common/server/common/controllers/sentry_controller.py index 1daee66fa..4d0727825 100644 --- a/applications/common/server/common/controllers/sentry_controller.py +++ b/applications/common/server/common/controllers/sentry_controller.py @@ -13,6 +13,7 @@ except: global_dsn = None + def getdsn(appname): # noqa: E501 """ Gets the Sentry DSN for a given application or returns the global dsn when set diff --git a/applications/common/server/common/models.py b/applications/common/server/common/models.py index 8817fa654..843e9d20d 100644 --- a/applications/common/server/common/models.py +++ b/applications/common/server/common/models.py @@ -16,4 +16,4 @@ def __init__(self, url, result_all, result_no_stop_words): self.result_no_stop_words = result_no_stop_words def __repr__(self): - return ''.format(self.id) \ No newline at end of file + return ''.format(self.id) diff --git a/applications/common/server/common/repository/sentry.py b/applications/common/server/common/repository/sentry.py index f53349951..4ad449ac0 100644 --- a/applications/common/server/common/repository/sentry.py +++ b/applications/common/server/common/repository/sentry.py @@ -1,14 +1,16 @@ -import sqlalchemy +import sqlalchemy from sqlalchemy.sql import text from cloudharness.utils.env import get_service_public_address from .db import get_db + class SentryProjectNotFound(Exception): pass + def _get_api_token(): # ToDo: may be we can use here a dynamic token, but for now let's use a hard coded one api_token = 'afe75d802007405dbc0c2fb1db4cc8b06b981017f58944d0afac700f743ee06a' @@ -16,31 +18,33 @@ def _get_api_token(): select token from sentry_apitoken where token=:api_token ''') - token = get_db().engine.execute(s, - api_token=api_token - ).fetchall() + token = get_db().engine.execute(s, + api_token=api_token + ).fetchall() if len(token) == 0: # token is not present in the Sentry database, let's create it s = text(''' insert into sentry_apitoken(user_id, token, scopes, date_added, scope_list) values (1, :api_token, 0, now(), :scope_list) ''') - get_db().engine.execute(s, - api_token=api_token, - scope_list='{event:admin,event:read,' - 'member:read,member:admin,' - 'project:read,project:releases,project:admin,project:write,' - 'team:read,team:write,team:admin,' - 'org:read,org:write,org:admin}' - ) + get_db().engine.execute(s, + api_token=api_token, + scope_list='{event:admin,event:read,' + 'member:read,member:admin,' + 'project:read,project:releases,project:admin,project:write,' + 'team:read,team:write,team:admin,' + 'org:read,org:write,org:admin}' + ) return _get_api_token() else: # return the first column from the first row of the query result return token[0][0] + def get_token(): return _get_api_token() + def get_dsn(appname): s = text(''' select public_key, p.id @@ -49,9 +53,9 @@ def get_dsn(appname): where p.slug=:project_slug ''') try: - public_key = get_db().engine.execute(s, - project_slug=appname - ).fetchall() + public_key = get_db().engine.execute(s, + project_slug=appname + ).fetchall() except sqlalchemy.exc.OperationalError: raise SentryProjectNotFound('Sentry is not initialized.') diff --git a/applications/common/server/common/test/test_sentry_controller.py b/applications/common/server/common/test/test_sentry_controller.py index a01d90c91..6f881ad0d 100644 --- a/applications/common/server/common/test/test_sentry_controller.py +++ b/applications/common/server/common/test/test_sentry_controller.py @@ -17,7 +17,7 @@ def test_getdsn(self): Gets the Sentry DSN for a given application """ - headers = { + headers = { 'Accept': 'application/json', } response = self.client.open( diff --git a/applications/common/server/common/util.py b/applications/common/server/common/util.py index fcaed08fa..8e7e71e62 100644 --- a/applications/common/server/common/util.py +++ b/applications/common/server/common/util.py @@ -68,8 +68,8 @@ def deserialize_date(string): :rtype: date """ if string is None: - return None - + return None + try: from dateutil.parser import parse return parse(string).date() @@ -88,8 +88,8 @@ def deserialize_datetime(string): :rtype: datetime """ if string is None: - return None - + return None + try: from dateutil.parser import parse return parse(string) diff --git a/applications/common/server/setup.py b/applications/common/server/setup.py index 007ca859b..838cb45c4 100644 --- a/applications/common/server/setup.py +++ b/applications/common/server/setup.py @@ -38,4 +38,3 @@ Cloud Harness Platform - Reference CH service API """ ) - diff --git a/applications/jupyterhub/deploy/resources/hub/jupyterhub_config.py b/applications/jupyterhub/deploy/resources/hub/jupyterhub_config.py index 8fdfa8c18..688ba72a8 100755 --- a/applications/jupyterhub/deploy/resources/hub/jupyterhub_config.py +++ b/applications/jupyterhub/deploy/resources/hub/jupyterhub_config.py @@ -1,4 +1,11 @@ # load the config object (satisfies linters) +from z2jh import ( + get_config, + get_name, + get_name_env, + get_secret_value, + set_config_if_not_none, +) c = get_config() # noqa import glob @@ -10,12 +17,12 @@ from kubernetes_asyncio import client from tornado.httpclient import AsyncHTTPClient -#CLOUDHARNESS: EDIT START +# CLOUDHARNESS: EDIT START import logging try: from harness_jupyter.jupyterhub import harness_hub - harness_hub() # activates harness hooks on jupyterhub + harness_hub() # activates harness hooks on jupyterhub except Exception as e: logging.error("could not import harness_jupyter", exc_info=True) # CLOUDHARNESS: EDIT END @@ -24,14 +31,6 @@ configuration_directory = os.path.dirname(os.path.realpath(__file__)) sys.path.insert(0, configuration_directory) -from z2jh import ( - get_config, - get_name, - get_name_env, - get_secret_value, - set_config_if_not_none, -) - def camelCaseify(s): """convert snake_case to camelCase @@ -513,9 +512,9 @@ def camelCaseify(s): email_domain = 'local' common_oauth_traits = ( - ('client_id', None), - ('client_secret', None), - ('oauth_callback_url', 'callbackUrl'), + ('client_id', None), + ('client_secret', None), + ('oauth_callback_url', 'callbackUrl'), ) print("Auth type", auth_type) if auth_type == 'ch': @@ -560,4 +559,3 @@ def camelCaseify(s): c.domain = get_config('root.domain') c.namespace = get_config('root.namespace') # CLOUDHARNESS: EDIT END - \ No newline at end of file diff --git a/applications/jupyterhub/deploy/resources/hub/z2jh.py b/applications/jupyterhub/deploy/resources/hub/z2jh.py index 2fe0d25b8..24bba5552 100755 --- a/applications/jupyterhub/deploy/resources/hub/z2jh.py +++ b/applications/jupyterhub/deploy/resources/hub/z2jh.py @@ -121,12 +121,12 @@ def get_config(key, default=None): # EDIT: CLOUDHARNESS START import re if value and isinstance(value, str): - replace_var = re.search("{{.*?}}", value) + replace_var = re.search("{{.*?}}", value) if replace_var: variable = replace_var.group(0)[2:-2].strip() repl = get_config(variable) - + if repl: print("replace", variable, "in", value, ":", repl) value = re.sub("{{.*?}}", repl, value) diff --git a/applications/jupyterhub/src/chauthenticator/chauthenticator/auth.py b/applications/jupyterhub/src/chauthenticator/chauthenticator/auth.py index e7b4cb0dc..ba4489fce 100644 --- a/applications/jupyterhub/src/chauthenticator/chauthenticator/auth.py +++ b/applications/jupyterhub/src/chauthenticator/chauthenticator/auth.py @@ -13,11 +13,13 @@ handler.setLevel(logging.DEBUG) logging.getLogger().addHandler(handler) + class CloudHarnessAuthenticateHandler(BaseHandler): """ Handler for /chkclogin Creates a new user based on the keycloak user, and auto starts their server """ + def initialize(self, force_new_server, process_user): super().initialize() self.force_new_server = force_new_server @@ -28,23 +30,23 @@ def get(self): self.clear_login_cookie() try: - - accessToken = self.request.cookies.get( - 'kc-access', None) or self.request.cookies.get('accessToken', None) - print("Token", accessToken) - if accessToken == '-1' or not accessToken: - self.redirect('/hub/logout') - - accessToken = accessToken.value - user_data = AuthClient.decode_token(accessToken) - username = user_data['sub'] - print("Username", username, "-",user_data['preferred_username']) - raw_user = self.user_from_username(username) - print("JH user: ", raw_user.__dict__) - self.set_login_cookie(raw_user) + + accessToken = self.request.cookies.get( + 'kc-access', None) or self.request.cookies.get('accessToken', None) + print("Token", accessToken) + if accessToken == '-1' or not accessToken: + self.redirect('/hub/logout') + + accessToken = accessToken.value + user_data = AuthClient.decode_token(accessToken) + username = user_data['sub'] + print("Username", username, "-", user_data['preferred_username']) + raw_user = self.user_from_username(username) + print("JH user: ", raw_user.__dict__) + self.set_login_cookie(raw_user) except Exception as e: - logging.error("Error getting user from session", exc_info=True) - raise + logging.error("Error getting user from session", exc_info=True) + raise user = yield gen.maybe_future(self.process_user(raw_user, self)) self.redirect(self.get_next_url(user)) diff --git a/applications/jupyterhub/src/harness_jupyter/harness_jupyter/jupyterhub.py b/applications/jupyterhub/src/harness_jupyter/harness_jupyter/jupyterhub.py index ac7dafa66..3c9679e34 100644 --- a/applications/jupyterhub/src/harness_jupyter/harness_jupyter/jupyterhub.py +++ b/applications/jupyterhub/src/harness_jupyter/harness_jupyter/jupyterhub.py @@ -116,7 +116,7 @@ def change_pod_manifest(self: KubeSpawner): quota_ws_open = user_quotas.get("quota-ws-open") # Default value, might be overwritten by the app config - self.storage_pvc_ensure = bool(self.pvc_name) + self.storage_pvc_ensure = bool(self.pvc_name) if quota_ws_open: # get user number of pods running @@ -124,11 +124,11 @@ def change_pod_manifest(self: KubeSpawner): num_of_pods = len([s for s in servers if s.active]) if num_of_pods > int(quota_ws_open): raise PodSpawnException( - "You reached your quota of {} concurrent servers." - " One must be deleted before a new server can be started".format( - quota_ws_open - ), - ) + "You reached your quota of {} concurrent servers." + " One must be deleted before a new server can be started".format( + quota_ws_open + ), + ) try: subdomain = self.handler.request.host.split( str(self.config['domain']))[0][0:-1] @@ -263,13 +263,12 @@ def change_pod_manifest(self: KubeSpawner): from pprint import pprint pprint(self.storage_class) - # If there's a timeout, just let it propagate asyncio.ensure_future(exponential_backoff( - partial( - self._make_create_pvc_request, pvc, self.k8s_api_request_timeout - ), - f'Could not create PVC {self.pvc_name}', - # Each req should be given k8s_api_request_timeout seconds. - timeout=self.k8s_api_request_retry_timeout, - )) + partial( + self._make_create_pvc_request, pvc, self.k8s_api_request_timeout + ), + f'Could not create PVC {self.pvc_name}', + # Each req should be given k8s_api_request_timeout seconds. + timeout=self.k8s_api_request_retry_timeout, + )) diff --git a/applications/nfsserver/nfs-subdir-external-provisioner/release-tools/boilerplate/boilerplate.py b/applications/nfsserver/nfs-subdir-external-provisioner/release-tools/boilerplate/boilerplate.py index 5618b9ab8..26a2412ac 100755 --- a/applications/nfsserver/nfs-subdir-external-provisioner/release-tools/boilerplate/boilerplate.py +++ b/applications/nfsserver/nfs-subdir-external-provisioner/release-tools/boilerplate/boilerplate.py @@ -50,6 +50,7 @@ verbose_out = sys.stderr if args.verbose else open("/dev/null", "w") + def get_refs(): refs = {} @@ -63,6 +64,7 @@ def get_refs(): return refs + def file_passes(filename, refs, regexs): try: f = open(filename, 'r') @@ -127,13 +129,16 @@ def file_passes(filename, refs, regexs): return True + def file_extension(filename): return os.path.splitext(filename)[1].split(".")[-1].lower() + skipped_dirs = ['Godeps', 'third_party', '_gopath', '_output', '.git', 'cluster/env.sh', 'vendor', 'test/e2e/generated/bindata.go', 'repo-infra/verify/boilerplate/test', '.glide'] + def normalize_files(files): newfiles = [] for pathname in files: @@ -142,6 +147,7 @@ def normalize_files(files): newfiles.append(pathname) return newfiles + def get_files(extensions): files = [] if len(args.filenames) > 0: @@ -170,13 +176,14 @@ def get_files(extensions): outfiles.append(pathname) return outfiles + def get_regexs(): regexs = {} # Search for "YEAR" which exists in the boilerplate, but shouldn't in the real thing - regexs["year"] = re.compile( 'YEAR' ) + regexs["year"] = re.compile('YEAR') # dates can be 2014, 2015, 2016, ..., CURRENT_YEAR, company holder names can be anything years = range(2014, date.today().year + 1) - regexs["date"] = re.compile( '(%s)' % "|".join(map(lambda l: str(l), years)) ) + regexs["date"] = re.compile('(%s)' % "|".join(map(lambda l: str(l), years))) # strip // +build \n\n build constraints regexs["go_build_constraints"] = re.compile(r"^(// \+build.*\n)+\n", re.MULTILINE) # strip #!.* from shell scripts @@ -184,7 +191,6 @@ def get_regexs(): return regexs - def main(): regexs = get_regexs() refs = get_refs() @@ -196,5 +202,6 @@ def main(): return 0 + if __name__ == "__main__": sys.exit(main()) diff --git a/applications/notifications/server/notifications/__main__.py b/applications/notifications/server/notifications/__main__.py index 38389bde9..03c83f863 100644 --- a/applications/notifications/server/notifications/__main__.py +++ b/applications/notifications/server/notifications/__main__.py @@ -8,4 +8,3 @@ def main(): if __name__ == '__main__': main() - diff --git a/applications/notifications/server/notifications/adapters/base_adapter.py b/applications/notifications/server/notifications/adapters/base_adapter.py index 4ea030fe7..5ecf70f9e 100644 --- a/applications/notifications/server/notifications/adapters/base_adapter.py +++ b/applications/notifications/server/notifications/adapters/base_adapter.py @@ -65,7 +65,7 @@ def __init__(self, notification, channel, backend): os.path.join( self.channel["templateFolder"], self.notification["template"] - )) + )) @abc.abstractmethod def send(self, context): diff --git a/applications/notifications/server/notifications/backends/console_backend.py b/applications/notifications/server/notifications/backends/console_backend.py index 0a1fd005d..a50bc8030 100644 --- a/applications/notifications/server/notifications/backends/console_backend.py +++ b/applications/notifications/server/notifications/backends/console_backend.py @@ -18,4 +18,4 @@ def __init__(self, *args, **kwargs): def send(self): log.info("Send notification") log.info(f"args:{self.args}") - log.info("kwargs:\n"+"\n".join("{0}: {1!r}".format(k,v) for k,v in self.kwargs.items())) + log.info("kwargs:\n" + "\n".join("{0}: {1!r}".format(k, v) for k, v in self.kwargs.items())) diff --git a/applications/notifications/server/notifications/backends/email_backend.py b/applications/notifications/server/notifications/backends/email_backend.py index 53f71ec82..8a3ae77df 100644 --- a/applications/notifications/server/notifications/backends/email_backend.py +++ b/applications/notifications/server/notifications/backends/email_backend.py @@ -22,7 +22,6 @@ def __init__(self, email_from=None, email_to=None, subject=None, message=None, * self.subject = subject self.message = message - def send(self): logger.info(f"Sending notification email to {self.email_to}") msg = EmailMessage() @@ -35,7 +34,7 @@ def send(self): email_pass = get_secret_or_empty('email-password') email_host = conf.get_configuration()["smtp"]["host"] email_port = conf.get_configuration()["smtp"]["port"] - email_tls = conf.get_configuration()["smtp"].get("use_tls") + email_tls = conf.get_configuration()["smtp"].get("use_tls") smtp = smtplib.SMTP(email_host, email_port) if email_user or email_pass: diff --git a/applications/notifications/server/notifications/controllers/helpers.py b/applications/notifications/server/notifications/controllers/helpers.py index 090abd485..355e0894e 100644 --- a/applications/notifications/server/notifications/controllers/helpers.py +++ b/applications/notifications/server/notifications/controllers/helpers.py @@ -14,9 +14,9 @@ def send(operation, context): if not channel: continue - + for b in channel["backends"]: - if b == "email": + if b == "email": channel_backend = NotificationEmailBackend elif b == "console": channel_backend = NotificationConsoleBackend diff --git a/applications/notifications/server/notifications/controllers/notifications_controller.py b/applications/notifications/server/notifications/controllers/notifications_controller.py index a0afde4ae..cd4634c52 100644 --- a/applications/notifications/server/notifications/controllers/notifications_controller.py +++ b/applications/notifications/server/notifications/controllers/notifications_controller.py @@ -43,6 +43,7 @@ def handle_event(self, message: CDCEvent): } ) + class NotificationsController: _notification_handlers = [] @@ -52,7 +53,7 @@ def __init__(self): @staticmethod def handler(app, event_client, message): - log.debug("Handler received message: %s",message) + log.debug("Handler received message: %s", message) for nh in [nh for nh in NotificationsController._notification_handlers if nh.message_type == message.get("message_type")]: nh.handle_event(CDCEvent.from_dict(message)) @@ -64,8 +65,8 @@ def _init_handlers(self): log.info(f"Init handler for event {notification_app['app']}.{notification_type['name']} type {event_type}") nss = NotificationHandler( event_type, - notification_app["app"], - notification_type["name"], + notification_app["app"], + notification_type["name"], notification_type["events"]) if not nss.topic_id in (handler.topic_id for handler in NotificationsController._notification_handlers): self._consume_topic(nss.topic_id) diff --git a/applications/notifications/server/setup.py b/applications/notifications/server/setup.py index e126c6655..4f4ecf1b6 100644 --- a/applications/notifications/server/setup.py +++ b/applications/notifications/server/setup.py @@ -34,4 +34,3 @@ notifications """ ) - diff --git a/applications/samples/backend/samples/controllers/auth_controller.py b/applications/samples/backend/samples/controllers/auth_controller.py index 5d3ecb47b..e2930680d 100644 --- a/applications/samples/backend/samples/controllers/auth_controller.py +++ b/applications/samples/backend/samples/controllers/auth_controller.py @@ -24,4 +24,4 @@ def valid_cookie(): # noqa: E501 :rtype: List[Valid] """ - return 'OK!' \ No newline at end of file + return 'OK!' diff --git a/applications/samples/backend/samples/controllers/security_controller_.py b/applications/samples/backend/samples/controllers/security_controller_.py index 8dd254a23..c052e680f 100644 --- a/applications/samples/backend/samples/controllers/security_controller_.py +++ b/applications/samples/backend/samples/controllers/security_controller_.py @@ -13,5 +13,3 @@ def info_from_bearerAuth(token): :rtype: dict | None """ return {'uid': 'user_id'} - - diff --git a/applications/samples/backend/samples/controllers/test_controller.py b/applications/samples/backend/samples/controllers/test_controller.py index ff1089736..f3580dc3d 100644 --- a/applications/samples/backend/samples/controllers/test_controller.py +++ b/applications/samples/backend/samples/controllers/test_controller.py @@ -24,13 +24,13 @@ def ping(): # noqa: E501 """ import os - + expected_environment_variables = { 'WORKERS': '3', 'ENVIRONMENT_TEST_A': 'value', 'ENVIRONMENT_TEST_B': '123', } - + for key, expected_value in expected_environment_variables.items(): try: environment_value = os.environ[key] @@ -38,9 +38,10 @@ def ping(): # noqa: E501 raise Exception(f'Expected environment variable {key} to be {expected_value}, but got {environment_value}') except KeyError: raise Exception(f'Expected to have an environment variable {key} defined') - + import time return time.time() + def serialization(): - return User(last_name="Last", first_name="First") \ No newline at end of file + return User(last_name="Last", first_name="First") diff --git a/applications/samples/backend/samples/service/resource_service.py b/applications/samples/backend/samples/service/resource_service.py index 64765ad24..9fbba7b45 100644 --- a/applications/samples/backend/samples/service/resource_service.py +++ b/applications/samples/backend/samples/service/resource_service.py @@ -8,6 +8,7 @@ counter = 0 resources = {} + class ResourceNotFound(Exception): pass @@ -34,7 +35,7 @@ def get_sample_resource(sampleresource_id: int): # noqa: E501 def get_sample_resources() -> List[SampleResource]: - return [v for v in resources.values()] + return [v for v in resources.values()] def update_sample_resource(sampleresource_id: int, sample_resource: SampleResource) -> List[SampleResource]: diff --git a/applications/samples/backend/samples/test/test_sample.py b/applications/samples/backend/samples/test/test_sample.py index a6f1c78f9..62acfabab 100644 --- a/applications/samples/backend/samples/test/test_sample.py +++ b/applications/samples/backend/samples/test/test_sample.py @@ -1,2 +1,2 @@ def test_sample(): - assert True \ No newline at end of file + assert True diff --git a/applications/samples/backend/samples/util.py b/applications/samples/backend/samples/util.py index 5b241814f..b802fafda 100644 --- a/applications/samples/backend/samples/util.py +++ b/applications/samples/backend/samples/util.py @@ -67,8 +67,8 @@ def deserialize_date(string): :rtype: date """ if string is None: - return None - + return None + try: from dateutil.parser import parse return parse(string).date() @@ -87,8 +87,8 @@ def deserialize_datetime(string): :rtype: datetime """ if string is None: - return None - + return None + try: from dateutil.parser import parse return parse(string) @@ -144,4 +144,4 @@ def _deserialize_dict(data, boxed_type): :rtype: dict """ return {k: _deserialize(v, boxed_type) - for k, v in data.items() } + for k, v in data.items()} diff --git a/applications/samples/backend/setup.py b/applications/samples/backend/setup.py index 16908eb68..fe7e0ce52 100644 --- a/applications/samples/backend/setup.py +++ b/applications/samples/backend/setup.py @@ -37,4 +37,3 @@ CloudHarness Sample api """ ) - diff --git a/applications/samples/frontend/.eslintrc.cjs b/applications/samples/frontend/.eslintrc.cjs deleted file mode 100644 index c73cf24e4..000000000 --- a/applications/samples/frontend/.eslintrc.cjs +++ /dev/null @@ -1,19 +0,0 @@ -module.exports = { - root: true, - env: { browser: true, es2020: true }, - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:react-hooks/recommended', - ], - ignorePatterns: ['dist', '.eslintrc.cjs', 'src/rest/*'], - parser: '@typescript-eslint/parser', - plugins: ['react-refresh'], - rules: { - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], - '@typescript-eslint/no-explicit-any': 'off', - } -} diff --git a/applications/samples/frontend/eslint.config.js b/applications/samples/frontend/eslint.config.js new file mode 100644 index 000000000..5fbd4636c --- /dev/null +++ b/applications/samples/frontend/eslint.config.js @@ -0,0 +1,56 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' + + +export default tseslint.config( + { + ignores: [ + 'dist', + 'node_modules', + '.yalc', + 'src/rest/*' // do not lint generated code + ] + }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx,js,jsx}'], + languageOptions: { + ecmaVersion: "latest", + globals: globals.browser, + sourceType: "module" + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + indent: ["error", 2, { + SwitchCase: 1, + }], + curly: "error", // enforce braces for one-line blocks + "no-tabs": "error", // enforce no tabs + "no-console": ["warn", { + allow: ["warn", "error", "debug"], + }], + "@typescript-eslint/no-explicit-any": "off", // No strict typing (annoying especially with React elements and events callbacks) + "consistent-return": "warn", // https://eslint.org/docs/latest/rules/consistent-return + "prefer-arrow-callback": ["warn"], + "object-curly-spacing": ["warn", "always"], // enforce consistent spacing inside braces + "func-style": "off", // function expressions or arrow functions are equally valid + "no-unneeded-ternary": "warn", // disallow unnecessary ternary expressions + // React rules: https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules + "react/prop-types": "off", // PropTypes are not forced + "react/forbid-prop-types": "off", // all PropTypes are allowed + "react-hooks/rules-of-hooks": "error", // https://react.dev/reference/rules/rules-of-hooks + "react-hooks/exhaustive-deps": "warn", // Hooks dependency array, sometimes it's better to ignore + }, + } +) \ No newline at end of file diff --git a/applications/samples/frontend/package.json b/applications/samples/frontend/package.json index a104f3afc..6d468d9a6 100644 --- a/applications/samples/frontend/package.json +++ b/applications/samples/frontend/package.json @@ -8,9 +8,9 @@ "start": "DOMAIN=http://localhost:5000 vite", "start:dev": "DOMAIN=https://test.ch.metacell.us vite", "start:local": "DOMAIN=http://samples.ch vite", - "prebuild": "eslint .", + "prebuild": "eslint src", "build": "vite build", - "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "lint": "eslint src --report-unused-disable-directives --fix", "preview": "vite preview" }, "dependencies": { @@ -18,16 +18,16 @@ "react-dom": "^18.3.1" }, "devDependencies": { - "@types/node": "^22.0.0", + "@eslint/js": "^9.9.0", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", - "@typescript-eslint/eslint-plugin": "^7.15.0", - "@typescript-eslint/parser": "^7.15.0", "@vitejs/plugin-react": "^4.3.1", - "eslint": "^8.57.0", - "eslint-plugin-react-hooks": "^4.6.2", - "eslint-plugin-react-refresh": "^0.4.7", - "typescript": "^5.2.2", - "vite": "^5.3.4" + "eslint": "^9.9.0", + "eslint-plugin-react-hooks": "^5.1.0-rc.0", + "eslint-plugin-react-refresh": "^0.4.9", + "globals": "^15.9.0", + "typescript": "^5.5.3", + "typescript-eslint": "^8.0.1", + "vite": "^5.4.1" } } diff --git a/applications/samples/frontend/src/App.tsx b/applications/samples/frontend/src/App.tsx index eb8c0452e..a6a2ac789 100644 --- a/applications/samples/frontend/src/App.tsx +++ b/applications/samples/frontend/src/App.tsx @@ -3,14 +3,14 @@ import Version from './components/Version'; const Main = () => ( - <> - -

Sample React application is working!

- - -

See api documentation here

- - ) + <> + +

Sample React application is working!

+ + +

See api documentation here

+ +) export default Main; diff --git a/applications/samples/frontend/vite.config.ts b/applications/samples/frontend/vite.config.ts index 15155d738..8bb506cbf 100644 --- a/applications/samples/frontend/vite.config.ts +++ b/applications/samples/frontend/vite.config.ts @@ -22,21 +22,21 @@ export default defineConfig(({ mode }) => { return { - plugins: [react()], - server: { - port: 9000, - proxy: { - '/api/': { - target: replaceHost( proxyTarget, 'samples'), - secure: false, - changeOrigin: true, - }, - '/proxy/common/api': { - target: replaceHost( proxyTarget, 'common'), - secure: false, - changeOrigin: true, - rewrite: (path) => path.replace(/^\/proxy\/common\/api/, '/api') + plugins: [react()], + server: { + port: 9000, + proxy: { + '/api/': { + target: replaceHost( proxyTarget, 'samples'), + secure: false, + changeOrigin: true, + }, + '/proxy/common/api': { + target: replaceHost( proxyTarget, 'common'), + secure: false, + changeOrigin: true, + rewrite: (path) => path.replace(/^\/proxy\/common\/api/, '/api') + } } - } -}}} + } }} ) diff --git a/applications/samples/frontend/yarn.lock b/applications/samples/frontend/yarn.lock index 3b6f86c4c..5fe49fa26 100644 --- a/applications/samples/frontend/yarn.lock +++ b/applications/samples/frontend/yarn.lock @@ -334,49 +334,61 @@ dependencies: eslint-visitor-keys "^3.3.0" -"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1": +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.11.0": version "4.11.0" resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.0.tgz#b0ffd0312b4a3fd2d6f77237e7248a5ad3a680ae" integrity sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A== -"@eslint/eslintrc@^2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" - integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== +"@eslint/config-array@^0.18.0": + version "0.18.0" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.18.0.tgz#37d8fe656e0d5e3dbaea7758ea56540867fd074d" + integrity sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw== + dependencies: + "@eslint/object-schema" "^2.1.4" + debug "^4.3.1" + minimatch "^3.1.2" + +"@eslint/eslintrc@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.1.0.tgz#dbd3482bfd91efa663cbe7aa1f506839868207b6" + integrity sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.6.0" - globals "^13.19.0" + espree "^10.0.1" + globals "^14.0.0" ignore "^5.2.0" import-fresh "^3.2.1" js-yaml "^4.1.0" minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.57.0": - version "8.57.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" - integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== +"@eslint/js@9.10.0", "@eslint/js@^9.9.0": + version "9.10.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.10.0.tgz#eaa3cb0baec497970bb29e43a153d0d5650143c6" + integrity sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g== + +"@eslint/object-schema@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.4.tgz#9e69f8bb4031e11df79e03db09f9dbbae1740843" + integrity sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ== -"@humanwhocodes/config-array@^0.11.14": - version "0.11.14" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" - integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== +"@eslint/plugin-kit@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.1.0.tgz#809b95a0227ee79c3195adfb562eb94352e77974" + integrity sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ== dependencies: - "@humanwhocodes/object-schema" "^2.0.2" - debug "^4.3.1" - minimatch "^3.0.5" + levn "^0.4.1" "@humanwhocodes/module-importer@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== -"@humanwhocodes/object-schema@^2.0.2": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" - integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== +"@humanwhocodes/retry@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.0.tgz#6d86b8cb322660f03d3f0aa94b99bdd8e172d570" + integrity sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew== "@jridgewell/gen-mapping@^0.3.5": version "0.3.5" @@ -431,85 +443,85 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@rollup/rollup-android-arm-eabi@4.19.0": - version "4.19.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.19.0.tgz#3d9fd50164b94964f5de68c3c4ce61933b3a338d" - integrity sha512-JlPfZ/C7yn5S5p0yKk7uhHTTnFlvTgLetl2VxqE518QgyM7C9bSfFTYvB/Q/ftkq0RIPY4ySxTz+/wKJ/dXC0w== - -"@rollup/rollup-android-arm64@4.19.0": - version "4.19.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.19.0.tgz#e1a6d4bca2eb08c84fd996a4bf896ce4b6f4014c" - integrity sha512-RDxUSY8D1tWYfn00DDi5myxKgOk6RvWPxhmWexcICt/MEC6yEMr4HNCu1sXXYLw8iAsg0D44NuU+qNq7zVWCrw== - -"@rollup/rollup-darwin-arm64@4.19.0": - version "4.19.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.19.0.tgz#0a3fffea69489a24a96079af414b0be78df8abbc" - integrity sha512-emvKHL4B15x6nlNTBMtIaC9tLPRpeA5jMvRLXVbl/W9Ie7HhkrE7KQjvgS9uxgatL1HmHWDXk5TTS4IaNJxbAA== - -"@rollup/rollup-darwin-x64@4.19.0": - version "4.19.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.19.0.tgz#13fbdb15f58f090871b0ffff047ece06ad6ad74c" - integrity sha512-fO28cWA1dC57qCd+D0rfLC4VPbh6EOJXrreBmFLWPGI9dpMlER2YwSPZzSGfq11XgcEpPukPTfEVFtw2q2nYJg== - -"@rollup/rollup-linux-arm-gnueabihf@4.19.0": - version "4.19.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.19.0.tgz#e9d9219ddf6f6e946e2ee322198af12466d2c868" - integrity sha512-2Rn36Ubxdv32NUcfm0wB1tgKqkQuft00PtM23VqLuCUR4N5jcNWDoV5iBC9jeGdgS38WK66ElncprqgMUOyomw== - -"@rollup/rollup-linux-arm-musleabihf@4.19.0": - version "4.19.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.19.0.tgz#4ba804a00b5e793196a622f6977e05f23e01f59a" - integrity sha512-gJuzIVdq/X1ZA2bHeCGCISe0VWqCoNT8BvkQ+BfsixXwTOndhtLUpOg0A1Fcx/+eA6ei6rMBzlOz4JzmiDw7JQ== - -"@rollup/rollup-linux-arm64-gnu@4.19.0": - version "4.19.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.19.0.tgz#d871e3f41de759a6db27fc99235b782ba47c15cc" - integrity sha512-0EkX2HYPkSADo9cfeGFoQ7R0/wTKb7q6DdwI4Yn/ULFE1wuRRCHybxpl2goQrx4c/yzK3I8OlgtBu4xvted0ug== - -"@rollup/rollup-linux-arm64-musl@4.19.0": - version "4.19.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.19.0.tgz#6e63f7ad4cc51bd2c693a2826fd279de9eaa05b5" - integrity sha512-GlIQRj9px52ISomIOEUq/IojLZqzkvRpdP3cLgIE1wUWaiU5Takwlzpz002q0Nxxr1y2ZgxC2obWxjr13lvxNQ== - -"@rollup/rollup-linux-powerpc64le-gnu@4.19.0": - version "4.19.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.19.0.tgz#1540b284d91c440bc9fa7a1714cfb71a5597e94d" - integrity sha512-N6cFJzssruDLUOKfEKeovCKiHcdwVYOT1Hs6dovDQ61+Y9n3Ek4zXvtghPPelt6U0AH4aDGnDLb83uiJMkWYzQ== - -"@rollup/rollup-linux-riscv64-gnu@4.19.0": - version "4.19.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.19.0.tgz#70ae58103b5bc7ba2e2235738b51d97022c8ef92" - integrity sha512-2DnD3mkS2uuam/alF+I7M84koGwvn3ZVD7uG+LEWpyzo/bq8+kKnus2EVCkcvh6PlNB8QPNFOz6fWd5N8o1CYg== - -"@rollup/rollup-linux-s390x-gnu@4.19.0": - version "4.19.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.19.0.tgz#579ca5f271421a961d3c73d221202c79e02ff03a" - integrity sha512-D6pkaF7OpE7lzlTOFCB2m3Ngzu2ykw40Nka9WmKGUOTS3xcIieHe82slQlNq69sVB04ch73thKYIWz/Ian8DUA== - -"@rollup/rollup-linux-x64-gnu@4.19.0": - version "4.19.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.19.0.tgz#f0282d761b8b4e7b92b236813475248e37231849" - integrity sha512-HBndjQLP8OsdJNSxpNIN0einbDmRFg9+UQeZV1eiYupIRuZsDEoeGU43NQsS34Pp166DtwQOnpcbV/zQxM+rWA== - -"@rollup/rollup-linux-x64-musl@4.19.0": - version "4.19.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.19.0.tgz#65da807ac66c505ad14b76f1e5976006cb67dd5f" - integrity sha512-HxfbvfCKJe/RMYJJn0a12eiOI9OOtAUF4G6ozrFUK95BNyoJaSiBjIOHjZskTUffUrB84IPKkFG9H9nEvJGW6A== - -"@rollup/rollup-win32-arm64-msvc@4.19.0": - version "4.19.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.19.0.tgz#1eed24b91f421c2eea8bb7ca8889ba0c867e1780" - integrity sha512-HxDMKIhmcguGTiP5TsLNolwBUK3nGGUEoV/BO9ldUBoMLBssvh4J0X8pf11i1fTV7WShWItB1bKAKjX4RQeYmg== - -"@rollup/rollup-win32-ia32-msvc@4.19.0": - version "4.19.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.19.0.tgz#1ed93c9cdc84e185359797a686f4d1576afcea58" - integrity sha512-xItlIAZZaiG/u0wooGzRsx11rokP4qyc/79LkAOdznGRAbOFc+SfEdfUOszG1odsHNgwippUJavag/+W/Etc6Q== - -"@rollup/rollup-win32-x64-msvc@4.19.0": - version "4.19.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.19.0.tgz#baf9b65023ea2ecc5e6ec68f787a0fecfd8ee84c" - integrity sha512-xNo5fV5ycvCCKqiZcpB65VMR11NJB+StnxHz20jdqRAktfdfzhgjTiJ2doTDQE/7dqGaV5I7ZGqKpgph6lCIag== +"@rollup/rollup-android-arm-eabi@4.21.2": + version "4.21.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.2.tgz#0412834dc423d1ff7be4cb1fc13a86a0cd262c11" + integrity sha512-fSuPrt0ZO8uXeS+xP3b+yYTCBUd05MoSp2N/MFOgjhhUhMmchXlpTQrTpI8T+YAwAQuK7MafsCOxW7VrPMrJcg== + +"@rollup/rollup-android-arm64@4.21.2": + version "4.21.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.2.tgz#baf1a014b13654f3b9e835388df9caf8c35389cb" + integrity sha512-xGU5ZQmPlsjQS6tzTTGwMsnKUtu0WVbl0hYpTPauvbRAnmIvpInhJtgjj3mcuJpEiuUw4v1s4BimkdfDWlh7gA== + +"@rollup/rollup-darwin-arm64@4.21.2": + version "4.21.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.2.tgz#0a2c364e775acdf1172fe3327662eec7c46e55b1" + integrity sha512-99AhQ3/ZMxU7jw34Sq8brzXqWH/bMnf7ZVhvLk9QU2cOepbQSVTns6qoErJmSiAvU3InRqC2RRZ5ovh1KN0d0Q== + +"@rollup/rollup-darwin-x64@4.21.2": + version "4.21.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.2.tgz#a972db75890dfab8df0da228c28993220a468c42" + integrity sha512-ZbRaUvw2iN/y37x6dY50D8m2BnDbBjlnMPotDi/qITMJ4sIxNY33HArjikDyakhSv0+ybdUxhWxE6kTI4oX26w== + +"@rollup/rollup-linux-arm-gnueabihf@4.21.2": + version "4.21.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.2.tgz#1609d0630ef61109dd19a278353e5176d92e30a1" + integrity sha512-ztRJJMiE8nnU1YFcdbd9BcH6bGWG1z+jP+IPW2oDUAPxPjo9dverIOyXz76m6IPA6udEL12reYeLojzW2cYL7w== + +"@rollup/rollup-linux-arm-musleabihf@4.21.2": + version "4.21.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.2.tgz#3c1dca5f160aa2e79e4b20ff6395eab21804f266" + integrity sha512-flOcGHDZajGKYpLV0JNc0VFH361M7rnV1ee+NTeC/BQQ1/0pllYcFmxpagltANYt8FYf9+kL6RSk80Ziwyhr7w== + +"@rollup/rollup-linux-arm64-gnu@4.21.2": + version "4.21.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.2.tgz#c2fe376e8b04eafb52a286668a8df7c761470ac7" + integrity sha512-69CF19Kp3TdMopyteO/LJbWufOzqqXzkrv4L2sP8kfMaAQ6iwky7NoXTp7bD6/irKgknDKM0P9E/1l5XxVQAhw== + +"@rollup/rollup-linux-arm64-musl@4.21.2": + version "4.21.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.2.tgz#e62a4235f01e0f66dbba587c087ca6db8008ec80" + integrity sha512-48pD/fJkTiHAZTnZwR0VzHrao70/4MlzJrq0ZsILjLW/Ab/1XlVUStYyGt7tdyIiVSlGZbnliqmult/QGA2O2w== + +"@rollup/rollup-linux-powerpc64le-gnu@4.21.2": + version "4.21.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.2.tgz#24b3457e75ee9ae5b1c198bd39eea53222a74e54" + integrity sha512-cZdyuInj0ofc7mAQpKcPR2a2iu4YM4FQfuUzCVA2u4HI95lCwzjoPtdWjdpDKyHxI0UO82bLDoOaLfpZ/wviyQ== + +"@rollup/rollup-linux-riscv64-gnu@4.21.2": + version "4.21.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.2.tgz#38edfba9620fe2ca8116c97e02bd9f2d606bde09" + integrity sha512-RL56JMT6NwQ0lXIQmMIWr1SW28z4E4pOhRRNqwWZeXpRlykRIlEpSWdsgNWJbYBEWD84eocjSGDu/XxbYeCmwg== + +"@rollup/rollup-linux-s390x-gnu@4.21.2": + version "4.21.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.2.tgz#a3bfb8bc5f1e802f8c76cff4a4be2e9f9ac36a18" + integrity sha512-PMxkrWS9z38bCr3rWvDFVGD6sFeZJw4iQlhrup7ReGmfn7Oukrr/zweLhYX6v2/8J6Cep9IEA/SmjXjCmSbrMQ== + +"@rollup/rollup-linux-x64-gnu@4.21.2": + version "4.21.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.2.tgz#0dadf34be9199fcdda44b5985a086326344f30ad" + integrity sha512-B90tYAUoLhU22olrafY3JQCFLnT3NglazdwkHyxNDYF/zAxJt5fJUB/yBoWFoIQ7SQj+KLe3iL4BhOMa9fzgpw== + +"@rollup/rollup-linux-x64-musl@4.21.2": + version "4.21.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.2.tgz#7b7deddce240400eb87f2406a445061b4fed99a8" + integrity sha512-7twFizNXudESmC9oneLGIUmoHiiLppz/Xs5uJQ4ShvE6234K0VB1/aJYU3f/4g7PhssLGKBVCC37uRkkOi8wjg== + +"@rollup/rollup-win32-arm64-msvc@4.21.2": + version "4.21.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.2.tgz#a0ca0c5149c2cfb26fab32e6ba3f16996fbdb504" + integrity sha512-9rRero0E7qTeYf6+rFh3AErTNU1VCQg2mn7CQcI44vNUWM9Ze7MSRS/9RFuSsox+vstRt97+x3sOhEey024FRQ== + +"@rollup/rollup-win32-ia32-msvc@4.21.2": + version "4.21.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.2.tgz#aae2886beec3024203dbb5569db3a137bc385f8e" + integrity sha512-5rA4vjlqgrpbFVVHX3qkrCo/fZTj1q0Xxpg+Z7yIo3J2AilW7t2+n6Q8Jrx+4MrYpAnjttTYF8rr7bP46BPzRw== + +"@rollup/rollup-win32-x64-msvc@4.21.2": + version "4.21.2" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.2.tgz#e4291e3c1bc637083f87936c333cdbcad22af63b" + integrity sha512-6UUxd0+SKomjdzuAcp+HAmxw1FlGBnl1v2yEPSabtx4lBfdXHDVsW7+lQkgz9cNFJGY3AWR7+V8P5BqkD9L9nA== "@types/babel__core@^7.20.5": version "7.20.5" @@ -549,13 +561,6 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== -"@types/node@^22.0.0": - version "22.0.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.0.0.tgz#04862a2a71e62264426083abe1e27e87cac05a30" - integrity sha512-VT7KSYudcPOzP5Q0wfbowyNLaVR8QWUdw+088uFWwfvpY6uCWaXpqV6ieLAu9WBcnTa7H4Z5RLK8I5t2FuOcqw== - dependencies: - undici-types "~6.11.1" - "@types/prop-types@*": version "15.7.12" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6" @@ -576,92 +581,87 @@ "@types/prop-types" "*" csstype "^3.0.2" -"@typescript-eslint/eslint-plugin@^7.15.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.17.0.tgz#c8ed1af1ad2928ede5cdd207f7e3090499e1f77b" - integrity sha512-pyiDhEuLM3PuANxH7uNYan1AaFs5XE0zw1hq69JBvGvE7gSuEoQl1ydtEe/XQeoC3GQxLXyOVa5kNOATgM638A== +"@typescript-eslint/eslint-plugin@8.5.0": + version "8.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.5.0.tgz#7c1863693a98371703686e1c0fac64ffc576cdb1" + integrity sha512-lHS5hvz33iUFQKuPFGheAB84LwcJ60G8vKnEhnfcK1l8kGVLro2SFYW6K0/tj8FUhRJ0VHyg1oAfg50QGbPPHw== dependencies: "@eslint-community/regexpp" "^4.10.0" - "@typescript-eslint/scope-manager" "7.17.0" - "@typescript-eslint/type-utils" "7.17.0" - "@typescript-eslint/utils" "7.17.0" - "@typescript-eslint/visitor-keys" "7.17.0" + "@typescript-eslint/scope-manager" "8.5.0" + "@typescript-eslint/type-utils" "8.5.0" + "@typescript-eslint/utils" "8.5.0" + "@typescript-eslint/visitor-keys" "8.5.0" graphemer "^1.4.0" ignore "^5.3.1" natural-compare "^1.4.0" ts-api-utils "^1.3.0" -"@typescript-eslint/parser@^7.15.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.17.0.tgz#be8e32c159190cd40a305a2121220eadea5a88e7" - integrity sha512-puiYfGeg5Ydop8eusb/Hy1k7QmOU6X3nvsqCgzrB2K4qMavK//21+PzNE8qeECgNOIoertJPUC1SpegHDI515A== +"@typescript-eslint/parser@8.5.0": + version "8.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.5.0.tgz#d590e1ef9f31f26d423999ad3f687723247e6bcc" + integrity sha512-gF77eNv0Xz2UJg/NbpWJ0kqAm35UMsvZf1GHj8D9MRFTj/V3tAciIWXfmPLsAAF/vUlpWPvUDyH1jjsr0cMVWw== dependencies: - "@typescript-eslint/scope-manager" "7.17.0" - "@typescript-eslint/types" "7.17.0" - "@typescript-eslint/typescript-estree" "7.17.0" - "@typescript-eslint/visitor-keys" "7.17.0" + "@typescript-eslint/scope-manager" "8.5.0" + "@typescript-eslint/types" "8.5.0" + "@typescript-eslint/typescript-estree" "8.5.0" + "@typescript-eslint/visitor-keys" "8.5.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@7.17.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.17.0.tgz#e072d0f914662a7bfd6c058165e3c2b35ea26b9d" - integrity sha512-0P2jTTqyxWp9HiKLu/Vemr2Rg1Xb5B7uHItdVZ6iAenXmPo4SZ86yOPCJwMqpCyaMiEHTNqizHfsbmCFT1x9SA== +"@typescript-eslint/scope-manager@8.5.0": + version "8.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.5.0.tgz#385341de65b976f02b295b8aca54bb4ffd6b5f07" + integrity sha512-06JOQ9Qgj33yvBEx6tpC8ecP9o860rsR22hWMEd12WcTRrfaFgHr2RB/CA/B+7BMhHkXT4chg2MyboGdFGawYg== dependencies: - "@typescript-eslint/types" "7.17.0" - "@typescript-eslint/visitor-keys" "7.17.0" + "@typescript-eslint/types" "8.5.0" + "@typescript-eslint/visitor-keys" "8.5.0" -"@typescript-eslint/type-utils@7.17.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.17.0.tgz#c5da78feb134c9c9978cbe89e2b1a589ed22091a" - integrity sha512-XD3aaBt+orgkM/7Cei0XNEm1vwUxQ958AOLALzPlbPqb8C1G8PZK85tND7Jpe69Wualri81PLU+Zc48GVKIMMA== +"@typescript-eslint/type-utils@8.5.0": + version "8.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.5.0.tgz#6215b23aa39dbbd8dde0a4ef9ee0f745410c29b1" + integrity sha512-N1K8Ix+lUM+cIDhL2uekVn/ZD7TZW+9/rwz8DclQpcQ9rk4sIL5CAlBC0CugWKREmDjBzI/kQqU4wkg46jWLYA== dependencies: - "@typescript-eslint/typescript-estree" "7.17.0" - "@typescript-eslint/utils" "7.17.0" + "@typescript-eslint/typescript-estree" "8.5.0" + "@typescript-eslint/utils" "8.5.0" debug "^4.3.4" ts-api-utils "^1.3.0" -"@typescript-eslint/types@7.17.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.17.0.tgz#7ce8185bdf06bc3494e73d143dbf3293111b9cff" - integrity sha512-a29Ir0EbyKTKHnZWbNsrc/gqfIBqYPwj3F2M+jWE/9bqfEHg0AMtXzkbUkOG6QgEScxh2+Pz9OXe11jHDnHR7A== +"@typescript-eslint/types@8.5.0": + version "8.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.5.0.tgz#4465d99331d1276f8fb2030e4f9c73fe01a05bf9" + integrity sha512-qjkormnQS5wF9pjSi6q60bKUHH44j2APxfh9TQRXK8wbYVeDYYdYJGIROL87LGZZ2gz3Rbmjc736qyL8deVtdw== -"@typescript-eslint/typescript-estree@7.17.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.17.0.tgz#dcab3fea4c07482329dd6107d3c6480e228e4130" - integrity sha512-72I3TGq93t2GoSBWI093wmKo0n6/b7O4j9o8U+f65TVD0FS6bI2180X5eGEr8MA8PhKMvYe9myZJquUT2JkCZw== +"@typescript-eslint/typescript-estree@8.5.0": + version "8.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.5.0.tgz#6e5758cf2f63aa86e9ddfa4e284e2e0b81b87557" + integrity sha512-vEG2Sf9P8BPQ+d0pxdfndw3xIXaoSjliG0/Ejk7UggByZPKXmJmw3GW5jV2gHNQNawBUyfahoSiCFVov0Ruf7Q== dependencies: - "@typescript-eslint/types" "7.17.0" - "@typescript-eslint/visitor-keys" "7.17.0" + "@typescript-eslint/types" "8.5.0" + "@typescript-eslint/visitor-keys" "8.5.0" debug "^4.3.4" - globby "^11.1.0" + fast-glob "^3.3.2" is-glob "^4.0.3" minimatch "^9.0.4" semver "^7.6.0" ts-api-utils "^1.3.0" -"@typescript-eslint/utils@7.17.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.17.0.tgz#815cd85b9001845d41b699b0ce4f92d6dfb84902" - integrity sha512-r+JFlm5NdB+JXc7aWWZ3fKSm1gn0pkswEwIYsrGPdsT2GjsRATAKXiNtp3vgAAO1xZhX8alIOEQnNMl3kbTgJw== +"@typescript-eslint/utils@8.5.0": + version "8.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.5.0.tgz#4d4ffed96d0654546a37faa5b84bdce16d951634" + integrity sha512-6yyGYVL0e+VzGYp60wvkBHiqDWOpT63pdMV2CVG4LVDd5uR6q1qQN/7LafBZtAtNIn/mqXjsSeS5ggv/P0iECw== dependencies: "@eslint-community/eslint-utils" "^4.4.0" - "@typescript-eslint/scope-manager" "7.17.0" - "@typescript-eslint/types" "7.17.0" - "@typescript-eslint/typescript-estree" "7.17.0" + "@typescript-eslint/scope-manager" "8.5.0" + "@typescript-eslint/types" "8.5.0" + "@typescript-eslint/typescript-estree" "8.5.0" -"@typescript-eslint/visitor-keys@7.17.0": - version "7.17.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.17.0.tgz#680465c734be30969e564b4647f38d6cdf49bfb0" - integrity sha512-RVGC9UhPOCsfCdI9pU++K4nD7to+jTcMIbXTSOcrLqUEW6gF2pU1UUbYJKc9cvcRSK1UDeMJ7pdMxf4bhMpV/A== +"@typescript-eslint/visitor-keys@8.5.0": + version "8.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.5.0.tgz#13028df3b866d2e3e2e2cc4193cf2c1e0e04c4bf" + integrity sha512-yTPqMnbAZJNy2Xq2XU8AdtOW9tJIr+UQb64aXB9f3B1498Zx9JorVgFJcZpEc9UBuCCrdzKID2RGAMkYcDtZOw== dependencies: - "@typescript-eslint/types" "7.17.0" + "@typescript-eslint/types" "8.5.0" eslint-visitor-keys "^3.4.3" -"@ungap/structured-clone@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" - integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== - "@vitejs/plugin-react@^4.3.1": version "4.3.1" resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.3.1.tgz#d0be6594051ded8957df555ff07a991fb618b48e" @@ -678,7 +678,7 @@ acorn-jsx@^5.3.2: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn@^8.9.0: +acorn@^8.12.0: version "8.12.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== @@ -717,25 +717,6 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== - -axios@^1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.2.tgz#b625db8a7051fbea61c35a3cbb3a1daa7b9c7621" - integrity sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw== - dependencies: - follow-redirects "^1.15.6" - form-data "^4.0.0" - proxy-from-env "^1.1.0" - balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" @@ -824,13 +805,6 @@ color-name@~1.1.4: resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -combined-stream@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - concat-map@0.0.1: version "0.0.1" resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" @@ -867,25 +841,6 @@ deep-is@^0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== - -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - -doctrine@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz" - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== - dependencies: - esutils "^2.0.2" - electron-to-chromium@^1.4.820: version "1.5.2" resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.2.tgz" @@ -935,66 +890,67 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -eslint-plugin-react-hooks@^4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz#c829eb06c0e6f484b3fbb85a97e57784f328c596" - integrity sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ== +eslint-plugin-react-hooks@^5.1.0-rc.0: + version "5.1.0-rc-fb9a90fa48-20240614" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0-rc-fb9a90fa48-20240614.tgz#206a7ec005f0b286aaf7091f4e566083d310b189" + integrity sha512-xsiRwaDNF5wWNC4ZHLut+x/YcAxksUd9Rizt7LaEn3bV8VyYRpXnRJQlLOfYaVy9esk4DFP4zPPnoNVjq5Gc0w== -eslint-plugin-react-refresh@^0.4.7: - version "0.4.9" - resolved "https://registry.yarnpkg.com/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.9.tgz#bf870372b353b12e1e6fb7fc41b282d9cbc8d93d" - integrity sha512-QK49YrBAo5CLNLseZ7sZgvgTy21E6NEw22eZqc4teZfH8pxV3yXc9XXOYfUI6JNpw7mfHNkAeWtBxrTyykB6HA== +eslint-plugin-react-refresh@^0.4.9: + version "0.4.11" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.11.tgz#e450761a2bdb260aa10cfb73f846209a737827cb" + integrity sha512-wrAKxMbVr8qhXTtIKfXqAn5SAtRZt0aXxe5P23Fh4pUAdC6XEsybGLB8P0PI4j1yYqOgUEUlzKAGDfo7rJOjcw== -eslint-scope@^7.2.2: - version "7.2.2" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" - integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== +eslint-scope@^8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.0.2.tgz#5cbb33d4384c9136083a71190d548158fe128f94" + integrity sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" -eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.3: version "3.4.3" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -eslint@^8.57.0: - version "8.57.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668" - integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== +eslint-visitor-keys@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz#e3adc021aa038a2a8e0b2f8b0ce8f66b9483b1fb" + integrity sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw== + +eslint@^9.9.0: + version "9.10.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.10.0.tgz#0bd74d7fe4db77565d0e7f57c7df6d2b04756806" + integrity sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw== dependencies: "@eslint-community/eslint-utils" "^4.2.0" - "@eslint-community/regexpp" "^4.6.1" - "@eslint/eslintrc" "^2.1.4" - "@eslint/js" "8.57.0" - "@humanwhocodes/config-array" "^0.11.14" + "@eslint-community/regexpp" "^4.11.0" + "@eslint/config-array" "^0.18.0" + "@eslint/eslintrc" "^3.1.0" + "@eslint/js" "9.10.0" + "@eslint/plugin-kit" "^0.1.0" "@humanwhocodes/module-importer" "^1.0.1" + "@humanwhocodes/retry" "^0.3.0" "@nodelib/fs.walk" "^1.2.8" - "@ungap/structured-clone" "^1.2.0" ajv "^6.12.4" chalk "^4.0.0" cross-spawn "^7.0.2" debug "^4.3.2" - doctrine "^3.0.0" escape-string-regexp "^4.0.0" - eslint-scope "^7.2.2" - eslint-visitor-keys "^3.4.3" - espree "^9.6.1" - esquery "^1.4.2" + eslint-scope "^8.0.2" + eslint-visitor-keys "^4.0.0" + espree "^10.1.0" + esquery "^1.5.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" + file-entry-cache "^8.0.0" find-up "^5.0.0" glob-parent "^6.0.2" - globals "^13.19.0" - graphemer "^1.4.0" ignore "^5.2.0" imurmurhash "^0.1.4" is-glob "^4.0.0" is-path-inside "^3.0.3" - js-yaml "^4.1.0" json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" lodash.merge "^4.6.2" minimatch "^3.1.2" natural-compare "^1.4.0" @@ -1002,16 +958,16 @@ eslint@^8.57.0: strip-ansi "^6.0.1" text-table "^0.2.0" -espree@^9.6.0, espree@^9.6.1: - version "9.6.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" - integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== +espree@^10.0.1, espree@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.1.0.tgz#8788dae611574c0f070691f522e4116c5a11fc56" + integrity sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA== dependencies: - acorn "^8.9.0" + acorn "^8.12.0" acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.4.1" + eslint-visitor-keys "^4.0.0" -esquery@^1.4.2: +esquery@^1.5.0: version "1.6.0" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== @@ -1040,9 +996,9 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.2.9: +fast-glob@^3.3.2: version "3.3.2" - resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== dependencies: "@nodelib/fs.stat" "^2.0.2" @@ -1068,12 +1024,12 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" -file-entry-cache@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" - integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== +file-entry-cache@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" + integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== dependencies: - flat-cache "^3.0.4" + flat-cache "^4.0.0" fill-range@^7.1.1: version "7.1.1" @@ -1090,39 +1046,19 @@ find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" -flat-cache@^3.0.4: - version "3.2.0" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" - integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== +flat-cache@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" + integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== dependencies: flatted "^3.2.9" - keyv "^4.5.3" - rimraf "^3.0.2" + keyv "^4.5.4" flatted@^3.2.9: version "3.3.1" resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== -follow-redirects@^1.15.6: - version "1.15.6" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" - integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== - -form-data@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" - integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - fsevents@~2.3.2, fsevents@~2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" @@ -1147,41 +1083,20 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" -glob@^7.1.3: - version "7.2.3" - resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - globals@^11.1.0: version "11.12.0" resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^13.19.0: - version "13.24.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" - integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== - dependencies: - type-fest "^0.20.2" +globals@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" + integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== -globby@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" - integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.2.9" - ignore "^5.2.0" - merge2 "^1.4.1" - slash "^3.0.0" +globals@^15.9.0: + version "15.9.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-15.9.0.tgz#e9de01771091ffbc37db5714dab484f9f69ff399" + integrity sha512-SmSKyLLKFbSr6rptvP8izbyxJL4ILwqO9Jg23UA0sDlGlu58V59D1//I3vlc0KJphVdUR7vMjHIplYnzBxorQA== graphemer@^1.4.0: version "1.4.0" @@ -1216,19 +1131,6 @@ imurmurhash@^0.1.4: resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2: - version "2.0.4" - resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" @@ -1293,7 +1195,7 @@ json5@^2.2.3: resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== -keyv@^4.5.3: +keyv@^4.5.4: version "4.5.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== @@ -1334,7 +1236,7 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" -merge2@^1.3.0, merge2@^1.4.1: +merge2@^1.3.0: version "1.4.1" resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== @@ -1347,19 +1249,7 @@ micromatch@^4.0.4: braces "^3.0.3" picomatch "^2.3.1" -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.12: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: +minimatch@^3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -1393,13 +1283,6 @@ node-releases@^2.0.14: resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz" integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== -once@^1.3.0: - version "1.4.0" - resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - optionator@^0.9.3: version "0.9.4" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" @@ -1438,21 +1321,11 @@ path-exists@^4.0.0: resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - path-key@^3.1.0: version "3.1.1" resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - picocolors@^1.0.0, picocolors@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz" @@ -1463,10 +1336,10 @@ picomatch@^2.3.1: resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -postcss@^8.4.39: - version "8.4.40" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.40.tgz#eb81f2a4dd7668ed869a6db25999e02e9ad909d8" - integrity sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q== +postcss@^8.4.43: + version "8.4.45" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.45.tgz#538d13d89a16ef71edbf75d895284ae06b79e603" + integrity sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q== dependencies: nanoid "^3.3.7" picocolors "^1.0.1" @@ -1477,11 +1350,6 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -proxy-from-env@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== - punycode@^2.1.0: version "2.3.1" resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" @@ -1522,36 +1390,29 @@ reusify@^1.0.4: resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -rollup@^4.13.0: - version "4.19.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.19.0.tgz#83b08cc0b2bc38c26c194cb7f2cdabd84a2a8c02" - integrity sha512-5r7EYSQIowHsK4eTZ0Y81qpZuJz+MUuYeqmmYmRMl1nwhdmbiYqt5jwzf6u7wyOzJgYqtCRMtVRKOtHANBz7rA== +rollup@^4.20.0: + version "4.21.2" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.21.2.tgz#f41f277a448d6264e923dd1ea179f0a926aaf9b7" + integrity sha512-e3TapAgYf9xjdLvKQCkQTnbTKd4a6jwlpQSJJFokHGaX2IVjoEqkIIhiQfqsi0cdwlOD+tQGuOd5AJkc5RngBw== dependencies: "@types/estree" "1.0.5" optionalDependencies: - "@rollup/rollup-android-arm-eabi" "4.19.0" - "@rollup/rollup-android-arm64" "4.19.0" - "@rollup/rollup-darwin-arm64" "4.19.0" - "@rollup/rollup-darwin-x64" "4.19.0" - "@rollup/rollup-linux-arm-gnueabihf" "4.19.0" - "@rollup/rollup-linux-arm-musleabihf" "4.19.0" - "@rollup/rollup-linux-arm64-gnu" "4.19.0" - "@rollup/rollup-linux-arm64-musl" "4.19.0" - "@rollup/rollup-linux-powerpc64le-gnu" "4.19.0" - "@rollup/rollup-linux-riscv64-gnu" "4.19.0" - "@rollup/rollup-linux-s390x-gnu" "4.19.0" - "@rollup/rollup-linux-x64-gnu" "4.19.0" - "@rollup/rollup-linux-x64-musl" "4.19.0" - "@rollup/rollup-win32-arm64-msvc" "4.19.0" - "@rollup/rollup-win32-ia32-msvc" "4.19.0" - "@rollup/rollup-win32-x64-msvc" "4.19.0" + "@rollup/rollup-android-arm-eabi" "4.21.2" + "@rollup/rollup-android-arm64" "4.21.2" + "@rollup/rollup-darwin-arm64" "4.21.2" + "@rollup/rollup-darwin-x64" "4.21.2" + "@rollup/rollup-linux-arm-gnueabihf" "4.21.2" + "@rollup/rollup-linux-arm-musleabihf" "4.21.2" + "@rollup/rollup-linux-arm64-gnu" "4.21.2" + "@rollup/rollup-linux-arm64-musl" "4.21.2" + "@rollup/rollup-linux-powerpc64le-gnu" "4.21.2" + "@rollup/rollup-linux-riscv64-gnu" "4.21.2" + "@rollup/rollup-linux-s390x-gnu" "4.21.2" + "@rollup/rollup-linux-x64-gnu" "4.21.2" + "@rollup/rollup-linux-x64-musl" "4.21.2" + "@rollup/rollup-win32-arm64-msvc" "4.21.2" + "@rollup/rollup-win32-ia32-msvc" "4.21.2" + "@rollup/rollup-win32-x64-msvc" "4.21.2" fsevents "~2.3.2" run-parallel@^1.1.9: @@ -1590,11 +1451,6 @@ shebang-regex@^3.0.0: resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - source-map-js@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz" @@ -1655,20 +1511,19 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" -type-fest@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== - -typescript@^5.2.2: - version "5.5.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba" - integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== +typescript-eslint@^8.0.1: + version "8.5.0" + resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.5.0.tgz#041f6c302d0e9a8e116a33d60b0bb19f34036dd7" + integrity sha512-uD+XxEoSIvqtm4KE97etm32Tn5MfaZWgWfMMREStLxR6JzvHkc2Tkj7zhTEK5XmtpTmKHNnG8Sot6qDfhHtR1Q== + dependencies: + "@typescript-eslint/eslint-plugin" "8.5.0" + "@typescript-eslint/parser" "8.5.0" + "@typescript-eslint/utils" "8.5.0" -undici-types@~6.11.1: - version "6.11.1" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.11.1.tgz#432ea6e8efd54a48569705a699e62d8f4981b197" - integrity sha512-mIDEX2ek50x0OlRgxryxsenE5XaQD4on5U2inY7RApK3SOJpofyw7uW2AyfMKkhAxXIceo2DeWGVGwyvng1GNQ== +typescript@^5.5.3: + version "5.6.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.2.tgz#d1de67b6bef77c41823f822df8f0b3bcff60a5a0" + integrity sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw== update-browserslist-db@^1.1.0: version "1.1.0" @@ -1685,14 +1540,14 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -vite@^5.3.4: - version "5.3.5" - resolved "https://registry.yarnpkg.com/vite/-/vite-5.3.5.tgz#b847f846fb2b6cb6f6f4ed50a830186138cb83d8" - integrity sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA== +vite@^5.4.1: + version "5.4.3" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.3.tgz#771c470e808cb6732f204e1ee96c2ed65b97a0eb" + integrity sha512-IH+nl64eq9lJjFqU+/yrRnrHPVTlgy42/+IzbOdaFDVlyLgI/wDlf+FCobXLX1cT0X5+7LMyH1mIy2xJdLfo8Q== dependencies: esbuild "^0.21.3" - postcss "^8.4.39" - rollup "^4.13.0" + postcss "^8.4.43" + rollup "^4.20.0" optionalDependencies: fsevents "~2.3.3" @@ -1708,11 +1563,6 @@ word-wrap@^1.2.5: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== -wrappy@1: - version "1.0.2" - resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - yallist@^3.0.2: version "3.1.1" resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" diff --git a/applications/samples/tasks/sum/main.py b/applications/samples/tasks/sum/main.py index 578c49520..30a94736e 100644 --- a/applications/samples/tasks/sum/main.py +++ b/applications/samples/tasks/sum/main.py @@ -1,9 +1,9 @@ +from cloudharness.workflows.utils import get_shared_directory import sys import os assert len(sys.argv) > 2, 'Arguments not specified. Cannot proceed' -from cloudharness.workflows.utils import get_shared_directory a = float(sys.argv[1]) b = float(sys.argv[2]) @@ -15,4 +15,4 @@ print("File name is", file_name) with open(file_name, "w") as f: - f.write(str(a+b)) + f.write(str(a + b)) diff --git a/applications/samples/test/api/test_st.py b/applications/samples/test/api/test_st.py index afac61dc2..e67cb3e55 100644 --- a/applications/samples/test/api/test_st.py +++ b/applications/samples/test/api/test_st.py @@ -3,7 +3,7 @@ import schemathesis as st from schemathesis.checks import response_schema_conformance, not_a_server_error -from cloudharness_test import apitest_init # include to perform default authorization +from cloudharness_test import apitest_init # include to perform default authorization app_url = os.environ.get("APP_URL", "http://samples.ch.local/api") @@ -16,18 +16,21 @@ def test_api(case): pprint(response.__dict__) assert response.status_code >= 500, "this api errors on purpose" + @schema.parametrize(endpoint="/valid") def test_bearer(case): response = case.call() - + case.validate_response(response, checks=(response_schema_conformance,)) + @schema.parametrize(endpoint="/valid-cookie") def test_cookie(case): response = case.call() case.validate_response(response, checks=(response_schema_conformance,)) + @schema.parametrize(endpoint="/sampleresources", method="POST") def test_response(case): response = case.call() - case.validate_response(response, checks=(response_schema_conformance,)) \ No newline at end of file + case.validate_response(response, checks=(response_schema_conformance,)) diff --git a/applications/volumemanager/server/setup.py b/applications/volumemanager/server/setup.py index 5efc5693c..2d6ff8de9 100644 --- a/applications/volumemanager/server/setup.py +++ b/applications/volumemanager/server/setup.py @@ -36,4 +36,3 @@ CloudHarness Volumes manager API """ ) - diff --git a/applications/volumemanager/server/volumemanager/controllers/rest_controller.py b/applications/volumemanager/server/volumemanager/controllers/rest_controller.py index 879fb7434..5eb30a4f5 100644 --- a/applications/volumemanager/server/volumemanager/controllers/rest_controller.py +++ b/applications/volumemanager/server/volumemanager/controllers/rest_controller.py @@ -8,6 +8,7 @@ from volumemanager.models.persistent_volume_claim_create import PersistentVolumeClaimCreate # noqa: E501 from volumemanager import util + def pvc_name_get(name): # noqa: E501 """Used to retrieve a Persistent Volume Claim from the Kubernetes repository. @@ -26,7 +27,7 @@ def pvc_name_get(name): # noqa: E501 name=pvc.metadata.name, namespace=pvc.metadata.namespace, accessmode=pvc.status.access_modes[0], - size=pvc.status.capacity.get('storage','') + size=pvc.status.capacity.get('storage', '') ) return pvc @@ -44,7 +45,7 @@ def pvc_post(): # noqa: E501 if connexion.request.is_json: persistent_volume_claim_create = PersistentVolumeClaimCreate.from_dict(connexion.request.get_json()) # noqa: E501 create_persistent_volume_claim( - name=persistent_volume_claim_create.name, + name=persistent_volume_claim_create.name, size=persistent_volume_claim_create.size, logger=flask.current_app.logger) return 'Saved!' diff --git a/applications/volumemanager/server/volumemanager/controllers/security_controller_.py b/applications/volumemanager/server/volumemanager/controllers/security_controller_.py index 8dd254a23..c052e680f 100644 --- a/applications/volumemanager/server/volumemanager/controllers/security_controller_.py +++ b/applications/volumemanager/server/volumemanager/controllers/security_controller_.py @@ -13,5 +13,3 @@ def info_from_bearerAuth(token): :rtype: dict | None """ return {'uid': 'user_id'} - - diff --git a/applications/volumemanager/server/volumemanager/test/test_rest_controller.py b/applications/volumemanager/server/volumemanager/test/test_rest_controller.py index d62a1dbff..ec05bbbd5 100644 --- a/applications/volumemanager/server/volumemanager/test/test_rest_controller.py +++ b/applications/volumemanager/server/volumemanager/test/test_rest_controller.py @@ -19,7 +19,7 @@ def test_pvc_name_get(self): Used to retrieve a Persistent Volume Claim from the Kubernetes repository. """ - headers = { + headers = { 'Accept': 'application/json', 'Authorization': 'Bearer special-key', } @@ -36,10 +36,10 @@ def test_pvc_post(self): Used to create a Persistent Volume Claim in Kubernetes """ persistent_volume_claim_create = { - "size" : "2Gi (see also https://github.com/kubernetes/community/blob/master/contributors/design-proposals/scheduling/resources.md#resource-quantities)", - "name" : "pvc-1" -} - headers = { + "size": "2Gi (see also https://github.com/kubernetes/community/blob/master/contributors/design-proposals/scheduling/resources.md#resource-quantities)", + "name": "pvc-1" + } + headers = { 'Accept': 'application/json', 'Content-Type': 'application/json', 'Authorization': 'Bearer special-key', diff --git a/applications/volumemanager/server/volumemanager/util.py b/applications/volumemanager/server/volumemanager/util.py index 0c0ee9983..6f7117ad0 100644 --- a/applications/volumemanager/server/volumemanager/util.py +++ b/applications/volumemanager/server/volumemanager/util.py @@ -68,8 +68,8 @@ def deserialize_date(string): :rtype: date """ if string is None: - return None - + return None + try: from dateutil.parser import parse return parse(string).date() @@ -88,8 +88,8 @@ def deserialize_datetime(string): :rtype: datetime """ if string is None: - return None - + return None + try: from dateutil.parser import parse return parse(string) diff --git a/applications/workflows/server/setup.py b/applications/workflows/server/setup.py index 796a48125..c89f97a9b 100644 --- a/applications/workflows/server/setup.py +++ b/applications/workflows/server/setup.py @@ -36,4 +36,3 @@ Workflows API """ ) - diff --git a/applications/workflows/server/workflows_api/__main__.py b/applications/workflows/server/workflows_api/__main__.py index a49051eb4..bafa51099 100644 --- a/applications/workflows/server/workflows_api/__main__.py +++ b/applications/workflows/server/workflows_api/__main__.py @@ -6,5 +6,3 @@ if __name__ == '__main__': main() - - diff --git a/applications/workflows/server/workflows_api/test/test_create_and_access_controller.py b/applications/workflows/server/workflows_api/test/test_create_and_access_controller.py index 04b7ca3b5..ca147dadf 100644 --- a/applications/workflows/server/workflows_api/test/test_create_and_access_controller.py +++ b/applications/workflows/server/workflows_api/test/test_create_and_access_controller.py @@ -20,7 +20,7 @@ def test_delete_operation(self): deletes operation by name """ - headers = { + headers = { } response = self.client.open( '/operations/{name}'.format(name='name_example'), @@ -34,7 +34,7 @@ def test_get_operation(self): get operation by name """ - headers = { + headers = { 'Accept': 'application/json', } response = self.client.open( @@ -52,7 +52,7 @@ def test_list_operations(self): query_string = [('status', QUEUED), ('previous_search_token', 'previous_search_token_example'), ('limit', 10)] - headers = { + headers = { 'Accept': 'application/json', } response = self.client.open( @@ -68,7 +68,7 @@ def test_log_operation(self): get operation by name """ - headers = { + headers = { 'Accept': 'text/plain', } response = self.client.open( diff --git a/applications/workflows/server/workflows_api/util.py b/applications/workflows/server/workflows_api/util.py index d161f16fb..04f3df25e 100644 --- a/applications/workflows/server/workflows_api/util.py +++ b/applications/workflows/server/workflows_api/util.py @@ -68,8 +68,8 @@ def deserialize_date(string): :rtype: date """ if string is None: - return None - + return None + try: from dateutil.parser import parse return parse(string).date() @@ -88,8 +88,8 @@ def deserialize_datetime(string): :rtype: datetime """ if string is None: - return None - + return None + try: from dateutil.parser import parse return parse(string) diff --git a/applications/workflows/tasks/notify-queue/main.py b/applications/workflows/tasks/notify-queue/main.py index 8c58da54d..882833551 100644 --- a/applications/workflows/tasks/notify-queue/main.py +++ b/applications/workflows/tasks/notify-queue/main.py @@ -1,13 +1,15 @@ + import sys import os import logging +from cloudharness.workflows.utils import notify_queue + logging.basicConfig(stream=sys.stdout, level=logging.INFO) assert len( sys.argv) > 3, 'Not all arguments not specified. Cannot notify queue. Usage: [workflow status] [queue name] [payload]' -from cloudharness.workflows.utils import notify_queue queue = sys.argv[2] message = {'status': sys.argv[1], 'payload': sys.argv[3], 'workflow': os.getenv('CH_WORKFLOW_NAME')} diff --git a/applications/workflows/tasks/send-result-event/main.py b/applications/workflows/tasks/send-result-event/main.py index a8cad83c3..b8f81e7b0 100644 --- a/applications/workflows/tasks/send-result-event/main.py +++ b/applications/workflows/tasks/send-result-event/main.py @@ -1,17 +1,20 @@ import sys import os -print("Starting send-result-event") + import glob from cloudharness import log, set_debug from cloudharness.workflows.utils import notify_queue -MAX_FILE_SIZE = 2 ** 20 # 1MB + from cloudharness.events.client import EventClient from cloudharness.workflows.utils import get_workflow_name + +MAX_FILE_SIZE = 2 ** 20 # 1MB +print("Starting send-result-event") set_debug() -topic_name = get_workflow_name() # Coming from the workflow name +topic_name = get_workflow_name() # Coming from the workflow name log.info("Topic name is: " + topic_name) @@ -23,7 +26,6 @@ log.info("Sending content of directory `{}` to event queue topic `{}`".format(shared_directory, topic_name)) - assert os.path.exists(shared_directory), shared_directory + " does not exist." for file_path in glob.glob(f"{shared_directory}/*"): @@ -31,7 +33,7 @@ size = os.path.getsize(file_path) if size > MAX_FILE_SIZE: log.warning(f"{file_path} size is {size}, which is greater than the maximum of {MAX_FILE_SIZE}." - "The content will not be sent to the queue") + "The content will not be sent to the queue") notify_queue(topic_name, {file_path: "Error: size exceeded"}) log.info("Sending content for file `{}`".format(file_path)) @@ -42,5 +44,4 @@ log.error("Error reading file " + file_path + " " + str(e)) continue - notify_queue(topic_name, {os.path.basename(file_path): content}) diff --git a/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/admin.py b/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/admin.py index f3db25938..493384e07 100644 --- a/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/admin.py +++ b/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/admin.py @@ -12,6 +12,7 @@ admin.site.unregister(User) admin.site.unregister(Group) + class CHUserAdmin(ExtraButtonsMixin, UserAdmin): def has_add_permission(self, request): @@ -45,5 +46,6 @@ def sync_keycloak(self, request): get_user_service().sync_kc_users_groups() self.message_user(request, 'Keycloak users & groups synced.') + admin.site.register(User, CHUserAdmin) admin.site.register(Group, CHGroupAdmin) diff --git a/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/exceptions.py b/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/exceptions.py index 4b14452af..8c8b7eab7 100644 --- a/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/exceptions.py +++ b/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/exceptions.py @@ -1,16 +1,21 @@ from keycloak.exceptions import KeycloakOperationError + class KeycloakOIDCNoAdminRole(KeycloakOperationError): pass + class KeycloakOIDCNoDefaultUserRole(KeycloakOperationError): pass + class KeycloakOIDCNoProjectError(KeycloakOperationError): pass + class KeycloakOIDCAuthServiceNotInitError(KeycloakOperationError): pass + class KeycloakOIDUserServiceNotInitError(KeycloakOperationError): - pass \ No newline at end of file + pass diff --git a/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/middleware.py b/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/middleware.py index dfe399c11..56f707e79 100644 --- a/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/middleware.py +++ b/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/middleware.py @@ -10,6 +10,7 @@ from cloudharness_django.services import get_user_service, get_auth_service from cloudharness import log + def _get_user(): bearer = get_authentication_token() if bearer: @@ -31,12 +32,12 @@ def _get_user(): except Exception as e: log.exception("User mapping error, %s", payload["email"]) return None - + return None class BearerTokenMiddleware: - def __init__(self, get_response = None): + def __init__(self, get_response=None): # One-time configuration and initialization. self.get_response = get_response diff --git a/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/models.py b/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/models.py index 2a44315bb..99ea89cb6 100644 --- a/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/models.py +++ b/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/models.py @@ -2,9 +2,11 @@ from django.contrib.auth.models import Group, User # Create your models here. + + class Team(models.Model): group = models.OneToOneField(Group, on_delete=models.CASCADE) - kc_id = models.CharField(max_length = 100) + kc_id = models.CharField(max_length=100) owner = models.ForeignKey(User, on_delete=models.DO_NOTHING) def __str__(self): @@ -13,8 +15,7 @@ def __str__(self): class Member(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) - kc_id = models.CharField(max_length = 100, db_index=True) + kc_id = models.CharField(max_length=100, db_index=True) def __str__(self): return f"{self.user.first_name} {self.user.last_name}" - diff --git a/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/services/__init__.py b/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/services/__init__.py index 2bee296bf..4efca5168 100644 --- a/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/services/__init__.py +++ b/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/services/__init__.py @@ -10,25 +10,28 @@ _auth_service = None _user_service = None + def get_auth_service(): global _auth_service if not _auth_service: raise KeycloakOIDCAuthServiceNotInitError("Auth Service not initialized") return _auth_service + def get_user_service(): global _user_service if not _user_service: raise KeycloakOIDUserServiceNotInitError("User Service not initialized") return _user_service + def init_services( client_name: str = settings.KC_CLIENT_NAME, client_roles: List[str] = settings.KC_ALL_ROLES, privileged_roles: List[str] = settings.KC_PRIVILEGED_ROLES, admin_role: str = settings.KC_ADMIN_ROLE, default_user_role: str = settings.KC_DEFAULT_USER_ROLE - ): +): from cloudharness_django.services.auth import AuthService from cloudharness_django.services.user import UserService global _auth_service, _user_service diff --git a/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/services/auth.py b/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/services/auth.py index cb3a9ae87..5c808e9e9 100644 --- a/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/services/auth.py +++ b/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/services/auth.py @@ -22,7 +22,7 @@ class AuthorizationLevel(Enum): # create the auth client if os.path.isfile(ALLVALUES_PATH): try: - # CH values exists so running with a valid config + # CH values exists so running with a valid config auth_client = AuthClient(os.getenv("ACCOUNTS_ADMIN_USERNAME", None), os.getenv("ACCOUNTS_ADMIN_PASSWORD", None)) except: log.exception("Failed to initialize auth client") diff --git a/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/services/events.py b/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/services/events.py index b20fd1a70..55909afe2 100644 --- a/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/services/events.py +++ b/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/services/events.py @@ -23,7 +23,7 @@ def event_handler(app, event_client, message): resource_path = message["resource-path"].split("/") log.info(f"{event_client} {message}") - if resource in ["CLIENT_ROLE_MAPPING","GROUP","USER","GROUP_MEMBERSHIP"]: + if resource in ["CLIENT_ROLE_MAPPING", "GROUP", "USER", "GROUP_MEMBERSHIP"]: try: init_services() user_service = get_user_service() @@ -71,7 +71,7 @@ def setup_event_service(self): from cloudharness.applications import get_current_configuration current_app = get_current_configuration() - self.test_kafka_running() # if the test fails (raises an exception) then k8s will restart the application + self.test_kafka_running() # if the test fails (raises an exception) then k8s will restart the application # init the topics self.init_topics() except ConfigurationCallException as e: diff --git a/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/services/user.py b/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/services/user.py index e2ec82192..fc49ba368 100644 --- a/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/services/user.py +++ b/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/services/user.py @@ -3,12 +3,14 @@ from cloudharness_django.models import Team, Member from cloudharness_django.services.auth import AuthorizationLevel + def get_user_by_kc_id(kc_id) -> User: try: return Member.objects.get(kc_id=kc_id).user except Member.DoesNotExist: return None + class UserService: def __init__(self, auth_service): self.auth_service = auth_service @@ -35,7 +37,7 @@ def _map_kc_user(self, user, kc_user=None, is_superuser=False, delete=False): user.first_name = kc_user.get("firstName", "") user.last_name = kc_user.get("lastName", "") user.email = kc_user.get("email", "") - + user.is_active = kc_user.get("enabled", delete) return user @@ -80,7 +82,7 @@ def sync_kc_group(self, kc_group): superusers = User.objects.filter(is_superuser=True) if superusers and len(superusers) > 0: team = Team.objects.create( - owner=superusers[0], # one of the superusers will be the default team owner + owner=superusers[0], # one of the superusers will be the default team owner kc_id=kc_group["id"], group=group) team.save() @@ -95,14 +97,14 @@ def sync_kc_groups(self, kc_groups=None): def sync_kc_user(self, kc_user, is_superuser=False, delete=False): # sync the kc user with the django user - + user, created = User.objects.get_or_create(username=kc_user["username"]) - + Member.objects.get_or_create(user=user, kc_id=kc_user["id"]) user = self._map_kc_user(user, kc_user, is_superuser, delete) user.save() return user - + def sync_kc_user_groups(self, kc_user): # Sync the user usergroups and memberships user = User.objects.get(username=kc_user["email"]) diff --git a/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/settings.py b/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/settings.py index 27c6d5c7f..b9efd5726 100644 --- a/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/settings.py +++ b/infrastructure/common-images/cloudharness-django/libraries/cloudharness-django/cloudharness_django/settings.py @@ -12,19 +12,19 @@ INSTALLED_APPS = getattr( settings, 'INSTALLED_APPS', - []) + [ 'admin_extra_buttons',] + []) + ['admin_extra_buttons', ] # add the local apps -INSTALLED_APPS += ['cloudharness_django',] +INSTALLED_APPS += ['cloudharness_django', ] # add the CloudHarness Django auto login middleware MIDDLEWARE = getattr( - settings, - 'MIDDLEWARE', - [] - ) + [ - 'cloudharness_django.middleware.BearerTokenMiddleware', - ] + settings, + 'MIDDLEWARE', + [] +) + [ + 'cloudharness_django.middleware.BearerTokenMiddleware', +] # test if the kubernetes CH all values exists, if so then set up specific k8s stuff # IMPROTANT NOTE: @@ -35,10 +35,10 @@ app_name = settings.PROJECT_NAME.lower() try: current_app = applications.get_current_configuration() - + # if secured then set USE_X_FORWARDED_HOST because we are behind the GK proxy USE_X_FORWARDED_HOST = current_app.harness.secured - + # CSRF, set CSRF_TRUSTED_ORIGINS CH_DOMAIN = conf.get_domain() CSRF_TRUSTED_ORIGINS = getattr( @@ -65,14 +65,14 @@ if current_app.harness.database.type == "sqlite3": DATABASE_ENGINE = "django.db.backends.sqlite3" - DATABASE_NAME = os.path.join(getattr(settings,"PERSISTENT_ROOT","."), f"{app_name}.sqlite3") + DATABASE_NAME = os.path.join(getattr(settings, "PERSISTENT_ROOT", "."), f"{app_name}.sqlite3") DATABSE_HOST = None DATABASE_PORT = None elif current_app.harness.database.type == "postgres": - DATABASE_ENGINE = "django.db.backends.postgresql" - DATABASE_NAME = current_app.harness.database.postgres.initialdb - DATABSE_HOST = current_app.harness.database.name - DATABASE_PORT = current_app.harness.database.postgres.ports[0].port + DATABASE_ENGINE = "django.db.backends.postgresql" + DATABASE_NAME = current_app.harness.database.postgres.initialdb + DATABSE_HOST = current_app.harness.database.name + DATABASE_PORT = current_app.harness.database.postgres.ports[0].port # Database # https://docs.djangoproject.com/en/3.2/ref/settings/#databases @@ -86,7 +86,7 @@ "PORT": DATABASE_PORT, "TEST": { "ENGINE": "django.db.backends.sqlite3", - "NAME": os.path.join(getattr(settings,"PERSISTENT_ROOT","."), "testdb.sqlite3"), + "NAME": os.path.join(getattr(settings, "PERSISTENT_ROOT", "."), "testdb.sqlite3"), }, }, } diff --git a/libraries/client/cloudharness_cli/cloudharness_cli/samples/paths/operation_async/get.py b/libraries/client/cloudharness_cli/cloudharness_cli/samples/paths/operation_async/get.py index 24318033d..a18549299 100644 --- a/libraries/client/cloudharness_cli/cloudharness_cli/samples/paths/operation_async/get.py +++ b/libraries/client/cloudharness_cli/cloudharness_cli/samples/paths/operation_async/get.py @@ -25,7 +25,7 @@ from cloudharness_cli.samples import schemas # noqa: F401 -from cloudharness_cli/samples.model.inline_response202 import InlineResponse202 +from cloudharness_cli.samples.model.inline_response202 import InlineResponse202 from . import path @@ -231,5 +231,3 @@ def get( timeout=timeout, skip_deserialization=skip_deserialization ) - - diff --git a/libraries/client/cloudharness_cli/setup.py b/libraries/client/cloudharness_cli/setup.py index 890b95c25..ba088ed6e 100644 --- a/libraries/client/cloudharness_cli/setup.py +++ b/libraries/client/cloudharness_cli/setup.py @@ -37,4 +37,4 @@ long_description="""\ CloudHarness Python API Client # noqa: E501 """ -) \ No newline at end of file +) diff --git a/libraries/cloudharness-common/cloudharness/__init__.py b/libraries/cloudharness-common/cloudharness/__init__.py index 333321f25..b09092791 100644 --- a/libraries/cloudharness-common/cloudharness/__init__.py +++ b/libraries/cloudharness-common/cloudharness/__init__.py @@ -1,37 +1,42 @@ +import json as js +from cloudharness_model.encoder import CloudHarnessJSONEncoder import logging import sys log = logging -from cloudharness_model.encoder import CloudHarnessJSONEncoder FORMAT = "%(asctime)s [%(levelname)s] %(module)s.%(funcName)s: %(message)s" logging.basicConfig(stream=sys.stdout, format=FORMAT, level=logging.INFO) + def set_debug(): logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) # TODO log will write through a rest service -import json as js json_dumps = js.dumps + def dumps(o, *args, **kwargs): try: - if "cls" not in kwargs: + if "cls" not in kwargs: return json_dumps(o, cls=CloudHarnessJSONEncoder, *args, **kwargs) return json_dumps(o, *args, **kwargs) except: logging.error(repr(o)) raise + json = js json.dumps = dumps + class NotCorrectlyInitialized(Exception): pass + def init(appname: str): """ Init cloudharness functionality for the current app @@ -52,5 +57,5 @@ def init(appname: str): except Exception as e: log.warning(f'Error enabling Sentry for {appname}', exc_info=True) -__all__ = ['log', 'init'] +__all__ = ['log', 'init'] diff --git a/libraries/cloudharness-common/cloudharness/auth/exceptions.py b/libraries/cloudharness-common/cloudharness/auth/exceptions.py index 254340b8d..3cc1e4eb1 100644 --- a/libraries/cloudharness-common/cloudharness/auth/exceptions.py +++ b/libraries/cloudharness-common/cloudharness/auth/exceptions.py @@ -1,11 +1,14 @@ from keycloak.exceptions import KeycloakAuthenticationError, KeycloakGetError + class UserNotFound(KeycloakGetError): pass + class InvalidToken(Exception): pass + class AuthSecretNotFound(Exception): def __init__(self, secret_name): - Exception.__init__(self, f"Secret {secret_name} not found.") \ No newline at end of file + Exception.__init__(self, f"Secret {secret_name} not found.") diff --git a/libraries/cloudharness-common/cloudharness/auth/keycloak.py b/libraries/cloudharness-common/cloudharness/auth/keycloak.py index c86aed3af..443ebdade 100644 --- a/libraries/cloudharness-common/cloudharness/auth/keycloak.py +++ b/libraries/cloudharness-common/cloudharness/auth/keycloak.py @@ -21,9 +21,6 @@ ALLVALUES_PATH, exc_info=True) - - - def get_api_password() -> str: name = "api_user_password" AUTH_SECRET_PATH = os.environ.get( @@ -76,16 +73,18 @@ def get_server_url(): def get_auth_realm(): return conf.get_namespace() + def get_token(username, password): conf = get_configuration("accounts") keycloak_openid = KeycloakOpenID( - server_url=get_server_url(), - realm_name=get_auth_realm(), - client_id=conf["webclient"]["id"], + server_url=get_server_url(), + realm_name=get_auth_realm(), + client_id=conf["webclient"]["id"], client_secret_key=conf["webclient"]["secret"]) return keycloak_openid.token(username, password)['access_token'] + def is_uuid(s): import uuid try: @@ -116,7 +115,7 @@ def _get_keycloak_user_id(): keycloak_user_id = "-1" # No authorization --> no user else: token = authentication_token.split(' ')[-1] - + keycloak_user_id = AuthClient.decode_token(token)['sub'] return keycloak_user_id @@ -127,10 +126,9 @@ def __init__(self, username=None, password=None): """ self.user = username or os.getenv('ACCOUNTS_ADMIN_USERNAME', None) or "admin_api" - self.passwd = password or os.getenv('ACCOUNTS_ADMIN_PASSWORD', None) or get_api_password() + self.passwd = password or os.getenv('ACCOUNTS_ADMIN_PASSWORD', None) or get_api_password() # test if we can connect to the Keycloak server dummy_client = self.get_admin_client() - def get_admin_client(self): """ @@ -143,8 +141,6 @@ def get_admin_client(self): :return: KeycloakAdmin """ - - if not getattr(self, "_admin_client", None): self._admin_client = KeycloakAdmin( server_url=get_server_url(), @@ -189,7 +185,7 @@ def decode_token(cls, token, audience="web-client"): """ try: decoded = jwt.decode(token, cls.get_public_key(), - algorithms='RS256', audience=audience) + algorithms='RS256', audience=audience) except jwt.exceptions.InvalidTokenError as e: raise InvalidToken(e) from e return decoded @@ -270,7 +266,7 @@ def create_client_role(self, client_id, role): def get_group(self, group_id, with_members=False, with_details=False) -> UserGroup: """ Return the group in the application realm - + :param group_id: the group id to get :param with_members: Default False, when set to True all members of the group are also retrieved :param with_details: Default False, when set to True all attributes of the group are also retrieved @@ -307,7 +303,7 @@ def get_groups(self, with_members=False) -> List[UserGroup]: """ admin_client = self.get_admin_client() return [ - UserGroup.from_dict(self.get_group(group['id'], with_members)) + UserGroup.from_dict(self.get_group(group['id'], with_members)) for group in admin_client.get_groups() ] @@ -394,7 +390,7 @@ def group_user_remove(self, user_id, group_id): def get_users(self, query=None, with_details=False) -> List[User]: """ Return a list of all users in the application realm - + :param query: Default None, the query filter for getting the users :param with_details: Default False, when set to True all attributes of the group are also retrieved @@ -413,7 +409,7 @@ def get_users(self, query=None, with_details=False) -> List[User]: user.update({ "userGroups": admin_client.get_user_groups(user['id'], brief_representation=not with_details), 'realmRoles': admin_client.get_realm_roles_of_user(user['id']) - }) + }) users.append(User.from_dict(user)) return users @@ -442,23 +438,22 @@ def get_user(self, user_id, with_details=False) -> User: raise UserNotFound(user_id) except InvalidToken as e: raise UserNotFound(user_id) - - + else: found_users = admin_client.get_users({"username": user_id, "exact": True}) if len(found_users) == 0: raise UserNotFound(user_id) try: - user = admin_client.get_user(found_users[0]['id']) # Load full data + user = admin_client.get_user(found_users[0]['id']) # Load full data except KeycloakGetError as e: raise UserNotFound(user_id) except InvalidToken as e: raise UserNotFound(user_id) - + user.update({ - "userGroups": admin_client.get_user_groups(user_id=user['id'], brief_representation=not with_details), - 'realmRoles': admin_client.get_realm_roles_of_user(user['id']) - }) + "userGroups": admin_client.get_user_groups(user_id=user['id'], brief_representation=not with_details), + 'realmRoles': admin_client.get_realm_roles_of_user(user['id']) + }) return User.from_dict(user) def get_current_user(self) -> User: @@ -474,7 +469,7 @@ def get_current_user(self) -> User: :return: UserRepresentation + GroupRepresentation """ return self.get_user(self._get_keycloak_user_id()) - + @with_refreshtoken def get_user_realm_roles(self, user_id) -> List[str]: """ @@ -487,7 +482,7 @@ def get_user_realm_roles(self, user_id) -> List[str]: :return: (array RoleRepresentation) """ - admin_client=self.get_admin_client() + admin_client = self.get_admin_client() return admin_client.get_realm_roles_of_user(user_id) def get_current_user_realm_roles(self) -> List[str]: @@ -510,8 +505,8 @@ def get_user_client_roles(self, user_id, client_name) -> List[str]: :param client_name: Client name :return: (array RoleRepresentation) """ - admin_client=self.get_admin_client() - client_id=admin_client.get_client_id(client_name) + admin_client = self.get_admin_client() + client_id = admin_client.get_client_id(client_name) return admin_client.get_client_roles_of_user(user_id, client_id) def get_current_user_client_roles(self, client_name) -> List[str]: @@ -521,7 +516,7 @@ def get_current_user_client_roles(self, client_name) -> List[str]: :param client_name: Client name :return: UserRepresentation + GroupRepresentation """ - cur_user_id=self._get_keycloak_user_id() + cur_user_id = self._get_keycloak_user_id() return self.get_user_client_roles(cur_user_id, client_name) def user_has_client_role(self, user_id, client_name, role) -> bool: @@ -532,7 +527,7 @@ def user_has_client_role(self, user_id, client_name, role) -> bool: :param client_name: Name of the client :param role: Name of the role """ - roles=[user_client_role for user_client_role in self.get_user_client_roles( + roles = [user_client_role for user_client_role in self.get_user_client_roles( user_id, client_name) if user_client_role['name'] == role] return roles != [] @@ -543,7 +538,7 @@ def user_has_realm_role(self, user_id, role) -> bool: :param user_id: User id :param role: Name of the role """ - roles=[user_realm_role for user_realm_role in self.get_user_realm_roles( + roles = [user_realm_role for user_realm_role in self.get_user_realm_roles( user_id) if user_realm_role['name'] == role] return roles != [] @@ -577,8 +572,8 @@ def get_client_role_members(self, client_name, role) -> List[User]: :param client_name: Client name :param role: Role name """ - admin_client=self.get_admin_client() - client_id=admin_client.get_client_id(client_name) + admin_client = self.get_admin_client() + client_id = admin_client.get_client_id(client_name) return [User.from_dict(u) for u in admin_client.get_client_role_members(client_id, role)] @with_refreshtoken @@ -591,10 +586,10 @@ def user_add_update_attribute(self, user_id, attribute_name, attribute_value): param attribute_value: value of the attribute """ - admin_client=self.get_admin_client() - user=self.get_user(user_id) - attributes=user.get('attributes', {}) or {} - attributes[attribute_name]=attribute_value + admin_client = self.get_admin_client() + user = self.get_user(user_id) + attributes = user.get('attributes', {}) or {} + attributes[attribute_name] = attribute_value admin_client.update_user( user_id, { @@ -610,9 +605,9 @@ def user_delete_attribute(self, user_id, attribute_name): param attribute_name: name of the attribute to delete :return: boolean True on success, False is attribute not in user attributes """ - admin_client=self.get_admin_client() - user=self.get_user(user_id) - attributes=user.get('attributes', None) + admin_client = self.get_admin_client() + user = self.get_user(user_id) + attributes = user.get('attributes', None) if attributes and attribute_name in attributes: del attributes[attribute_name] admin_client.update_user( diff --git a/libraries/cloudharness-common/cloudharness/errors.py b/libraries/cloudharness-common/cloudharness/errors.py index b53436922..1304cdaa9 100644 --- a/libraries/cloudharness-common/cloudharness/errors.py +++ b/libraries/cloudharness-common/cloudharness/errors.py @@ -3,23 +3,28 @@ def __init__(self, topic_id): self.topic_id = topic_id Exception.__init__(self, f'Events: unable to produce message to topic -> {topic_id}') + class EventTopicCreationException(Exception): def __init__(self, topic_id): self.topic_id = topic_id Exception.__init__(self, f'Events: unable to create topic -> {topic_id}') + class EventTopicConsumeException(Exception): def __init__(self, topic_id): self.topic_id = topic_id Exception.__init__(self, f'Events: unable to consume messages from topic -> {topic_id}') + class EventTopicDeleteException(Exception): def __init__(self, topic_id): self.topic_id = topic_id Exception.__init__(self, f'Events: unable to delete topic -> {topic_id}') + class EventGeneralException(Exception): pass + class MongoDBConfError(Exception): - pass \ No newline at end of file + pass diff --git a/libraries/cloudharness-common/cloudharness/events/client.py b/libraries/cloudharness-common/cloudharness/events/client.py index 0dad22546..f7c00391f 100644 --- a/libraries/cloudharness-common/cloudharness/events/client.py +++ b/libraries/cloudharness-common/cloudharness/events/client.py @@ -28,6 +28,7 @@ AUTH_CLIENT = None CURRENT_APP_NAME = config.get_current_app_name() + def get_authclient(): global AUTH_CLIENT if not AUTH_CLIENT: @@ -55,13 +56,12 @@ def _get_consumer(self, group_id='default') -> KafkaConsumer: group_id=group_id, value_deserializer=lambda x: json.loads(x.decode('utf-8'))) - def create_topic(self): """ Connects to cloudharness Events and creates a new topic Return: True if topic was created correctly, False otherwise. """ - ## Connect to kafka + # Connect to kafka admin_client = KafkaAdminClient(bootstrap_servers=self._get_bootstrap_servers(), client_id=self._get_client_id()) # ## Create topic @@ -184,7 +184,6 @@ def send_event(message_type, operation, obj, uid="id", func_name=None, func_args except Exception as e: log.error('send_event error.', exc_info=True) - def consume_all_cdc(self, group_id='default') -> Generator[CDCEvent, None, None]: """ Return a list of object modification messages published in the topic @@ -222,10 +221,10 @@ def consume_all(self, group_id='default') -> list: def delete_topic(self) -> bool: log.debug("Deleting topic " + self.topic_id) - ## Connect to kafka + # Connect to kafka admin_client = KafkaAdminClient(bootstrap_servers=self._get_bootstrap_servers(), client_id=self._get_client_id()) - ## Delete topic + # Delete topic try: admin_client.delete_topics([self.topic_id]) return True @@ -254,8 +253,8 @@ def _consume_task(self, app=None, group_id=None, handler=None): log.error(f"Error during execution of the consumer Topic {self.topic_id} --> {e}", exc_info=True) self.consumer.close() except Exception as e: - log.error(f"Error during execution of the consumer Topic {self.topic_id} --> {e}", exc_info=True) - time.sleep(15) + log.error(f"Error during execution of the consumer Topic {self.topic_id} --> {e}", exc_info=True) + time.sleep(15) def async_consume(self, app=None, handler=None, group_id='default'): log.debug('creating thread') @@ -263,7 +262,7 @@ def async_consume(self, app=None, handler=None, group_id='default'): log.debug('get current object from app') app = app._get_current_object() self._consumer_thread = threading.Thread( - target=self._consume_task, + target=self._consume_task, kwargs={'app': app, 'group_id': group_id, 'handler': handler}) @@ -271,6 +270,7 @@ def async_consume(self, app=None, handler=None, group_id='default'): self._consumer_thread.start() log.debug('thread started') + if __name__ == "__main__": # creat the required os env variables os.environ['CLOUDHARNESS_EVENTS_CLIENT_ID'] = env.get_cloudharness_events_client_id() diff --git a/libraries/cloudharness-common/cloudharness/infrastructure/k8s.py b/libraries/cloudharness-common/cloudharness/infrastructure/k8s.py index 530921421..9af64a7a7 100644 --- a/libraries/cloudharness-common/cloudharness/infrastructure/k8s.py +++ b/libraries/cloudharness-common/cloudharness/infrastructure/k8s.py @@ -5,24 +5,22 @@ """ import kubernetes -import yaml -import os -from pathlib import Path - from cloudharness import log # TODO handle group -version = 'v1alpha1' +from cloudharness.utils.config import CloudharnessConfig as conf + # determine the namespace of the current app and run the workflow in that namespace -from cloudharness.utils.config import CloudharnessConfig as conf namespace = conf.get_namespace() +version = 'v1alpha1' # --- Api functions --- ` + def get_api_client(): configuration = get_configuration() api_instance = kubernetes.client.CoreV1Api(kubernetes.client.ApiClient(get_configuration())) @@ -51,7 +49,7 @@ def create_namespace(): try: api_response = api_instance.create_namespace(body) - except Exception as e: + except Exception as e: raise Exception(f"Error creating namespace: {namespace}") from e diff --git a/libraries/cloudharness-common/cloudharness/middleware/__init__.py b/libraries/cloudharness-common/cloudharness/middleware/__init__.py index 780ebd49d..c3e0531ad 100644 --- a/libraries/cloudharness-common/cloudharness/middleware/__init__.py +++ b/libraries/cloudharness-common/cloudharness/middleware/__init__.py @@ -2,9 +2,11 @@ _authentication_token = ContextVar("ch_authentication_token", default=None) + def set_authentication_token(authentication_token): if authentication_token: _authentication_token.set(authentication_token) + def get_authentication_token(): return _authentication_token.get() diff --git a/libraries/cloudharness-common/cloudharness/models.py b/libraries/cloudharness-common/cloudharness/models.py index 83e950b37..a9719b604 100644 --- a/libraries/cloudharness-common/cloudharness/models.py +++ b/libraries/cloudharness-common/cloudharness/models.py @@ -1 +1 @@ -from cloudharness_model import * \ No newline at end of file +from cloudharness_model import * diff --git a/libraries/cloudharness-common/cloudharness/sentry/__init__.py b/libraries/cloudharness-common/cloudharness/sentry/__init__.py index fd1b3e5a4..ccc093165 100644 --- a/libraries/cloudharness-common/cloudharness/sentry/__init__.py +++ b/libraries/cloudharness-common/cloudharness/sentry/__init__.py @@ -6,6 +6,7 @@ sentry_environment = os.environ.get("DOMAIN", "Production") + def get_dsn(appname): """ Helper function for getting the Sentry DSN of the project of the application @@ -21,15 +22,16 @@ def get_dsn(appname): Usage examples: from cloudharness.sentry import get_dsn dsn = get_dsn('notifications') - """ + """ url = get_common_service_cluster_address() + f'/api/sentry/getdsn/{appname}' response = requests.get(url, verify=False).json() dsn = response['dsn'] - if dsn and len(dsn)>0: + if dsn and len(dsn) > 0: return dsn else: return None + def init(appname=None, traces_sample_rate=0, integrations=None, **kwargs): """ Init cloudharness Sentry functionality for the current app @@ -65,4 +67,5 @@ def init(appname=None, traces_sample_rate=0, integrations=None, **kwargs): **kwargs ) + __all__ = ['get_dsn', 'init'] diff --git a/libraries/cloudharness-common/cloudharness/service/pvc.py b/libraries/cloudharness-common/cloudharness/service/pvc.py index 18c4f725c..1bca54aff 100644 --- a/libraries/cloudharness-common/cloudharness/service/pvc.py +++ b/libraries/cloudharness-common/cloudharness/service/pvc.py @@ -64,6 +64,7 @@ def _get_default_storage_class() -> str: break return selected_sc + def create_persistent_volume_claim(name, size, logger, storage_class=None, useNFS=False, template=None, access_mode=None, **kwargs): """ Create a Persistent Volume Claim in the Kubernetes cluster. @@ -87,7 +88,7 @@ def create_persistent_volume_claim(name, size, logger, storage_class=None, useNF if not size: raise Exception(f"Size must be set. Got {size!r}.") - + if not storage_class: if not useNFS: storage_class = _get_default_storage_class() @@ -102,18 +103,18 @@ def create_persistent_volume_claim(name, size, logger, storage_class=None, useNF path = os.path.join(os.path.dirname(__file__), 'templates', 'pvc.yaml') template = open(path, 'rt').read() text = template.format( - name=name, - size=size, - storageClass=storage_class) + name=name, + size=size, + storageClass=storage_class) data = dict_merge(yaml.safe_load(text), kwargs) if access_mode: data["spec"]["accessModes"] = [access_mode] obj = _get_corev1_api().create_namespaced_persistent_volume_claim( - namespace=conf.get_configuration()['namespace'], - body=data, - ) + namespace=conf.get_configuration()['namespace'], + body=data, + ) logger.info(f"PVC child is created: %s", obj) @@ -123,7 +124,7 @@ def persistent_volume_claim_exists(name): Args: name (string): the name of the PVC - + Returns: boolean: True if the PVC exists, False is the PVC doesn't exist """ diff --git a/libraries/cloudharness-common/cloudharness/utils/__init__.py b/libraries/cloudharness-common/cloudharness/utils/__init__.py index 26e0b0f7a..cca07dacb 100644 --- a/libraries/cloudharness-common/cloudharness/utils/__init__.py +++ b/libraries/cloudharness-common/cloudharness/utils/__init__.py @@ -28,8 +28,8 @@ def dict_merge(dct, merge_dct, add_keys=True): } for k, v in merge_dct.items(): - if (k in dct and isinstance(dct[k], dict) - and isinstance(merge_dct[k], collections.abc.Mapping)): + if (k in dct and isinstance(dct[k], dict) and + isinstance(merge_dct[k], collections.abc.Mapping)): dct[k] = dict_merge(dct[k], merge_dct[k], add_keys=add_keys) else: dct[k] = merge_dct[k] diff --git a/libraries/cloudharness-common/cloudharness/utils/config.py b/libraries/cloudharness-common/cloudharness/utils/config.py index 13108d7d3..f7024e9d6 100644 --- a/libraries/cloudharness-common/cloudharness/utils/config.py +++ b/libraries/cloudharness-common/cloudharness/utils/config.py @@ -24,15 +24,16 @@ def __getitem__(self, key_or_path: str): obj = obj[k] return obj + class CloudharnessConfig: """ Helper class for the Cloud Harness configuration The CH configuration will be loaded from the values.yaml generated by helm via the harness-deployment script - + """ - allvalues={} + allvalues = {} @classmethod def _get_all_values(cls) -> dict: @@ -63,7 +64,6 @@ def get_current_app_name(cls): def get_domain(cls): return cls.get_configuration()['domain'] - @classmethod def get_registry_name(cls): return cls.get_configuration()['registry']['name'] @@ -122,7 +122,7 @@ def get_application_by_filter(cls, **filter): tmp_obj = tmp_obj[key] if (tmp_obj == filter_value) or \ (filter_value == False and tmp_obj is None) or \ - (filter_value == True and tmp_obj is not None): + (filter_value == True and tmp_obj is not None): apps.append(app) except KeyError: pass diff --git a/libraries/cloudharness-common/cloudharness/utils/env.py b/libraries/cloudharness-common/cloudharness/utils/env.py index 05588aadc..cf3d47b8d 100644 --- a/libraries/cloudharness-common/cloudharness/utils/env.py +++ b/libraries/cloudharness-common/cloudharness/utils/env.py @@ -25,21 +25,22 @@ def set_default_environment(): values = conf.get_configuration() if values and 'env' in values: - os.environ.update({v['name']: str(v["value"]) for v in values['env'] if v['name'] not in os.environ}) - - + os.environ.update({v['name']: str(v["value"]) for v in values['env'] if v['name'] not in os.environ}) set_default_environment() + def get_namespace(): try: - namespace=conf.get_configuration()['namespace'] + namespace = conf.get_configuration()['namespace'] except: - namespace='' + namespace = '' return namespace -namespace=get_namespace() + +namespace = get_namespace() + class VariableNotFound(Exception): def __init__(self, variable_name): @@ -71,6 +72,7 @@ def get_image_registry(): return DEFAULT_IMAGE_REGISTRY + def name_to_variable(application_name): return application_name.upper().replace('-', '_') @@ -92,7 +94,7 @@ def get_service_cluster_address(cloudharness_app_name): def cluster_service_address(service_name): - return f'{service_name}.{namespace}.svc.cluster.local' + return f'{service_name}.{namespace}.svc.cluster.local' def use_public_services(): @@ -101,6 +103,7 @@ def use_public_services(): except VariableNotFound: return False + def get_sub_variable(*vars): return get_variable(name_to_variable('_'.join(vars))) @@ -116,19 +119,24 @@ def get_public_domain(): def get_cloudharness_workflows_service_url(): return get_service_public_address('workflows') + def get_cloudharness_sentry_service_url(): return get_configuration('sentry').get_public_address() + def get_sentry_service_cluster_address(): return get_configuration('sentry').get_service_address() + def get_cloudharness_common_service_url(): return get_configuration('common').get_public_address() + def get_common_service_cluster_address(): common_app = get_configuration('common') return common_app.get_service_address() + def get_auth_service_cluster_address(): return get_configuration('accounts').get_service_address() diff --git a/libraries/cloudharness-common/cloudharness/utils/secrets.py b/libraries/cloudharness-common/cloudharness/utils/secrets.py index 7e39013a9..dc9dc1422 100644 --- a/libraries/cloudharness-common/cloudharness/utils/secrets.py +++ b/libraries/cloudharness-common/cloudharness/utils/secrets.py @@ -13,7 +13,7 @@ def get_secret(name: str) -> str: Helper class for the CloudHarness application secrets The application secret will be read from the secret file - + Args: name (str): name of the secret key (str): name of the data key in the secret diff --git a/libraries/cloudharness-common/cloudharness/utils/settings.py b/libraries/cloudharness-common/cloudharness/utils/settings.py index 8b1378917..e69de29bb 100644 --- a/libraries/cloudharness-common/cloudharness/utils/settings.py +++ b/libraries/cloudharness-common/cloudharness/utils/settings.py @@ -1 +0,0 @@ - diff --git a/libraries/cloudharness-common/cloudharness/workflows/argo.py b/libraries/cloudharness-common/cloudharness/workflows/argo.py index 46595beb3..4f1c4cdcd 100644 --- a/libraries/cloudharness-common/cloudharness/workflows/argo.py +++ b/libraries/cloudharness-common/cloudharness/workflows/argo.py @@ -133,11 +133,11 @@ def get_workflows(status=None, limit=10, continue_token=None, timeout_seconds=3, service = WorkflowServiceApi(api_client=get_api_client()) try: - api_response = service.list_workflows(namespace, list_options_limit=limit, list_options_continue=continue_token, - list_options_label_selector=f"workflows.argoproj.io/phase={status}" if status else None, - _request_timeout=timeout_seconds, - list_options_field_selector=field_selector, fields=fields, **kwargs) - + api_response = service.list_workflows(namespace, list_options_limit=limit, list_options_continue=continue_token, + list_options_label_selector=f"workflows.argoproj.io/phase={status}" if status else None, + _request_timeout=timeout_seconds, + list_options_field_selector=field_selector, fields=fields, **kwargs) + except ValueError: # Exception is raised when no results are found return SearchResult(V1alpha1WorkflowList(items=[], metadata={})) @@ -203,4 +203,4 @@ def get_workflow_logs_list(workflow_name): conf.get_configuration()['test'] = True res = get_workflows() pprint(res) - # pprint(get_workflow('hello-world-sfzd4')) \ No newline at end of file + # pprint(get_workflow('hello-world-sfzd4')) diff --git a/libraries/cloudharness-common/cloudharness/workflows/tasks.py b/libraries/cloudharness-common/cloudharness/workflows/tasks.py index c88ccc215..550ef8489 100644 --- a/libraries/cloudharness-common/cloudharness/workflows/tasks.py +++ b/libraries/cloudharness-common/cloudharness/workflows/tasks.py @@ -11,7 +11,7 @@ class Task(argo.ArgoObject): Abstract interface for a task. """ - def __init__(self, name, resources={}, volume_mounts=[], **env_args): + def __init__(self, name, resources={}, volume_mounts=[], **env_args): self.name = name.replace(' ', '-').lower() self.resources = resources self.__envs = get_cloudharness_variables() @@ -33,7 +33,7 @@ def affinity_spec(self): return affinity_spec(([PodExecutionContext('usesvolume', self.external_volumes[0], True)] if self.external_volumes else []) + [ PodExecutionContext(f'usesvolume-{v}', 'true', True) for v in self.external_volumes - ] ) + ]) @property def image_name(self): diff --git a/libraries/cloudharness-common/cloudharness/workflows/utils.py b/libraries/cloudharness-common/cloudharness/workflows/utils.py index 2ad94fd2b..f65d65917 100644 --- a/libraries/cloudharness-common/cloudharness/workflows/utils.py +++ b/libraries/cloudharness-common/cloudharness/workflows/utils.py @@ -8,6 +8,7 @@ SHARED_DIRECTORY_VARIABLE_NAME = "shared_directory" + class PodExecutionContext: """ Key-value pair representing the execution context with other pods. @@ -19,15 +20,18 @@ def __init__(self, key, value, required=False): self.value = str(value) self.required = required + def get_workflow_name(): """Get the workflow name from inside a workflow""" name = get_variable(WORKFLOW_NAME_VARIABLE_NAME) remove = name.split("-")[-1] return name[0:-len(remove) - 1] + def volume_requires_affinity(v): return ':' in v and 'rwx' not in v[-4:] + def get_shared_directory(): return os.getenv(SHARED_DIRECTORY_VARIABLE_NAME) diff --git a/libraries/cloudharness-common/tests/test_applications.py b/libraries/cloudharness-common/tests/test_applications.py index 73da5bfe9..b1df3d198 100644 --- a/libraries/cloudharness-common/tests/test_applications.py +++ b/libraries/cloudharness-common/tests/test_applications.py @@ -1,15 +1,13 @@ +from cloudharness.utils.config import CloudharnessConfig, ConfigObject +from cloudharness.applications import ApplicationConfiguration, get_configuration from .test_env import set_default_environment set_default_environment() -from cloudharness.applications import ApplicationConfiguration, get_configuration -from cloudharness.utils.config import CloudharnessConfig, ConfigObject - - conf_1 = { 'name': 'app1', - + 'harness': { 'name': 'app1', 'subdomain': 'myapp', @@ -72,6 +70,7 @@ def test_application_conf(): assert uut.conf.admin.role == 'administrator' assert uut.conf["admin.role"] == 'administrator' + def test_get_configuration(): CloudharnessConfig.apps = { 'a': conf_1, @@ -89,8 +88,7 @@ def test_get_configuration(): assert uut["freefield"]["a"] == 1 assert uut["freefield"].a == 1 assert uut["freefield.a"] == 1 - - + assert uut.freefield.a == 1 uut = get_configuration('app2') @@ -100,9 +98,6 @@ def test_get_configuration(): assert not uut.is_auto_deployment() assert uut.is_sentry_enabled() - - - # TODO subapp support # uut = get_configuration('app2sub') diff --git a/libraries/cloudharness-common/tests/test_env.py b/libraries/cloudharness-common/tests/test_env.py index b4f2638c4..6956b6663 100644 --- a/libraries/cloudharness-common/tests/test_env.py +++ b/libraries/cloudharness-common/tests/test_env.py @@ -1,3 +1,5 @@ +from cloudharness.utils.config import CloudharnessConfig as conf +from cloudharness.utils.env import set_default_environment import pytest import os import yaml @@ -5,11 +7,9 @@ HERE = os.path.dirname(os.path.realpath(__file__)) os.environ["CH_VALUES_PATH"] = os.path.join(HERE, "values.yaml") -from cloudharness.utils.env import set_default_environment -from cloudharness.utils.config import CloudharnessConfig as conf def set_test_environment(): - + values_file = os.environ["CH_VALUES_PATH"] if not os.path.exists(values_file): @@ -21,5 +21,3 @@ def set_test_environment(): pprint(values) conf.get_configuration().update(values) set_default_environment() - - diff --git a/libraries/cloudharness-common/tests/test_infrastructure.py b/libraries/cloudharness-common/tests/test_infrastructure.py index c9395467b..ab060d8e8 100644 --- a/libraries/cloudharness-common/tests/test_infrastructure.py +++ b/libraries/cloudharness-common/tests/test_infrastructure.py @@ -1,8 +1,8 @@ +from cloudharness.infrastructure import k8s from .test_env import set_test_environment set_test_environment() -from cloudharness.infrastructure import k8s kubectl_enabled = False diff --git a/libraries/cloudharness-common/tests/test_middleware.py b/libraries/cloudharness-common/tests/test_middleware.py index 053a13378..bd438fae5 100644 --- a/libraries/cloudharness-common/tests/test_middleware.py +++ b/libraries/cloudharness-common/tests/test_middleware.py @@ -11,6 +11,7 @@ def new_init(self): pass + def new_decode_token(token): # if everything went fine then the token contains the value of sub # let's return it @@ -18,10 +19,12 @@ def new_decode_token(token): 'sub': token } + def test_setting_and_getting_auth_token(): set_authentication_token(TEST_AUTHENTICATION_TOKEN) assert get_authentication_token() == TEST_AUTHENTICATION_TOKEN + def test_decoding(): mocker = AuthClient mocker.decode_token = new_decode_token diff --git a/libraries/cloudharness-common/tests/test_quota.py b/libraries/cloudharness-common/tests/test_quota.py index 707ea8a6e..4c886bd76 100644 --- a/libraries/cloudharness-common/tests/test_quota.py +++ b/libraries/cloudharness-common/tests/test_quota.py @@ -1,28 +1,31 @@ +from cloudharness.auth.quota import get_user_quotas +from cloudharness.applications import get_configuration +from cloudharness import set_debug from .test_env import set_test_environment set_test_environment() -from cloudharness import set_debug -from cloudharness.applications import get_configuration -from cloudharness.auth.quota import get_user_quotas set_debug() jh_config = get_configuration("jupyterhub") assert jh_config is not None + def test_get_quotas(mocker): def mock_get_admin_client(self): return None + def mock_get_current_user(self): - return {"id":"123"} + return {"id": "123"} + def mock_get_user(self, user_id, with_details): return { "attributes": { "quota-ws-guaranteemem": [0.5] }, "userGroups": [ - {"path": "/Base", "attributes": {'quota-ws-maxmem': [2.5], 'quota-ws-maxcpu': [1], 'quota-ws-open': [3], "quota-ws-guaranteemem": [0.1]} }, + {"path": "/Base", "attributes": {'quota-ws-maxmem': [2.5], 'quota-ws-maxcpu': [1], 'quota-ws-open': [3], "quota-ws-guaranteemem": [0.1]}}, {"path": "/Base/Base 1/Base 1 1", "attributes": {'quota-ws-maxcpu': [2], 'quota-ws-open': [10]}}, {"path": "/Base/Base 2", "attributes": {'quota-ws-maxmem': [8], 'quota-ws-maxcpu': [0.25], 'quota-ws-guaranteecpu': [0.25]}}, {"path": "/Low CU", "attributes": {'quota-ws-maxmem': [3], 'quota-ws-maxcpu': [2.5], 'quota-ws-open': [1]}} @@ -32,7 +35,7 @@ def mock_get_user(self, user_id, with_details): mocker.patch('cloudharness.auth.keycloak.AuthClient.get_current_user', mock_get_current_user) mocker.patch('cloudharness.auth.keycloak.AuthClient.get_user', mock_get_user) user_quotas_jh = get_user_quotas(jh_config, user_id=None) - + assert user_quotas_jh.get("quota-ws-maxmem") == 8.0 assert user_quotas_jh.get("quota-ws-maxcpu") == 2.5 assert user_quotas_jh.get("quota-ws-open") == 10.0 diff --git a/libraries/cloudharness-common/tests/test_workflow.py b/libraries/cloudharness-common/tests/test_workflow.py index 18a2c6d15..c66dbbd03 100644 --- a/libraries/cloudharness-common/tests/test_workflow.py +++ b/libraries/cloudharness-common/tests/test_workflow.py @@ -1,4 +1,8 @@ """Notice, this test needs a fully operating kubernetes with argo environment in the container running the test""" +from cloudharness.utils.config import CloudharnessConfig +from cloudharness.workflows import argo +from cloudharness import set_debug +from cloudharness.workflows import operations, tasks, utils import requests import yaml @@ -7,10 +11,6 @@ set_test_environment() -from cloudharness.workflows import operations, tasks, utils -from cloudharness import set_debug -from cloudharness.workflows import argo -from cloudharness.utils.config import CloudharnessConfig set_debug() @@ -19,17 +19,20 @@ assert 'registry' in CloudharnessConfig.get_configuration() + def check_wf(wf): - + assert wf["kind"] == "Workflow" assert "spec" in wf + def test_volume_affinity_check(): assert not utils.volume_requires_affinity("a") assert utils.volume_requires_affinity("a:b") assert utils.volume_requires_affinity("a:b:ro") assert not utils.volume_requires_affinity("a:b:rwx") + def test_sync_workflow(): def f(): import time @@ -41,8 +44,7 @@ def f(): op = operations.DistributedSyncOperation('test-sync-op-', task) # print('\n', yaml.dump(op.to_workflow())) wf = op.to_workflow() - - + if execute: print(op.execute()) @@ -120,7 +122,7 @@ def test_single_task_shared(): accounts_offset = 1 if is_accounts_present() else 0 assert len(op.volumes) == 1 assert len(wf['spec']['volumes']) == 2 + accounts_offset - assert wf['spec']['volumes'][1+accounts_offset]['persistentVolumeClaim']['claimName'] == 'myclaim' + assert wf['spec']['volumes'][1 + accounts_offset]['persistentVolumeClaim']['claimName'] == 'myclaim' if accounts_offset == 1: assert wf['spec']['volumes'][1]['secret']['secretName'] == 'accounts' assert len(wf['spec']['templates'][0]['container']['volumeMounts']) == 2 + accounts_offset @@ -135,19 +137,20 @@ def test_single_task_shared(): if execute: print(op.execute()) + def test_pipeline_shared(): shared_directory = 'myclaim:/mnt/shared' task_write = operations.CustomTask('download-file', 'workflows-extract-download', url='https://raw.githubusercontent.com/openworm/org.geppetto/master/README.md') task_script = tasks.BashTask('print-file', source="ls -la") op = operations.PipelineOperation('test-custom-connected-op-', [task_write, task_script], - shared_directory=shared_directory, shared_volume_size=100) + shared_directory=shared_directory, shared_volume_size=100) wf = op.to_workflow() accounts_offset = 1 if is_accounts_present() else 0 assert len(op.volumes) == 1 assert len(wf['spec']['volumes']) == 2 + accounts_offset - assert wf['spec']['volumes'][1+accounts_offset]['persistentVolumeClaim']['claimName'] == 'myclaim' + assert wf['spec']['volumes'][1 + accounts_offset]['persistentVolumeClaim']['claimName'] == 'myclaim' if accounts_offset == 1: assert wf['spec']['volumes'][1]['secret']['secretName'] == 'accounts' assert len(wf['spec']['templates'][1]['container']['volumeMounts']) == 2 + accounts_offset @@ -160,7 +163,8 @@ def test_pipeline_shared(): assert affinity_glob['values'][0] == 'myclaim' if execute: - print(op.execute()) + print(op.execute()) + def test_single_task_shared_rwx(): shared_directory = 'myclaim:/mnt/shared:rwx' @@ -174,16 +178,14 @@ def test_single_task_shared_rwx(): accounts_offset = 1 if is_accounts_present() else 0 assert len(op.volumes) == 1 assert len(wf['spec']['volumes']) == 2 + accounts_offset - assert wf['spec']['volumes'][1+accounts_offset]['persistentVolumeClaim']['claimName'] == 'myclaim' + assert wf['spec']['volumes'][1 + accounts_offset]['persistentVolumeClaim']['claimName'] == 'myclaim' if accounts_offset == 1: assert wf['spec']['volumes'][1]['secret']['secretName'] == 'accounts' assert len(wf['spec']['templates'][0]['container']['volumeMounts']) == 2 + accounts_offset - - - assert not 'affinity' in wf['spec'], "Pod affinity should not be added for rwx volumes" + def test_single_task_volume_notshared(): task_write = operations.CustomTask('download-file', 'workflows-extract-download', volume_mounts=["a:b"], @@ -194,12 +196,11 @@ def test_single_task_volume_notshared(): accounts_offset = 1 if is_accounts_present() else 0 assert len(op.volumes) == 0 assert len(wf['spec']['volumes']) == 2 + accounts_offset - assert wf['spec']['volumes'][1+accounts_offset]['persistentVolumeClaim']['claimName'] == 'a' + assert wf['spec']['volumes'][1 + accounts_offset]['persistentVolumeClaim']['claimName'] == 'a' if accounts_offset == 1: assert wf['spec']['volumes'][1]['secret']['secretName'] == 'accounts' assert len(wf['spec']['templates'][0]['container']['volumeMounts']) == 2 + accounts_offset - assert 'affinity' not in wf['spec'] affinity_tpl = \ @@ -211,6 +212,7 @@ def test_single_task_volume_notshared(): if execute: print(op.execute()) + def test_single_task_volumes_notshared(): shared_directory = 'myclaim:/mnt/shared' task_write = operations.CustomTask('download-file', 'workflows-extract-download', volume_mounts=["a:b"], @@ -221,7 +223,7 @@ def test_single_task_volumes_notshared(): accounts_offset = 1 if is_accounts_present() else 0 assert len(op.volumes) == 1 assert len(wf['spec']['volumes']) == 3 + accounts_offset - assert wf['spec']['volumes'][1+accounts_offset]['persistentVolumeClaim']['claimName'] == 'myclaim' + assert wf['spec']['volumes'][1 + accounts_offset]['persistentVolumeClaim']['claimName'] == 'myclaim' if accounts_offset == 1: assert wf['spec']['volumes'][1]['secret']['secretName'] == 'accounts' assert len(wf['spec']['templates'][0]['container']['volumeMounts']) == 3 + accounts_offset @@ -241,6 +243,7 @@ def test_single_task_volumes_notshared(): if execute: print(op.execute()) + def test_single_task_shared_multiple(): shared_directory = ['myclaim:/mnt/shared', 'myclaim2:/mnt/shared2:ro'] task_write = operations.CustomTask('download-file', 'workflows-extract-download', @@ -253,16 +256,16 @@ def test_single_task_shared_multiple(): assert len(op.volumes) == 2 assert len(wf['spec']['volumes']) == 3 + accounts_offset - assert wf['spec']['volumes'][1+accounts_offset]['persistentVolumeClaim']['claimName'] == 'myclaim' + assert wf['spec']['volumes'][1 + accounts_offset]['persistentVolumeClaim']['claimName'] == 'myclaim' assert len(wf['spec']['templates'][0]['container']['volumeMounts']) == 3 + accounts_offset - assert wf['spec']['templates'][0]['container']['volumeMounts'][2+accounts_offset]['readonly'] + assert wf['spec']['templates'][0]['container']['volumeMounts'][2 + accounts_offset]['readonly'] assert wf['spec']['templates'][0]['metadata']['labels']['usesvolume'] assert 'affinity' in wf['spec'] assert len(wf['spec']['affinity']['podAffinity'][ - 'requiredDuringSchedulingIgnoredDuringExecution']) == 2, "A pod affinity for each volume is expected" + 'requiredDuringSchedulingIgnoredDuringExecution']) == 2, "A pod affinity for each volume is expected" affinity_expr = \ wf['spec']['affinity']['podAffinity']['requiredDuringSchedulingIgnoredDuringExecution'][0]['labelSelector'][ 'matchExpressions'][0] @@ -272,7 +275,6 @@ def test_single_task_shared_multiple(): print(op.execute()) - def test_single_task_shared_script(): shared_directory = 'myclaim:/mnt/shared' task_write = tasks.BashTask('download-file', source="ls -la") @@ -283,12 +285,9 @@ def test_single_task_shared_script(): accounts_offset = 1 if is_accounts_present() else 0 assert len(op.volumes) == 1 - assert len(wf['spec']['volumes']) == 2+accounts_offset - assert wf['spec']['volumes'][1+accounts_offset]['persistentVolumeClaim']['claimName'] == 'myclaim' - assert len(wf['spec']['templates'][0]['script']['volumeMounts']) == 2+accounts_offset - - - + assert len(wf['spec']['volumes']) == 2 + accounts_offset + assert wf['spec']['volumes'][1 + accounts_offset]['persistentVolumeClaim']['claimName'] == 'myclaim' + assert len(wf['spec']['templates'][0]['script']['volumeMounts']) == 2 + accounts_offset def test_result_task_workflow(): @@ -385,7 +384,7 @@ def f(): operations.PodExecutionContext('a', 'b'), operations.PodExecutionContext('c', 'd', required=True), operations.PodExecutionContext('e', 'f') - )) + )) workflow = op.to_workflow() assert 'affinity' in workflow['spec'] preferred = workflow['spec']['affinity']['podAffinity']['preferredDuringSchedulingIgnoredDuringExecution'] @@ -417,7 +416,6 @@ def f(): def test_gpu_workflow(): - from cloudharness.workflows import operations, tasks my_task = tasks.CustomTask('my-gpu', 'myapp-mytask', resources={"limits": {"nvidia.com/gpu": 1}}) @@ -426,4 +424,4 @@ def test_gpu_workflow(): if verbose: print('\n', yaml.dump(wf)) - assert "nvidia.com/gpu" in wf['spec']['templates'][1]["container"]["resources"]["limits"] \ No newline at end of file + assert "nvidia.com/gpu" in wf['spec']['templates'][1]["container"]["resources"]["limits"] diff --git a/libraries/cloudharness-utils/cloudharness_utils/constants.py b/libraries/cloudharness-utils/cloudharness_utils/constants.py index 53282691f..ced46d232 100644 --- a/libraries/cloudharness-utils/cloudharness_utils/constants.py +++ b/libraries/cloudharness-utils/cloudharness_utils/constants.py @@ -44,4 +44,4 @@ E2E_TESTS_DIRNAME = 'e2e' API_TESTS_DIRNAME = 'api' -E2E_TESTS_PROJECT_PATH = "test/test-e2e" \ No newline at end of file +E2E_TESTS_PROJECT_PATH = "test/test-e2e" diff --git a/libraries/cloudharness-utils/cloudharness_utils/testing/api.py b/libraries/cloudharness-utils/cloudharness_utils/testing/api.py index d71719139..57032519e 100644 --- a/libraries/cloudharness-utils/cloudharness_utils/testing/api.py +++ b/libraries/cloudharness-utils/cloudharness_utils/testing/api.py @@ -12,7 +12,7 @@ def get_api_filename(app_dir): def get_schemathesis_command(api_filename, app_config: ApplicationHarnessConfig, app_domain: str): - return ["st", "--pre-run", "cloudharness_test.apitest_init", "run", api_filename, *get_schemathesis_params(app_config, app_domain)] + return ["st", "--pre-run", "cloudharness_test.apitest_init", "run", api_filename, *get_schemathesis_params(app_config, app_domain)] def get_schemathesis_params(app_config: ApplicationHarnessConfig, app_domain: str): diff --git a/libraries/cloudharness-utils/cloudharness_utils/testing/util.py b/libraries/cloudharness-utils/cloudharness_utils/testing/util.py index da758d871..528f611d6 100644 --- a/libraries/cloudharness-utils/cloudharness_utils/testing/util.py +++ b/libraries/cloudharness-utils/cloudharness_utils/testing/util.py @@ -4,13 +4,14 @@ from cloudharness_model.models import ApplicationUser, ApplicationTestConfig, ApplicationHarnessConfig, E2ETestsConfig + def get_user_password(main_user: ApplicationUser): return main_user.password or "test" + def get_app_environment(app_config: ApplicationHarnessConfig, app_domain, use_local_env=True): my_env = os.environ.copy() if use_local_env else {} my_env["APP_URL"] = app_domain - if app_config.accounts and app_config.accounts.users: main_user: ApplicationUser = app_config.accounts.users[0] @@ -25,4 +26,4 @@ def get_app_environment(app_config: ApplicationHarnessConfig, app_domain, use_lo my_env["IGNORE_CONSOLE_ERRORS"] = "true" if e2e_config.ignoreRequestErrors: my_env["IGNORE_REQUEST_ERRORS"] = "true" - return my_env \ No newline at end of file + return my_env diff --git a/lint-check.sh b/lint-check.sh new file mode 100644 index 000000000..426bfaff8 --- /dev/null +++ b/lint-check.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# Run autopep8 with --diff and capture the output +diff_output=$(autopep8 --select=E1,E2,E3,W,E4,E7,E502 --recursive --diff --exclude '**/cloudharness_cli/**/*,**/models/*,**/model/*' .) +# Check if the output is non-empty +if [ -n "$diff_output" ]; then + echo $diff_output + echo "Code style issues found in the above files. Please run autopep8 to fix them." + exit 1 +fi \ No newline at end of file diff --git a/tools/cloudharness-test/cloudharness_test/api.py b/tools/cloudharness-test/cloudharness_test/api.py index 5193902d4..216a669c2 100644 --- a/tools/cloudharness-test/cloudharness_test/api.py +++ b/tools/cloudharness-test/cloudharness_test/api.py @@ -6,7 +6,7 @@ from ch_cli_tools.preprocessing import get_build_paths -from cloudharness_model.models import HarnessMainConfig, ApiTestsConfig, ApplicationHarnessConfig +from cloudharness_model.models import HarnessMainConfig, ApiTestsConfig, ApplicationHarnessConfig from cloudharness_utils.testing.util import get_app_environment from cloudharness_utils.testing.api import get_api_filename, get_urls_from_api_file, get_schemathesis_command @@ -90,4 +90,3 @@ def run_api_tests(root_paths, helm_values: HarnessMainConfig, base_domain, inclu logging.error( "Some api test failed. Check output for more information.") exit(1) - diff --git a/tools/cloudharness-test/cloudharness_test/e2e.py b/tools/cloudharness-test/cloudharness_test/e2e.py index 388ed36dc..d164d9151 100644 --- a/tools/cloudharness-test/cloudharness_test/e2e.py +++ b/tools/cloudharness-test/cloudharness_test/e2e.py @@ -6,7 +6,7 @@ from cloudharness_model.models import ApplicationHarnessConfig -from cloudharness_utils.constants import E2E_TESTS_PROJECT_PATH, E2E_TESTS_DIRNAME +from cloudharness_utils.constants import E2E_TESTS_PROJECT_PATH, E2E_TESTS_DIRNAME from ch_cli_tools.preprocessing import get_build_paths from cloudharness_utils.testing.util import get_app_environment @@ -16,6 +16,7 @@ E2E_TESTS_PROJECT_ROOT = os.path.abspath(E2E_TESTS_PROJECT_PATH) if os.path.exists( E2E_TESTS_PROJECT_PATH) else os.path.join(ROOT, E2E_TESTS_PROJECT_PATH) + def run_e2e_tests(root_paths, helm_values, base_domain, included_applications=[], headless=False): if which("npm") is None: @@ -24,55 +25,53 @@ def run_e2e_tests(root_paths, helm_values, base_domain, included_applications=[] node_modules_path = os.path.join(E2E_TESTS_PROJECT_ROOT, "node_modules") if not os.path.exists(node_modules_path): - logging.info("Installing Jest-Puppeteer base project") - subprocess.run(["npm", "install"], cwd=E2E_TESTS_PROJECT_ROOT) + logging.info("Installing Jest-Puppeteer base project") + subprocess.run(["npm", "install"], cwd=E2E_TESTS_PROJECT_ROOT) artifacts = get_build_paths( - helm_values=helm_values, root_paths=root_paths) + helm_values=helm_values, root_paths=root_paths) failed = False for appkey in helm_values.apps: app_config: ApplicationHarnessConfig = helm_values.apps[appkey].harness appname = app_config.name - + if included_applications and appname not in included_applications: continue if not app_config.test.e2e.enabled: continue tests_dir = os.path.join( - artifacts[appkey.replace("_", "-")], "test", E2E_TESTS_DIRNAME) - + artifacts[appkey.replace("_", "-")], "test", E2E_TESTS_DIRNAME) + if not app_config.domain and not app_config.subdomain: logging.warn( - "Application %s has a test folder but no subdomain/domain is specified", appname) + "Application %s has a test folder but no subdomain/domain is specified", appname) continue app_domain = f"http{'s' if helm_values.tls else ''}://" + \ - (app_config.domain or f"{app_config.subdomain}.{base_domain}") - - - + (app_config.domain or f"{app_config.subdomain}.{base_domain}") + env = get_app_environment(app_config, app_domain) if not headless and os.environ.get('DISPLAY'): env["PUPPETEER_DISPLAY"] = "display" if os.path.exists(tests_dir): - + app_node_modules_path = os.path.join(tests_dir, "node_modules") if not os.path.exists(app_node_modules_path): logging.info("Linking tests libraries to %s", - app_node_modules_path) + app_node_modules_path) os.symlink(node_modules_path, app_node_modules_path) env["APP"] = artifacts[appkey.replace("_", "-")] logging.info( - "Running tests for application %s on domain %s", appname, app_domain) + "Running tests for application %s on domain %s", appname, app_domain) result = subprocess.run(["npm", "run", "test:app"], - cwd=E2E_TESTS_PROJECT_ROOT, env=env) + cwd=E2E_TESTS_PROJECT_ROOT, env=env) if result.returncode > 0: failed = True if failed: logging.error("Some end to end test failed. Check output for more information.") - exit(1) \ No newline at end of file + exit(1) diff --git a/tools/cloudharness-test/cloudharness_test/utils.py b/tools/cloudharness-test/cloudharness_test/utils.py index d00464563..e8a4aa21a 100644 --- a/tools/cloudharness-test/cloudharness_test/utils.py +++ b/tools/cloudharness-test/cloudharness_test/utils.py @@ -1,5 +1,6 @@ import requests + def url_check(url): try: # Get Url @@ -11,4 +12,4 @@ def url_check(url): except requests.exceptions.RequestException as e: # print URL with Errs - return False \ No newline at end of file + return False diff --git a/tools/cloudharness-test/harness-test b/tools/cloudharness-test/harness-test index 43c2d5016..fa69ddf88 100644 --- a/tools/cloudharness-test/harness-test +++ b/tools/cloudharness-test/harness-test @@ -41,7 +41,7 @@ if __name__ == "__main__": help=f'Run only end to end tests (default: run both api and end to end tests') parser.add_argument('-a', '--api', dest='run_api', action="store_const", default=None, const=True, help=f'Run only end to end tests (default: run both api and end to end tests') - + parser.add_argument('-hl', '--headless', dest='headless', action="store_true", default=False, help=f'Run headless end to end tests') args, unknown = parser.parse_known_args(sys.argv[1:]) @@ -61,24 +61,22 @@ if __name__ == "__main__": with open(helm_values_path) as f: helm_values_raw = yaml.load(f) - - if args.domain: - helm_values_raw["domain"] = args.domain + helm_values_raw["domain"] = args.domain with open(helm_values_path, 'w') as f: yaml.dump(helm_values_raw, f) helm_values = HarnessMainConfig.from_dict(helm_values_raw) base_domain = args.domain or helm_values.domain - + logging.info("Base domain is %s", base_domain) from cloudharness.utils.config import CloudharnessConfig CloudharnessConfig.allvalues["domain"] = base_domain if not url_check(f"http{'s' if helm_values.tls else ''}://{base_domain}"): logging.error( "Base domain unreachable. Is your deployment up and running? If yes, check your DNS/hosts settings.") - + included_applications = args.include if args.run_api == True or args.run_e2e is None: run_api_tests(root_paths, helm_values, base_domain, included_applications) diff --git a/tools/cloudharness-test/setup.py b/tools/cloudharness-test/setup.py index acb76ab4e..5f07dfd6a 100644 --- a/tools/cloudharness-test/setup.py +++ b/tools/cloudharness-test/setup.py @@ -28,7 +28,6 @@ ] - setup( name=NAME, version=VERSION, diff --git a/tools/deployment-cli-tools/ch_cli_tools/__init__.py b/tools/deployment-cli-tools/ch_cli_tools/__init__.py index 3df254d48..cca88e9b6 100644 --- a/tools/deployment-cli-tools/ch_cli_tools/__init__.py +++ b/tools/deployment-cli-tools/ch_cli_tools/__init__.py @@ -12,4 +12,4 @@ HERE = os.path.dirname(os.path.realpath(__file__)) -CH_ROOT = os.getenv("CH_ROOT") or dn(dn(dn(dn(os.path.realpath(__file__))))).replace(os.path.sep, '/') \ No newline at end of file +CH_ROOT = os.getenv("CH_ROOT") or dn(dn(dn(dn(os.path.realpath(__file__))))).replace(os.path.sep, '/') diff --git a/tools/deployment-cli-tools/ch_cli_tools/codefresh.py b/tools/deployment-cli-tools/ch_cli_tools/codefresh.py index 3fc5e833a..5bf5c1a2e 100644 --- a/tools/deployment-cli-tools/ch_cli_tools/codefresh.py +++ b/tools/deployment-cli-tools/ch_cli_tools/codefresh.py @@ -34,6 +34,7 @@ def literal_presenter(dumper, data): yaml.add_representer(str, literal_presenter) + def get_main_domain(url): try: url = url.split("//")[1].split("/")[0] @@ -45,6 +46,7 @@ def get_main_domain(url): except: return "${{ DEFAULT_REPO }}" + def clone_step_spec(conf: GitDependencyConfig, context_path: str): return { "title": f"Cloning {os.path.basename(conf.url)} repository...", @@ -52,9 +54,10 @@ def clone_step_spec(conf: GitDependencyConfig, context_path: str): "repo": conf.url, "revision": conf.branch_tag, "working_directory": join(context_path, "dependencies", conf.path or ""), - "git": get_main_domain(conf.url) # Cannot really tell what's the git config name, usually the name of the repo + "git": get_main_domain(conf.url) # Cannot really tell what's the git config name, usually the name of the repo } + def write_env_file(helm_values: HarnessMainConfig, filename, registry_secret=None): env = {} logging.info("Create env file with image info %s", filename) @@ -74,8 +77,6 @@ def check_image_exists(name, image): else: env[app_specific_tag_variable(name) + "_NEW"] = 1 - - for app in helm_values.apps.values(): if app.harness and app.harness.deployment.image: env[app_specific_tag_variable(app.name)] = extract_tag(app.harness.deployment.image) @@ -95,8 +96,6 @@ def check_image_exists(name, image): f.write(f"{k}={v}\n") - - def create_codefresh_deployment_scripts(root_paths, envs=(), include=(), exclude=(), template_name=CF_TEMPLATE_PATH, base_image_name=None, helm_values: HarnessMainConfig = None, save=True): @@ -128,7 +127,7 @@ def create_codefresh_deployment_scripts(root_paths, envs=(), include=(), exclude for root_path in root_paths: for e in envs: - + template_name = f"codefresh-template-{e}.yaml" template_path = join( root_path, DEPLOYMENT_CONFIGURATION_PATH, template_name) @@ -144,7 +143,7 @@ def create_codefresh_deployment_scripts(root_paths, envs=(), include=(), exclude steps = codefresh['steps'] def get_app_domain(app_config: ApplicationHarnessConfig): - base_domain=[c for c in codefresh['steps']['prepare_deployment']['commands'] if 'harness-deployment' in c][0].split("-d ")[1].split(" ")[0] + base_domain = [c for c in codefresh['steps']['prepare_deployment']['commands'] if 'harness-deployment' in c][0].split("-d ")[1].split(" ")[0] return f"https://{app_config.subdomain}.{base_domain}" def e2e_test_environment(app_config: ApplicationHarnessConfig, app_domain: str = None): @@ -245,7 +244,6 @@ def codefresh_steps_from_base_path(base_path, build_step, fixed_context=None, in clean_path(dockerfile_relative_to_root), app_name), environment=e2e_test_environment(app_config) ) - def add_unit_test_step(app_config: ApplicationHarnessConfig): # Create a run step for each application with tests/unit.yaml file using the corresponding image built at the previous step @@ -265,7 +263,7 @@ def add_unit_test_step(app_config: ApplicationHarnessConfig): codefresh_steps_from_base_path(join(root_path, BASE_IMAGES_PATH), CD_BUILD_STEP_BASE, fixed_context=relpath(root_path, os.getcwd()), include=helm_values[KEY_TASK_IMAGES].keys()) codefresh_steps_from_base_path(join(root_path, STATIC_IMAGES_PATH), CD_BUILD_STEP_STATIC, - include=helm_values[KEY_TASK_IMAGES].keys()) + include=helm_values[KEY_TASK_IMAGES].keys()) codefresh_steps_from_base_path(join( root_path, APPS_PATH), CD_BUILD_STEP_PARALLEL) @@ -281,7 +279,7 @@ def add_unit_test_step(app_config: ApplicationHarnessConfig): codefresh_steps_from_base_path(join( root_path, TEST_IMAGES_PATH), CD_BUILD_STEP_TEST, include=(name,), fixed_context=relpath(root_path, os.getcwd()), publish=False) steps[CD_API_TEST_STEP]["image"] = image_tag_with_variables(name, app_specific_tag_variable(name), base_name=base_image_name) - + if not codefresh: logging.warning( "No template file found. Codefresh script not created.") @@ -402,9 +400,10 @@ def codefresh_app_publish_spec(app_name, build_tag, base_name=None): step_spec['tags'].append('latest') return step_spec + def image_tag_with_variables(app_name, build_tag, base_name=""): return "${{REGISTRY}}/%s:${{%s}}" % (get_image_name( - app_name, base_name), build_tag or '${{DEPLOYMENT_TAG}}') + app_name, base_name), build_tag or '${{DEPLOYMENT_TAG}}') def app_specific_tag_variable(app_name): @@ -421,7 +420,7 @@ def codefresh_app_build_spec(app_name, app_context_path, dockerfile_path="Docker title=title, working_directory='./' + app_context_path, dockerfile=dockerfile_path) - + tag = app_specific_tag_variable(app_name) build["tag"] = "${{%s}}" % tag @@ -451,11 +450,12 @@ def add_arg_dependencies(dependencies): helm_values.apps[values_key].harness.dependencies.build) except (KeyError, AttributeError): add_arg_dependencies(helm_values['task-images']) - + when_condition = existing_build_when_condition(tag) build["when"] = when_condition return build + def existing_build_when_condition(tag): """ See https://codefresh.io/docs/docs/pipelines/conditional-execution-of-steps/#execute-steps-according-to-the-presence-of-a-variable @@ -472,5 +472,5 @@ def existing_build_when_condition(tag): } } } - + return when_condition diff --git a/tools/deployment-cli-tools/ch_cli_tools/helm.py b/tools/deployment-cli-tools/ch_cli_tools/helm.py index 2552dcbe2..80d6f08eb 100644 --- a/tools/deployment-cli-tools/ch_cli_tools/helm.py +++ b/tools/deployment-cli-tools/ch_cli_tools/helm.py @@ -212,7 +212,7 @@ def __init_base_images(self, base_image_name): self.static_images.update(find_dockerfiles_paths( os.path.join(root_path, STATIC_IMAGES_PATH))) return self.base_images - + def __init_test_images(self, base_image_name): test_images = {} for root_path in self.root_paths: @@ -224,7 +224,6 @@ def __init_test_images(self, base_image_name): return test_images - def __find_static_dockerfile_paths(self, root_path): return find_dockerfiles_paths(os.path.join(root_path, BASE_IMAGES_PATH)) + find_dockerfiles_paths(os.path.join(root_path, STATIC_IMAGES_PATH)) @@ -324,7 +323,6 @@ 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: @@ -415,19 +413,19 @@ def image_tag(self, image_name, build_context_path=None, dependencies=()): tag = generate_tag_from_content(build_context_path, ignore) logging.info(f"Content hash: {tag}") dependencies = dependencies or guess_build_dependencies_from_dockerfile(build_context_path) - tag = sha1((tag + "".join(self.all_images.get(n , '') for n in dependencies)).encode("utf-8")).hexdigest() + tag = sha1((tag + "".join(self.all_images.get(n, '') for n in dependencies)).encode("utf-8")).hexdigest() logging.info(f"Generated tag: {tag}") - app_name = image_name.split("/")[-1] # the image name can have a prefix + app_name = image_name.split("/")[-1] # the image name can have a prefix self.all_images[app_name] = tag return self.registry + image_name + (f':{tag}' if tag else '') - + def create_app_values_spec(self, app_name, app_path, base_image_name=None): logging.info('Generating values script for ' + app_name) specific_template_path = os.path.join(app_path, 'deploy', 'values.yaml') if os.path.exists(specific_template_path): logging.info("Specific values template found: " + - specific_template_path) + specific_template_path) values = get_template(specific_template_path) else: values = {} @@ -459,7 +457,7 @@ def create_app_values_spec(self, app_name, app_path, base_image_name=None): if len(image_paths) > 0: image_name = image_name_from_dockerfile_path(os.path.relpath( image_paths[0], os.path.dirname(app_path)), base_image_name) - + values['image'] = self.image_tag( image_name, build_context_path=app_path, dependencies=build_dependencies) elif KEY_HARNESS in values and not values[KEY_HARNESS].get(KEY_DEPLOYMENT, {}).get('image', None) and values[ diff --git a/tools/deployment-cli-tools/ch_cli_tools/models.py b/tools/deployment-cli-tools/ch_cli_tools/models.py index 83e950b37..a9719b604 100644 --- a/tools/deployment-cli-tools/ch_cli_tools/models.py +++ b/tools/deployment-cli-tools/ch_cli_tools/models.py @@ -1 +1 @@ -from cloudharness_model import * \ No newline at end of file +from cloudharness_model import * diff --git a/tools/deployment-cli-tools/ch_cli_tools/preprocessing.py b/tools/deployment-cli-tools/ch_cli_tools/preprocessing.py index 8fc973131..f19fe9bfe 100644 --- a/tools/deployment-cli-tools/ch_cli_tools/preprocessing.py +++ b/tools/deployment-cli-tools/ch_cli_tools/preprocessing.py @@ -8,8 +8,7 @@ from .helm import KEY_APPS, KEY_TASK_IMAGES from .utils import app_name_from_path, merge_app_directories, merge_configuration_directories, find_subdirs -from cloudharness_utils.constants import APPS_PATH,BASE_IMAGES_PATH, STATIC_IMAGES_PATH, DEFAULT_MERGE_PATH - +from cloudharness_utils.constants import APPS_PATH, BASE_IMAGES_PATH, STATIC_IMAGES_PATH, DEFAULT_MERGE_PATH def preprocess_build_overrides(root_paths, helm_values, merge_build_path=DEFAULT_MERGE_PATH): @@ -27,15 +26,12 @@ def preprocess_build_overrides(root_paths, helm_values, merge_build_path=DEFAULT def merge_appdir(root_path, base_path): app_name = app_name_from_path(basename(base_path)) dest_path = join( - merge_build_path, - relpath( base_path, root_path) - ) + merge_build_path, + relpath(base_path, root_path) + ) merge_configuration_directories(artifacts[app_name], dest_path) merge_configuration_directories(base_path, dest_path) - - - for root_path in root_paths: for base_path in find_subdirs(join(root_path, BASE_IMAGES_PATH)): @@ -70,18 +66,19 @@ def merge_appdir(root_path, base_path): elif app_name.replace("-", "_") in helm_values[KEY_APPS]: merge_appdir(root_path, base_path) merged = True - + if exists(merge_build_path): with open(join(merge_build_path, ".dockerignore"), "a") as dst: - + for root_path in root_paths: ignore_file = join(root_path, ".dockerignore") if os.path.exists(ignore_file): with open(ignore_file) as src: dst.write(src.read()) - + return (root_paths + [merge_build_path]) if merged else root_paths + def get_build_paths(root_paths, helm_values, merge_build_path=DEFAULT_MERGE_PATH): """ Gets the same paths from preprocess_build_overrides @@ -101,12 +98,12 @@ def get_build_paths(root_paths, helm_values, merge_build_path=DEFAULT_MERGE_PATH artifacts[app_name] = base_path else: artifacts[app_name] = join( - merge_build_path, - relpath( base_path, root_path) - ) + merge_build_path, + relpath(base_path, root_path) + ) for root_path in root_paths: for base_path in find_subdirs(join(root_path, STATIC_IMAGES_PATH)): - + app_name = app_name_from_path(basename(base_path)) if app_name not in helm_values[KEY_TASK_IMAGES]: continue @@ -114,9 +111,9 @@ def get_build_paths(root_paths, helm_values, merge_build_path=DEFAULT_MERGE_PATH artifacts[app_name] = base_path else: artifacts[app_name] = join( - merge_build_path, - relpath( base_path, root_path) - ) + merge_build_path, + relpath(base_path, root_path) + ) for root_path in root_paths: for base_path in find_subdirs(join(root_path, APPS_PATH)): @@ -127,8 +124,8 @@ def get_build_paths(root_paths, helm_values, merge_build_path=DEFAULT_MERGE_PATH artifacts[app_name] = base_path else: artifacts[app_name] = join( - merge_build_path, - relpath( base_path, root_path) - ) + merge_build_path, + relpath(base_path, root_path) + ) return artifacts diff --git a/tools/deployment-cli-tools/ch_cli_tools/skaffold.py b/tools/deployment-cli-tools/ch_cli_tools/skaffold.py index e2fbf1c9b..ab39f491d 100644 --- a/tools/deployment-cli-tools/ch_cli_tools/skaffold.py +++ b/tools/deployment-cli-tools/ch_cli_tools/skaffold.py @@ -14,11 +14,13 @@ from . import HERE + def relpath_if(p1, p2): if os.path.isabs(p1): return p1 return relpath(p1, p2) + def create_skaffold_configuration(root_paths, helm_values: HarnessMainConfig, output_path='.', manage_task_images=True): skaffold_conf = get_template('skaffold-template.yaml', True) apps = helm_values.apps @@ -64,7 +66,6 @@ def build_artifact( in requirements] return artifact_spec - base_images = set() def process_build_dockerfile( @@ -106,7 +107,6 @@ def process_build_dockerfile( for dockerfile_path in base_dockerfiles: process_build_dockerfile(dockerfile_path, root_path, global_context=True) - release_config = skaffold_conf['deploy']['helm']['releases'][0] release_config['name'] = helm_values.namespace release_config['namespace'] = helm_values.namespace @@ -120,7 +120,6 @@ def process_build_dockerfile( for dockerfile_path in static_dockerfiles: process_build_dockerfile(dockerfile_path, root_path) - for root_path in root_paths: apps_path = join(root_path, APPS_PATH) app_dockerfiles = find_dockerfiles_paths(apps_path) @@ -171,7 +170,7 @@ def identify_unicorn_based_main(candidates): import re gunicorn_pattern = re.compile(r"gunicorn") # sort candidates, shortest path first - for candidate in sorted(candidates,key=lambda x: len(x.split("/"))): + for candidate in sorted(candidates, key=lambda x: len(x.split("/"))): dockerfile_path = f"{candidate}/.." while not exists(f"{dockerfile_path}/Dockerfile") and abspath(dockerfile_path) != abspath(root_path): dockerfile_path += "/.." @@ -205,17 +204,17 @@ def identify_unicorn_based_main(candidates): test_config: ApplicationTestConfig = helm_values.apps[app_key].harness.test if test_config.unit.enabled and test_config.unit.commands: - skaffold_conf['test'].append(dict( - image=get_image_tag(app_name), - custom=[dict(command="docker run $IMAGE " + cmd) for cmd in test_config.unit.commands] - )) - + skaffold_conf['test'].append(dict( + image=get_image_tag(app_name), + custom=[dict(command="docker run $IMAGE " + cmd) for cmd in test_config.unit.commands] + )) skaffold_conf['build']['artifacts'] = [v for v in artifacts.values()] merge_to_yaml_file(skaffold_conf, os.path.join( output_path, 'skaffold.yaml')) return skaffold_conf + def git_clone_hook(conf: GitDependencyConfig, context_path: str): return { 'command': [ @@ -223,10 +222,11 @@ def git_clone_hook(conf: GitDependencyConfig, context_path: str): join(os.path.dirname(os.path.dirname(HERE)), 'clone.sh'), conf.branch_tag, conf.url, - join(context_path, "dependencies", conf.path or os.path.basename(conf.url).split('.')[0]) + join(context_path, "dependencies", conf.path or os.path.basename(conf.url).split('.')[0]) ] } + def create_vscode_debug_configuration(root_paths, helm_values): logging.info( "Creating VS code cloud build configuration.\nCloud build extension is needed to debug.") @@ -267,11 +267,10 @@ def get_image_tag(name): "sourceFileMap": { "justMyCode": False, f"${{workspaceFolder}}/{app_relative_to_root}": apps[app_key].harness.get('sourceRoot', - "/usr/src/app"), + "/usr/src/app"), } }) - if not os.path.exists(os.path.dirname(vscode_launch_path)): os.makedirs(os.path.dirname(vscode_launch_path)) with open(vscode_launch_path, 'w') as f: @@ -285,4 +284,4 @@ def get_additional_build_args(helm_values: HarnessMainConfig, app_key: str) -> d if not (helm_values.apps[app_key].harness.dockerfile and helm_values.apps[app_key].harness.dockerfile.buildArgs): return None - return helm_values.apps[app_key].harness.dockerfile.buildArgs \ No newline at end of file + return helm_values.apps[app_key].harness.dockerfile.buildArgs diff --git a/tools/deployment-cli-tools/ch_cli_tools/utils.py b/tools/deployment-cli-tools/ch_cli_tools/utils.py index 3ec8e6f38..cb167110f 100644 --- a/tools/deployment-cli-tools/ch_cli_tools/utils.py +++ b/tools/deployment-cli-tools/ch_cli_tools/utils.py @@ -145,9 +145,11 @@ def get_template(yaml_path, base_default=False): def file_is_yaml(fname): return fname[-4:] == 'yaml' or fname[-3:] == 'yml' + def file_is_json(fname): return fname[-4:] == 'json' + def replaceindir(root_src_dir, source, replace): """ Does copy and merge (shutil.copytree requires that the destination does not exist) @@ -290,11 +292,13 @@ def merge_yaml_files(fname, fdest): content_src = yaml.load(f) merge_to_yaml_file(content_src, fdest) + def merge_json_files(fname, fdest): with open(fname) as f: content_src = json.load(f) merge_to_json_file(content_src, fdest) + def merge_to_json_file(content_src, fdest): if not content_src: return @@ -313,6 +317,7 @@ def merge_to_json_file(content_src, fdest): json.dump(merged, f, indent=2) return merged + def merge_to_yaml_file(content_src, fdest): if not content_src: return @@ -364,8 +369,8 @@ def dict_merge(dct, merge_dct, add_keys=True): } for k, v in merge_dct.items(): - if (k in dct and isinstance(dct[k], dict) - and isinstance(merge_dct[k], collections.abc.Mapping)): + if (k in dct and isinstance(dct[k], dict) and + isinstance(merge_dct[k], collections.abc.Mapping)): dct[k] = dict_merge(dct[k], merge_dct[k], add_keys=add_keys) else: dct[k] = merge_dct[k] @@ -424,6 +429,7 @@ def check_docker_manifest_exists(registry, image_name, tag, registry_secret=None 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 diff --git a/tools/deployment-cli-tools/harness-application b/tools/deployment-cli-tools/harness-application index 37bec8089..580352870 100644 --- a/tools/deployment-cli-tools/harness-application +++ b/tools/deployment-cli-tools/harness-application @@ -26,7 +26,7 @@ if __name__ == "__main__": description='Creates a new Application.') parser.add_argument('name', metavar='name', type=str, help='Application name') - parser.add_argument('-t', '--template', dest='templates', action="append", default=['base',], + parser.add_argument('-t', '--template', dest='templates', action="append", default=['base', ], help="""Add a template name. Available templates: @@ -65,13 +65,12 @@ if __name__ == "__main__": if 'webapp' in templates: if os.path.exists(os.path.join(app_path, 'frontend')): shutil.rmtree(os.path.join(app_path, 'frontend')) - cmd = ["yarn", "create", "vite", args.name, "--template", "react-ts"] + cmd = ["yarn", "create", "vite", args.name, "--template", "react-ts"] logging.info(f"Running command: {' '.join(cmd)}") subprocess.run(cmd, cwd=app_path) shutil.move(os.path.join(app_path, args.name), os.path.join(app_path, 'frontend')) generate_ts_client(openapi_file=os.path.join(app_path, 'api/openapi.yaml')) - if 'server' in templates: with tempfile.TemporaryDirectory() as tmp_dirname: copymergedir(os.path.join(CH_ROOT, APPLICATION_TEMPLATE_PATH, template_name), tmp_dirname) diff --git a/tools/deployment-cli-tools/harness-deployment b/tools/deployment-cli-tools/harness-deployment index a9cecabbf..2a0bdd0b2 100644 --- a/tools/deployment-cli-tools/harness-deployment +++ b/tools/deployment-cli-tools/harness-deployment @@ -60,8 +60,7 @@ if __name__ == "__main__": parser.add_argument('-N', '--no-cd', dest='no_cd_gen', action="store_const", default=None, const=True, help=f'Do not generate ci/cd files') parser.add_argument('-we', '--write-env', dest='write_env', action="store_const", default=None, const=True, - help=f'Write build env to .env file in {DEPLOYMENT_PATH}') - + help=f'Write build env to .env file in {DEPLOYMENT_PATH}') args, unknown = parser.parse_known_args(sys.argv[1:]) @@ -108,7 +107,7 @@ if __name__ == "__main__": envs=envs, base_image_name=helm_values['name'], helm_values=helm_values) - + if args.write_env: write_env_file(helm_values, os.path.join(root_paths[-1], DEPLOYMENT_PATH, ".env")) diff --git a/tools/deployment-cli-tools/setup.py b/tools/deployment-cli-tools/setup.py index 92814c43e..b01b2ae24 100644 --- a/tools/deployment-cli-tools/setup.py +++ b/tools/deployment-cli-tools/setup.py @@ -32,7 +32,6 @@ ] - setup( name=NAME, version=VERSION, diff --git a/tools/deployment-cli-tools/tests/resources_buggy/applications/myapp/venv/matplotlib/__main__.py b/tools/deployment-cli-tools/tests/resources_buggy/applications/myapp/venv/matplotlib/__main__.py index 37de2b0b6..4d9acb290 100644 --- a/tools/deployment-cli-tools/tests/resources_buggy/applications/myapp/venv/matplotlib/__main__.py +++ b/tools/deployment-cli-tools/tests/resources_buggy/applications/myapp/venv/matplotlib/__main__.py @@ -4,4 +4,4 @@ def fake_content(): - ... \ No newline at end of file + ... diff --git a/tools/deployment-cli-tools/tests/resources_buggy/applications/myapp2/venv/matplotlib/__main__.py b/tools/deployment-cli-tools/tests/resources_buggy/applications/myapp2/venv/matplotlib/__main__.py index 37de2b0b6..4d9acb290 100644 --- a/tools/deployment-cli-tools/tests/resources_buggy/applications/myapp2/venv/matplotlib/__main__.py +++ b/tools/deployment-cli-tools/tests/resources_buggy/applications/myapp2/venv/matplotlib/__main__.py @@ -4,4 +4,4 @@ def fake_content(): - ... \ No newline at end of file + ... diff --git a/tools/deployment-cli-tools/tests/test_codefresh.py b/tools/deployment-cli-tools/tests/test_codefresh.py index 8d118d40b..4de2a07f1 100644 --- a/tools/deployment-cli-tools/tests/test_codefresh.py +++ b/tools/deployment-cli-tools/tests/test_codefresh.py @@ -13,6 +13,7 @@ if not os.path.exists(os.path.join(myapp_path, "dependencies/a/.git")): os.makedirs(os.path.join(myapp_path, "dependencies/a/.git")) + def test_create_codefresh_configuration(): values = create_helm_chart( [CLOUDHARNESS_ROOT, RESOURCES], @@ -126,7 +127,7 @@ def test_create_codefresh_configuration(): assert len( tstep['commands']) == 2, "Unit test commands are not properly loaded from the unit test configuration file" assert tstep['commands'][0] == "tox", "Unit test commands are not properly loaded from the unit test configuration file" - + assert len(l1_steps[CD_BUILD_STEP_DEPENDENCIES]['steps']) == 3, "3 clone steps should be included as we have 2 dependencies from myapp, plus cloudharness" finally: shutil.rmtree(BUILD_MERGE_DIR) @@ -153,12 +154,12 @@ def test_create_codefresh_configuration_multienv(): ) build_included = [app['harness']['name'] - for app in values['apps'].values() if 'harness' in app] + for app in values['apps'].values() if 'harness' in app] cf = create_codefresh_deployment_scripts(root_paths, include=build_included, - envs=['dev', 'test'], - base_image_name=values['name'], - helm_values=values, save=False) + envs=['dev', 'test'], + base_image_name=values['name'], + helm_values=values, save=False) assert cf['test_step'] == 'test' assert cf['test'] == True @@ -173,7 +174,6 @@ def test_create_codefresh_configuration_multienv(): shutil.rmtree(BUILD_MERGE_DIR) - def test_create_codefresh_configuration_tests(): values = create_helm_chart( [CLOUDHARNESS_ROOT, RESOURCES], @@ -213,8 +213,6 @@ def test_create_codefresh_configuration_tests(): assert "test-api" in st_build_test_steps["test-api"]["dockerfile"], "test-api image must be built from root context" - - e2e_steps = l1_steps[CD_E2E_TEST_STEP]['scale'] assert "samples_e2e_test" in e2e_steps, "samples e2e test step must be included" @@ -245,13 +243,12 @@ def test_create_codefresh_configuration_tests(): for volume in test_step["volumes"]: assert "server" not in volume - assert any("CLOUDHARNESS_BASE" in arg for arg in st_build_test_steps["test-api"] ["build_arguments"]), "Missing build dependency on api test image" finally: shutil.rmtree(BUILD_MERGE_DIR) - + values = create_helm_chart( [CLOUDHARNESS_ROOT, RESOURCES], output_path=OUT, diff --git a/tools/deployment-cli-tools/tests/test_helm.py b/tools/deployment-cli-tools/tests/test_helm.py index ed53ab863..da0073f09 100644 --- a/tools/deployment-cli-tools/tests/test_helm.py +++ b/tools/deployment-cli-tools/tests/test_helm.py @@ -148,17 +148,16 @@ def test_collect_helm_values_precedence(): assert values[KEY_APPS]['events']['kafka']['resources']['limits']['memory'] == 'overridden' assert values[KEY_APPS]['events']['kafka']['resources']['limits']['cpu'] == 'overridden-prod' + def test_collect_helm_values_multiple_envs(): values = create_helm_chart([CLOUDHARNESS_ROOT, RESOURCES], output_path=OUT, domain="my.local", namespace='test', env=['dev', 'test'], local=False, tag=1, include=["myapp"]) - assert values[KEY_APPS]['myapp']['test'] == True, 'values-test not loaded' assert values[KEY_APPS]['myapp']['dev'] == True, 'values-dev not loaded' assert values[KEY_APPS]['myapp']['a'] == 'test', 'values-test not overriding' - def test_collect_helm_values_wrong_dependencies_validate(): try: values = create_helm_chart([CLOUDHARNESS_ROOT, f"{RESOURCES}/wrong-dependencies"], output_path=OUT, domain="my.local", @@ -209,21 +208,21 @@ def test_collect_helm_values_build_dependencies(): assert 'cloudharness-base-debian' not in values[KEY_TASK_IMAGES], "Cloudharness-base-debian is not included in any dependency" assert 'cloudharness-frontend-build' not in values[KEY_TASK_IMAGES], "cloudharness-frontend-build is not included in any dependency" + def test_collect_helm_values_build_dependencies_nodeps(): values = create_helm_chart([CLOUDHARNESS_ROOT, RESOURCES], output_path=OUT, domain="my.local", namespace='test', env='prod', local=False, tag=1, include=["events"]) - assert 'cloudharness-flask' not in values[KEY_TASK_IMAGES], "Cloudharness-flask is not included in the build dependencies" assert 'cloudharness-base' not in values[KEY_TASK_IMAGES], "Cloudharness-base is not included in the build dependencies" assert 'cloudharness-base-debian' not in values[KEY_TASK_IMAGES], "Cloudharness-base-debian is not included in any dependency" assert 'cloudharness-frontend-build' not in values[KEY_TASK_IMAGES], "cloudharness-frontend-build is not included in any dependency" + def test_collect_helm_values_build_dependencies_exclude(): values = create_helm_chart([CLOUDHARNESS_ROOT, RESOURCES], output_path=OUT, domain="my.local", namespace='test', env='prod', local=False, tag=1, include=["workflows"], exclude=["workflows-extract-download"]) - assert 'cloudharness-flask' in values[KEY_TASK_IMAGES], "Cloudharness-flask is included in the build dependencies" assert 'cloudharness-base' in values[KEY_TASK_IMAGES], "Cloudharness-base is included in cloudharness-flask Dockerfile and it should be guessed" assert 'workflows-extract-download' not in values[KEY_TASK_IMAGES], "workflows-extract-download has been explicitly excluded" @@ -274,6 +273,7 @@ def test_clear_all_dbconfig_if_nodb(): db_config = values[KEY_APPS]['myapp'][KEY_HARNESS][KEY_DATABASE] assert db_config is None + def test_tag_hash_generation(): v1 = generate_tag_from_content(RESOURCES) v2 = generate_tag_from_content(RESOURCES, ignore=['myapp']) @@ -297,18 +297,19 @@ def test_tag_hash_generation(): finally: os.remove(fname) + def test_collect_helm_values_auto_tag(): def create(): return create_helm_chart([CLOUDHARNESS_ROOT, RESOURCES], output_path=OUT, include=['samples', 'myapp'], - exclude=['events'], domain="my.local", - namespace='test', env='dev', local=False, tag=None, registry='reg') - + exclude=['events'], domain="my.local", + namespace='test', env='dev', local=False, tag=None, registry='reg') + BASE_KEY = "cloudharness-base" values = create() # Auto values are set by using the directory hash assert 'reg/cloudharness/myapp:' in values[KEY_APPS]['myapp'][KEY_HARNESS]['deployment']['image'] - assert 'reg/cloudharness/myapp:' in values.apps['myapp'].harness.deployment.image + assert 'reg/cloudharness/myapp:' in values.apps['myapp'].harness.deployment.image assert 'cloudharness/myapp-mytask' in values[KEY_TASK_IMAGES]['myapp-mytask'] assert values[KEY_APPS]['myapp'][KEY_HARNESS]['deployment']['image'] == values.apps['myapp'].harness.deployment.image v1 = values.apps['myapp'].harness.deployment.image @@ -320,7 +321,6 @@ def create(): assert v1 == values.apps['myapp'].harness.deployment.image, "Nothing changed the hash value" assert values["task-images"][BASE_KEY] == b1, "Base image should not change following the root .dockerignore" - try: fname = os.path.join(RESOURCES, 'applications', 'myapp', 'afile.txt') with open(fname, 'w') as f: @@ -333,7 +333,6 @@ def create(): finally: os.remove(fname) - try: with open(fname, 'w') as f: f.write('a') @@ -343,7 +342,6 @@ def create(): finally: os.remove(fname) - fname = os.path.join(RESOURCES, 'applications', 'myapp', 'afile.ignored') try: with open(fname, 'w') as f: @@ -355,8 +353,6 @@ def create(): assert v1 == values.apps['myapp'].harness.deployment.image, "Nothing should change the hash value as the file is ignored in the .dockerignore" finally: os.remove(fname) - - # Dependencies test: if a dependency is changed, the hash should change fname = os.path.join(RESOURCES, 'infrastructure/common-images', 'my-common', 'afile') @@ -366,25 +362,21 @@ def create(): f.write('a') values = create() - + assert c1 != values["task-images"]["my-common"], "If content of a static image is changed, the hash should change" assert v1 != values.apps['myapp'].harness.deployment.image, "If a static image dependency is changed, the hash should change" finally: os.remove(fname) - fname = os.path.join(CLOUDHARNESS_ROOT, 'atestfile') try: with open(fname, 'w') as f: f.write('a') values = create() - + assert b1 != values["task-images"][BASE_KEY], "Content for base image is changed, the hash should change" assert d1 != values["task-images"]["cloudharness-flask"], "Content for base image is changed, the static image should change" assert v1 != values.apps['myapp'].harness.deployment.image, "2 levels dependency: If a base image dependency is changed, the hash should change" finally: os.remove(fname) - - - diff --git a/tools/deployment-cli-tools/tests/test_preprocessing.py b/tools/deployment-cli-tools/tests/test_preprocessing.py index b09711788..37c4bc66a 100644 --- a/tools/deployment-cli-tools/tests/test_preprocessing.py +++ b/tools/deployment-cli-tools/tests/test_preprocessing.py @@ -9,6 +9,7 @@ CLOUDHARNESS_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(HERE))) MERGE_BUILD_DIR = "/tmp/build" + def test_get_build_paths(): values = create_helm_chart([CLOUDHARNESS_ROOT, RESOURCES], output_path=OUT, include=['samples', 'myapp'], exclude=['events'], domain="my.local", @@ -16,30 +17,30 @@ def test_get_build_paths(): artifacts = get_build_paths(root_paths=[CLOUDHARNESS_ROOT, RESOURCES], helm_values=values, merge_build_path=MERGE_BUILD_DIR) assert 'cloudharness-base' in artifacts assert "events" not in artifacts - assert "samples" in artifacts + assert "samples" in artifacts assert artifacts['cloudharness-base'] == os.path.join(MERGE_BUILD_DIR, BASE_IMAGES_PATH, "cloudharness-base") assert artifacts['samples'] == os.path.join(CLOUDHARNESS_ROOT, APPS_PATH, "samples") + def test_preprocess_build_overrides(): values = create_helm_chart([CLOUDHARNESS_ROOT, RESOURCES], output_path=OUT, include=['samples', 'myapp'], exclude=['events'], domain="my.local", namespace='test', env='dev', local=False, tag=1, registry='reg') - res = preprocess_build_overrides(root_paths=[CLOUDHARNESS_ROOT, RESOURCES], helm_values=values, merge_build_path=MERGE_BUILD_DIR) assert len(res) == 3 assert MERGE_BUILD_DIR in res[2] assert os.path.exists(MERGE_BUILD_DIR) - assert os.path.exists(os.path.join(MERGE_BUILD_DIR, BASE_IMAGES_PATH, "cloudharness-base/testfile")) - assert os.path.exists(os.path.join(MERGE_BUILD_DIR, BASE_IMAGES_PATH, "cloudharness-base/Dockerfile")) - assert not os.path.exists(os.path.join(MERGE_BUILD_DIR, BASE_IMAGES_PATH, "cloudharness-base-debian")) - assert not os.path.exists(os.path.join(MERGE_BUILD_DIR, APPS_PATH, "events")) + assert os.path.exists(os.path.join(MERGE_BUILD_DIR, BASE_IMAGES_PATH, "cloudharness-base/testfile")) + assert os.path.exists(os.path.join(MERGE_BUILD_DIR, BASE_IMAGES_PATH, "cloudharness-base/Dockerfile")) + assert not os.path.exists(os.path.join(MERGE_BUILD_DIR, BASE_IMAGES_PATH, "cloudharness-base-debian")) + assert not os.path.exists(os.path.join(MERGE_BUILD_DIR, APPS_PATH, "events")) - assert os.path.exists(os.path.join(MERGE_BUILD_DIR, APPS_PATH, "accounts/deploy/values.yaml")) - assert os.path.exists(os.path.join(MERGE_BUILD_DIR, APPS_PATH, "workflows/tasks/new-task/Dockerfile")) - assert os.path.exists(os.path.join(MERGE_BUILD_DIR, APPS_PATH, "workflows/tasks/notify-queue/new-file")) - assert os.path.exists(os.path.join(MERGE_BUILD_DIR, APPS_PATH, "workflows/tasks/notify-queue/Dockerfile")) + assert os.path.exists(os.path.join(MERGE_BUILD_DIR, APPS_PATH, "accounts/deploy/values.yaml")) + assert os.path.exists(os.path.join(MERGE_BUILD_DIR, APPS_PATH, "workflows/tasks/new-task/Dockerfile")) + assert os.path.exists(os.path.join(MERGE_BUILD_DIR, APPS_PATH, "workflows/tasks/notify-queue/new-file")) + assert os.path.exists(os.path.join(MERGE_BUILD_DIR, APPS_PATH, "workflows/tasks/notify-queue/Dockerfile")) shutil.rmtree(MERGE_BUILD_DIR) diff --git a/tools/deployment-cli-tools/tests/test_skaffold.py b/tools/deployment-cli-tools/tests/test_skaffold.py index 999ad6396..3c545d214 100644 --- a/tools/deployment-cli-tools/tests/test_skaffold.py +++ b/tools/deployment-cli-tools/tests/test_skaffold.py @@ -70,10 +70,9 @@ def test_create_skaffold_configuration(): cloudharness_flask_artifact = next( a for a in sk['build']['artifacts'] if a['image'] == 'reg/cloudharness/cloudharness-flask') - assert os.path.samefile(cloudharness_flask_artifact['context'], - join(CLOUDHARNESS_ROOT, 'infrastructure/common-images/cloudharness-flask') - ) + join(CLOUDHARNESS_ROOT, 'infrastructure/common-images/cloudharness-flask') + ) assert len(cloudharness_flask_artifact['requires']) == 1 @@ -94,7 +93,6 @@ def test_create_skaffold_configuration(): a for a in sk['build']['artifacts'] if a['image'] == 'reg/cloudharness/accounts') assert os.path.samefile(accounts_artifact['context'], '/tmp/build/applications/accounts') - # Custom unit tests assert len(sk['test']) == 2, 'Unit tests should be included' @@ -181,4 +179,4 @@ def test_create_skaffold_configuration_with_conflicting_dependencies_requirement assert 'matplotlib' not in release['overrides']['apps'] myapp_config = release['overrides']['apps']['myapp2'] - assert myapp_config['harness']['deployment']['args'][0] == '/usr/src/app/myapp_code/__main__.py' \ No newline at end of file + assert myapp_config['harness']['deployment']['args'][0] == '/usr/src/app/myapp_code/__main__.py' diff --git a/tools/deployment-cli-tools/tests/test_utils.py b/tools/deployment-cli-tools/tests/test_utils.py index cade77700..78e843334 100644 --- a/tools/deployment-cli-tools/tests/test_utils.py +++ b/tools/deployment-cli-tools/tests/test_utils.py @@ -76,13 +76,14 @@ def test_check_docker_manifest_exists(): assert check_docker_manifest_exists("gcr.io/metacellllc", "cloudharness/cloudharness-base", "latest") assert not check_docker_manifest_exists("gcr.io/metacellllc", "cloudharness/cloudharness-base", "RANDOM_TAG") + def test_find_dockerfile_paths(): - + myapp_path = os.path.join(HERE, "resources/applications/myapp") if not os.path.exists(os.path.join(myapp_path, "dependencies/a/.git")): os.makedirs(os.path.join(myapp_path, "dependencies/a/.git")) - + dockerfiles = find_dockerfiles_paths(myapp_path) assert len(dockerfiles) == 2 assert next(d for d in dockerfiles if d.endswith("myapp")), "Must find the Dockerfile in the root directory" - assert next(d for d in dockerfiles if d.endswith("myapp/tasks/mytask")), "Must find the Dockerfile in the tasks directory" \ No newline at end of file + assert next(d for d in dockerfiles if d.endswith("myapp/tasks/mytask")), "Must find the Dockerfile in the tasks directory"