From 6f3fc963ab1fe03693d576c774316b23c1c10b86 Mon Sep 17 00:00:00 2001 From: Filippo Ledda Date: Tue, 14 Jan 2025 18:23:48 +0100 Subject: [PATCH 01/12] CH-170 fix secrets upgrade --- .../helm/templates/auto-secrets.yaml | 62 +++++++++++-------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/deployment-configuration/helm/templates/auto-secrets.yaml b/deployment-configuration/helm/templates/auto-secrets.yaml index a0a37a2f8..0a1ccc3b0 100644 --- a/deployment-configuration/helm/templates/auto-secrets.yaml +++ b/deployment-configuration/helm/templates/auto-secrets.yaml @@ -1,5 +1,4 @@ {{- define "deploy_utils.secret" }} -{{- if .app.harness.secrets }} {{- $secret_name := printf "%s" .app.harness.deployment.name }} apiVersion: v1 kind: Secret @@ -9,42 +8,53 @@ metadata: labels: app: {{ .app.harness.deployment.name }} type: Opaque - {{- $secret := (lookup "v1" "Secret" .root.Values.namespace $secret_name) }} - {{- if $secret }} -# secret already exists - {{- if not (compact (values .app.harness.secrets)) }} -# secret values are null, copy from the existing secret -data: - {{- range $k, $v := $secret.data }} - {{ $k }}: {{ $v }} - {{- end }} - {{- else }} -# there are non default values in values.yaml, use these +{{- $secret := (lookup "v1" "Secret" .root.Values.namespace $secret_name) }} +{{/*- $secret := dict "data" (dict "test" "test") */}} stringData: - {{- range $k, $v := .app.harness.secrets }} - {{ $k }}: {{ $v | default (randAlphaNum 20) }} - {{- end }} - {{- end }} - {{- else }} -# secret doesn't exist -stringData: - {{- range $k, $v := .app.harness.secrets }} - {{ $k }}: {{ $v | default (randAlphaNum 20) }} + updated: {{ now | quote }} # Added because in case of update, if no field is updated, alla data is erased +{{- if $secret }} + {{- range $k, $v := .app.harness.secrets }} + {{- if $v }} + {{- if eq (typeOf $v) "string" }} + {{- if ne $v "?" }} + # Update/set value to value in values.yaml if specified + {{ $k }}: {{ $v | quote }} + {{- else }} + # Refresh at any deployment for ? (pure random) value + {{ $k }}: {{ randAlphaNum 20 | quote }} + {{- end }} + {{- else }} + # Type not recognized: setting to a empty string" + {{ $k }}-formatnotrecognized: {{ $v }} + {{ $k }}: "" + {{- end }} + {{- else if eq (typeOf $secret.data) (typeOf dict) }} + # Value empty or null in the values.yaml + {{- if not (hasKey $secret.data $k) }} + # Create a random secret value if not specified in values.yaml if it is not set and it is not already in the deployed secret (static random secret) */}} + {{ $k }}: {{ randAlphaNum 20 | quote }} + {{- else }} + # confirm previous value from the secret (static random secret already set, do nothing)} + {{- end}} {{- end }} + {{- end }} # range end +{{- else }} +# New secret + {{- range $k, $v := .app.harness.secrets }} + {{ $k }}: {{ $v | default (randAlphaNum 20) | quote }} {{- end }} {{- end }} --- {{- end }} ---- {{- range $app := .Values.apps }} ---- + {{- if $app.harness.secrets }}{{- if ne (len $app.harness.secrets) 0 }} {{- include "deploy_utils.secret" (dict "root" $ "app" $app) }} + {{- end }}{{- end }} {{- range $subapp := $app }} {{- if contains "map" (typeOf $subapp) }} - {{- if hasKey $subapp "harness" }} ---- + {{- if hasKey $subapp "harness" }}{{- if $app.harness.secrets }}{{- if ne (len $app.harness.secrets) 0 }} {{- include "deploy_utils.secret" (dict "root" $ "app" $subapp) }} - {{- end }} + {{- end }}{{- end }}{{- end }} {{- end }} {{- end }} {{- end }} \ No newline at end of file From e1a99b0edc19900b5aba7c0faa96a79cb430bb48 Mon Sep 17 00:00:00 2001 From: Filippo Ledda Date: Thu, 19 Feb 2026 12:03:46 +0100 Subject: [PATCH 02/12] chore: cleanup --- ch-166.patch | 14 -- .../codefresh-template-stage-onpremise.yaml | 162 ------------------ openapitools.json | 7 - 3 files changed, 183 deletions(-) delete mode 100644 ch-166.patch delete mode 100644 deployment-configuration/codefresh-template-stage-onpremise.yaml delete mode 100644 openapitools.json diff --git a/ch-166.patch b/ch-166.patch deleted file mode 100644 index 1603e0a07..000000000 --- a/ch-166.patch +++ /dev/null @@ -1,14 +0,0 @@ -diff --git a/tools/deployment-cli-tools/ch_cli_tools/codefresh.py b/tools/deployment-cli-tools/ch_cli_tools/codefresh.py -index 8bcf2b79..3ea43e31 100644 ---- a/tools/deployment-cli-tools/ch_cli_tools/codefresh.py -+++ b/tools/deployment-cli-tools/ch_cli_tools/codefresh.py -@@ -175,8 +175,7 @@ def create_codefresh_deployment_scripts(root_paths, envs=(), include=(), exclude - - if app_config and app_config.dependencies and app_config.dependencies.git: - for dep in app_config.dependencies.git: -- step_name = f"clone_{basename(dep.url).replace('.', '_')}_{basename(dockerfile_relative_to_root).replace('.', '_')}" -- steps[CD_BUILD_STEP_DEPENDENCIES]['steps'][step_name] = clone_step_spec(dep, dockerfile_relative_to_root) -+ steps[CD_BUILD_STEP_DEPENDENCIES]['steps'][f"clone_{basename(dep.url).replace(".", "_")}_{basename(dockerfile_relative_to_root).replace(".", "_")}"] = clone_step_spec(dep, dockerfile_relative_to_root) - - build = None - if build_step in steps: diff --git a/deployment-configuration/codefresh-template-stage-onpremise.yaml b/deployment-configuration/codefresh-template-stage-onpremise.yaml deleted file mode 100644 index 8b365a96f..000000000 --- a/deployment-configuration/codefresh-template-stage-onpremise.yaml +++ /dev/null @@ -1,162 +0,0 @@ -version: "1.0" -stages: - - prepare - - build - - deploy - - qa - - publish -steps: - main_clone: - title: Clone main repository - type: git-clone - stage: prepare - repo: "${{CF_REPO_OWNER}}/${{CF_REPO_NAME}}" - revision: "${{CF_BRANCH}}" - git: github - post_main_clone: - title: Post main clone - type: parallel - stage: prepare - steps: - clone_cloud_harness: - title: Cloning cloud-harness repository... - type: git-clone - stage: prepare - repo: "https://github.com/MetaCell/cloud-harness.git" - revision: "${{CLOUDHARNESS_BRANCH}}" - working_directory: . - git: github - prepare_deployment: - title: "Prepare helm chart" - image: python:3.12 - stage: prepare - working_directory: . - commands: - - bash cloud-harness/install.sh - - harness-deployment $PATHS -t ${{DEPLOYMENT_TAG}} -d ${{DOMAIN}} -r ${{REGISTRY}} -rs ${{REGISTRY_SECRET}} -n ${{NAMESPACE}} -e $ENV --no-cd $PARAMS - prepare_deployment_view: - commands: - - "helm template ./deployment/helm --debug -n ${{NAMESPACE}}" - environment: - - ACTION=auth - - KUBE_CONTEXT=${{NAMESPACE}} - image: codefresh/cfstep-helm:3.6.2 - stage: prepare - title: "View helm chart" - deployment: - stage: deploy - type: helm - working_directory: ./${{CF_REPO_NAME}} - title: Installing chart - arguments: - helm_version: 3.6.2 - chart_name: deployment/helm - release_name: ${{NAMESPACE}} - kube_context: ${{CLUSTER_NAME}} - namespace: ${{NAMESPACE}} - chart_version: ${{DEPLOYMENT_TAG}} - cmd_ps: --wait --timeout 600s --create-namespace - custom_value_files: - - ./deployment/helm/values.yaml - build_test_images: - title: Build test images - type: parallel - stage: qa - steps: [] - when: - condition: - all: - whenVarExists: 'includes("${{SKIP_TESTS}}", "{{SKIP_TESTS}}") == true' - wait_deployment: - stage: qa - title: Wait deployment to be ready - image: codefresh/kubectl - commands: - - kubectl config use-context ${{CLUSTER_NAME}} - - kubectl config set-context --current --namespace=${{NAMESPACE}} - tests_api: - stage: qa - title: Api tests - working_directory: /home/test - image: "${{REGISTRY}}/cloud-harness/test-api:latest" - fail_fast: false - commands: - - echo $APP_NAME - scale: {} - when: - condition: - all: - whenVarExists: 'includes("${{SKIP_TESTS}}", "{{SKIP_TESTS}}") == true' - tests_e2e: - stage: qa - title: End to end tests - working_directory: /home/test - image: "${{REGISTRY}}/cloud-harness/test-e2e:latest" - fail_fast: false - commands: - - yarn test - scale: {} - when: - condition: - all: - whenVarExists: 'includes("${{SKIP_TESTS}}", "{{SKIP_TESTS}}") == true' - manual_tests: - type: pending-approval - stage: publish - title: Manual tests performed - description: Manual tests have been performed and reported - timeout: - duration: 168 - finalState: approved - approval: - type: pending-approval - stage: publish - title: Approve release - description: Approve release and tagging/publication - timeout: - duration: 168 - finalState: approved - publish_helm_chart: - title: Publish Helm chart to artifact registry - stage: publish - image: google/cloud-sdk:alpine - working_directory: . - commands: - - echo $GCP_SA_KEY | base64 -d > /tmp/gcp-key.json - - gcloud auth activate-service-account --key-file=/tmp/gcp-key.json - - gcloud auth configure-docker ${{ARTIFACT_REGISTRY_LOCATION}}-docker.pkg.dev - - helm package ./deployment/helm --version ${{DEPLOYMENT_PUBLISH_TAG}} - - helm push ${{NAMESPACE}}-${{DEPLOYMENT_PUBLISH_TAG}}.tgz oci://${{ARTIFACT_REGISTRY_LOCATION}}-docker.pkg.dev/${{GCP_PROJECT_ID}}/${{HELM_REPO}} - - rm /tmp/gcp-key.json - when: - condition: - all: - whenVarExists: 'includes("${{DEPLOYMENT_PUBLISH_TAG}}", "{{DEPLOYMENT_PUBLISH_TAG}}") == false' - whenVarExists2: 'includes("${{ARTIFACT_REGISTRY_LOCATION}}", "{{ARTIFACT_REGISTRY_LOCATION}}") == false' - whenVarExists3: 'includes("${{GCP_PROJECT_ID}}", "{{GCP_PROJECT_ID}}") == false' - whenVarExists4: 'includes("${{HELM_REPO}}", "{{HELM_REPO}}") == false' - whenVarExists5: 'includes("${{GCP_SA_KEY}}", "{{GCP_SA_KEY}}") == false' - publish: - type: parallel - stage: publish - steps: REPLACE_ME - when: - condition: - all: - whenVarExists: 'includes("${{DEPLOYMENT_PUBLISH_TAG}}", "{{DEPLOYMENT_PUBLISH_TAG}}") == false' - git-tag: - title: Performing git tagging - stage: publish - image: alpine/git:latest - commands: - - git tag ${{DEPLOYMENT_PUBLISH_TAG}} - - ORIGIN=$(git remote get-url origin) - - PROTOCOL=https:// - - REPLACEMENT=${PROTOCOL}${{REPO_TOKEN}}@ - - git remote set-url origin ${ORIGIN/$PROTOCOL/$REPLACEMENT} - - git push origin --tags - when: - condition: - all: - whenVarExists: 'includes("${{DEPLOYMENT_PUBLISH_TAG}}", "{{DEPLOYMENT_PUBLISH_TAG}}") == false' - whenVarExists2: 'includes("${{REPO_TOKEN}}", "{{REPO_TOKEN}}") == false' diff --git a/openapitools.json b/openapitools.json deleted file mode 100644 index b11fef7de..000000000 --- a/openapitools.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "$schema": "node_modules/@openapitools/openapi-generator-cli/config.schema.json", - "spaces": 2, - "generator-cli": { - "version": "7.8.0" - } -} From 316b987c70032baf3ff3d70d8f7e2382e700502a Mon Sep 17 00:00:00 2001 From: Filippo Ledda Date: Thu, 19 Feb 2026 12:37:06 +0100 Subject: [PATCH 03/12] Upgrade documentation --- docs/upgrades/2.x_3.x.md | 73 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 docs/upgrades/2.x_3.x.md diff --git a/docs/upgrades/2.x_3.x.md b/docs/upgrades/2.x_3.x.md new file mode 100644 index 000000000..84643e5e5 --- /dev/null +++ b/docs/upgrades/2.x_3.x.md @@ -0,0 +1,73 @@ + +## Upgrade project from 2.x to 3.x + +## Update Python virtual environment + +With conda: + +```sh +conda create --name ENVNAME python=3.12 +conda activate ENVNAME +source install.sh +``` + +## Migrate cloudharness-base images to debian + +Before 3.x, there were 2 different base images that could be used on applications: + +- `cloudharness-base` -- based on Alpine -- `FROM $CLOUDHARNESS_BASE` in Dockerfiles +- `cloudharness-base-debian` -- based on Debian -- `FROM $CLOUDHARNESS_BASE_DEBIAN` in Dockerfiles + +Now `cloudharness-base` is based on Debian and `cloudharness-base-debian` cannot be used anymore as a dependency + +The new command `harness-migrate` will help with porting your cloudharness-base dependent images to debian. + +After that, it's likely that some apk based dependencies have still to be tweaked in your Dockerfiles. + + +## Update Keycloak + +Updating Keycloak is easily done by restoring a Postgres backup after the upgrade is done, +and letting Keycloak apply the migrations taking care of not mixing old and new replicas. + +### Prepare for update + +The new update is available on Cloud Harness 3.x+, or the develop branch. + +The following files are usually overridden and might need merge/replacement: + +applications/accounts/Dockerfile + +applications/accounts/deploy/resources/realm.json + +deployment-configuration/helm/templates/auto-gatekeepers.yaml + +In addition to this, there might be references to the code to old paths, as the new version removed the /auth prefix. + +If keycloak-js is used, better remove it as the updated version has known issues and doesn’t work on local deployments. The replacement to keycloak-js is to use a Gatekeeper and get user information from the kc-access cookie, as done for example here. + +### Upgrade procedure +#### Before upgrading +```sh +BACKUP_FILE=full_backup_keycloak_postgres.psql +NAMESPACE="${1:-neuroglass-research}" + +kubectl exec -n $NAMESPACE deployment/keycloak-postgres -- pg_dump -d auth_db -U postgres -F c > kc-$BACKUP_FILE + +kubectl scale deployment accounts --replicas=0 -n $NAMESPACE +``` + +#### After upgrading (new version deployed) +```sh +kubectl scale deployment accounts --replicas=0 -n $NAMESPACE +kubectl exec -i -n $NAMESPACE deployment/keycloak-postgres -- psql -U user -d auth_db -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;" +kubectl exec -i -n $NAMESPACE deployment/keycloak-postgres -- pg_restore --if-exists --no-owner --clean -d auth_db -U user < kc-$BACKUP_FILE +kubectl scale deployment accounts --replicas=1 -n $NAMESPACE +``` + +### Manual steps + +There are a few known issues that have to be fixed manually after the upgrade: + +1. Users cannot access their account page (e.g. https://accounts.research.neuroglass.dev.metacell.us/realms/mnp/account/) unless this: https://github.com/keycloak/keycloak/discussions/12894#discussioncomment-3145960 → add the client scopes as optional to the account-console client. +2. sub is not present in the token → add the mapper for sub to the profile client scope From 3ed3a35d450536fb72d16f5acc32a9baca4b79e0 Mon Sep 17 00:00:00 2001 From: Filippo Ledda Date: Fri, 20 Feb 2026 18:16:01 +0100 Subject: [PATCH 04/12] feat: add comprehensive instructions for Copilot, E2E testing, and deployment tools --- .github/instructions/copilot-instructions.md | 23 ++++++ .github/instructions/test-e2e.instructions.md | 23 ++++++ .github/instructions/tools.instructions.md | 74 +++++++++++++++++++ 3 files changed, 120 insertions(+) create mode 100644 .github/instructions/copilot-instructions.md create mode 100644 .github/instructions/test-e2e.instructions.md create mode 100644 .github/instructions/tools.instructions.md diff --git a/.github/instructions/copilot-instructions.md b/.github/instructions/copilot-instructions.md new file mode 100644 index 000000000..b54a16327 --- /dev/null +++ b/.github/instructions/copilot-instructions.md @@ -0,0 +1,23 @@ +# Cloud Harness + +Cloud Harness provides software infrastructure and tools for neuroscience data computing and analysis in a monorepo. + +## General concepts + +### Files content + +- `applications`: Cloud Harness custom server applications go here +- `client`: Cloud Harness generated client api +- `deployment`: deployment related scripts and files +- `deployment-configuration`: deployment customization files +- `infrastructure`: infrastructure utilities +- `libraries`: Cloud Harness shared libraries +- `docs`: developers documentation files +- `tools`: Cloud Harness CLI and other tools +- `test`: Cloud Harness test utilities and test code + +Verify which application/components is in scope and read specific prompt instruction before proceeding. + +Check best practices in every instruction file in scope and docs and apply them when writing code or performing code reviews. +Use reference for any questions regarding project structure, development workflow, and best practices. +If you have any doubts about where to find information, ask for clarification before proceeding. \ No newline at end of file diff --git a/.github/instructions/test-e2e.instructions.md b/.github/instructions/test-e2e.instructions.md new file mode 100644 index 000000000..0a89583d3 --- /dev/null +++ b/.github/instructions/test-e2e.instructions.md @@ -0,0 +1,23 @@ +--- +applyTo: "test/e2e/*" +--- +# Neuroglass Research E2E Tests + +## End-to-end (E2E) Tests +- **Location**: `test/e2e/*.spec.ts` +- **Framework**: Jest + Puppeteer (see existing tests for patterns) + +### Login Flow +- Tests must handle the 2-step login redirect when `APP_URL` points to the accounts domain. +- Use `USERNAME` and `PASSWORD` environment variables for credentials. +- Follow the existing flow: + - Navigate to `APP_URL` and detect redirect to accounts. + - Enter username, submit, wait for password field, enter password, submit. + - Wait for redirect back to the app and confirm route. + +### Stability Requirements (Mandatory) +- **Waiting**: Always use Puppeteer explicit waits (`waitForSelector`, `waitForFunction`, `waitForNavigation`) with timeouts. +- **Selectors**: Rely on stable, custom selectors (see `test/e2e/selectors.ts`). +- **Do not** depend on UI copy, text content, or fragile DOM structure. +- **Avoid fixed sleeps** unless there is no deterministic signal; prefer state-based waits. +- **Resilience**: When possible, guard against flaky overlays (cookie/announcement modals). diff --git a/.github/instructions/tools.instructions.md b/.github/instructions/tools.instructions.md new file mode 100644 index 000000000..2ab5d6a12 --- /dev/null +++ b/.github/instructions/tools.instructions.md @@ -0,0 +1,74 @@ +--- +applyTo: "tools/deployment-cli-tools/* +--- +# Neuroglass Project Developer Reference + +## Environment Setup + +### Required Environment +- **Conda Environment Name**: `ch` +- **Python Version**: 3.12+ +- **Activation Command**: `conda activate ch` + +### Package Managers +- **Frontend**: Yarn (NEVER use npm) +- **Backend**: pip (within conda environment only) + +### Pre-requisites Checklist +- [ ] Conda environment `ch` is activated +- [ ] Correct directory navigation completed +- [ ] Appropriate package manager selected (yarn/pip) + +## Development Workflow + +### Mandatory Pre-Command Steps +1. **ALWAYS** activate conda environment first: `conda activate ch` +2. Navigate to the appropriate project directory +3. Use yarn for frontend operations, pip for backend operations + +## Project Structure + +### Key Scripts +- `harness-generate` - Generate code +- `harness-deployment` - Generate deployment files: helm charts, ci/cd files, etc. +- `harness-application` - Generate application code (e.g., Django apps) +- `harness-migrate` - Migration helper tool +- `ch_cli_tools` - Python package for deployment and other tools +- `tests` - Unit test utilities and test code + + +## Code Style and best practices + +Take the following best practices into account when writing code for the project adn while performing code reviews: + +- Keep architecture lean: avoid unnecessary layers and abstractions. +- Use utils for stateless pure functions that don't hit external data sources nor the ORM. Utils are horizontal and can be used across the project. +- Use helpers to organize pieces of business logic; keep them stateless when possible. +- Use services for business workflows and cross-model coordination. Services are vertical on a single model or a group of related models +- Keep model logic close to the model when it represents domain rules or invariants. +- Handle exceptions only at the higher level; let lower layers raise. NEVER catch exceptions in helpers or services unless you are adding context and re-raising. +- Cover critical logic with unit tests, especially in helpers and services. Use mocks to isolate units under test. +- Prefer models classes for helpers and services to ensure data validation and clear interfaces. Use typed dicts for structured data that isn't covered by Schema classes. Use plain dicts only to represent real unstructured data. Avoid returning tuples. + + +## Important Constraints + +### File Creation Rules +- **NEVER** create new README or documentation files unless explicitly requested +- Follow existing documentation patterns when updates are needed + +### Development Server Rules +- **NEVER** run development servers +- **ALWAYS** assume servers are running +- **MUST** ask confirmation before opening browsers + +### Package Management Rules +- **Frontend**: ONLY use yarn, NEVER npm +- **Backend**: ONLY use pip within conda environment +- **ALWAYS** activate `mnp` conda environment before any backend work + +### CloudHarness Considerations +- Dependencies may need special handling in development environment +- Follow established patterns for CloudHarness integration + +--- \ No newline at end of file From 5d7f2a295146944b17e8267125f292eda599262c Mon Sep 17 00:00:00 2001 From: Filippo Ledda Date: Fri, 20 Feb 2026 18:17:05 +0100 Subject: [PATCH 05/12] CH-239 Enhance helm deployment with optional metadata arguments - Updated deployment configurations to include optional helm metadata arguments such as chart name, chart version, and app version in `codefresh-template-stage.yaml` and `codefresh-template-test.yaml`. - Refactored helm chart creation logic to support these new parameters in `helm.py`. - Added new field `image_name` in ApplicationHarnessConfig to allow custom image naming. - Updated OpenAPI and model documentation to reflect the new `image_name` field. - Added tests to validate the inclusion of helm metadata arguments and image name overrides in deployment configurations. - Cleaned up unused build steps in `codefresh-test.yaml` and removed redundant commands. --- applications/samples/deploy/values.yaml | 2 + .../codefresh-template-dev.yaml | 3 +- .../codefresh-template-prod.yaml | 3 +- .../codefresh-template-stage.yaml | 3 +- .../codefresh-template-test.yaml | 3 +- deployment/codefresh-test.yaml | 467 +----------------- docs/model/ApplicationHarnessConfig.md | 1 + libraries/models/api/openapi.yaml | 3 + .../models/application_harness_config.py | 6 +- .../models/docs/ApplicationHarnessConfig.md | 1 + libraries/models/pyproject.toml | 2 +- libraries/models/test-requirements.txt | 1 - .../ch_cli_tools/codefresh.py | 19 + .../deployment-cli-tools/ch_cli_tools/helm.py | 51 +- .../ch_cli_tools/utils.py | 2 + tools/deployment-cli-tools/harness-deployment | 21 +- .../myapp/deploy/values-imagename.yaml | 2 + .../tests/test_codefresh.py | 44 ++ tools/deployment-cli-tools/tests/test_helm.py | 58 +++ .../tests/test_skaffold.py | 4 +- 20 files changed, 214 insertions(+), 482 deletions(-) create mode 100644 tools/deployment-cli-tools/tests/resources/applications/myapp/deploy/values-imagename.yaml diff --git a/applications/samples/deploy/values.yaml b/applications/samples/deploy/values.yaml index ed04be7c7..607c99445 100644 --- a/applications/samples/deploy/values.yaml +++ b/applications/samples/deploy/values.yaml @@ -1,5 +1,6 @@ harness: subdomain: samples + image_name: sampleapp secured: true sentry: true port: 80 @@ -50,6 +51,7 @@ harness: - workflows - events - accounts + - cloudharness-base - common build: - cloudharness-flask diff --git a/deployment-configuration/codefresh-template-dev.yaml b/deployment-configuration/codefresh-template-dev.yaml index 511602c58..78460d49e 100644 --- a/deployment-configuration/codefresh-template-dev.yaml +++ b/deployment-configuration/codefresh-template-dev.yaml @@ -34,7 +34,8 @@ steps: working_directory: . commands: - bash cloud-harness/install.sh - - harness-deployment $PATHS -d ${{DOMAIN}} -r ${{REGISTRY}} -rs '${{REGISTRY_SECRET}}' -n ${{NAMESPACE}} --write-env -e $ENV --cache-url '${{IMAGE_CACHE_URL}}' $PARAMS + - export HELM_META_ARGS="$( [ -n "${{HARNESS_CHART_NAME}}" ] && printf -- "--name %s " "${{HARNESS_CHART_NAME}}"; [ -n "${{HARNESS_CHART_VERSION}}" ] && printf -- "--chart-version %s " "${{HARNESS_CHART_VERSION}}"; [ -n "${{HARNESS_APP_VERSION}}" ] && printf -- "--app-version %s" "${{HARNESS_APP_VERSION}}" )" + - harness-deployment $PATHS -d ${{DOMAIN}} -r ${{REGISTRY}} -rs '${{REGISTRY_SECRET}}' -n ${{NAMESPACE}} --write-env -e $ENV --cache-url '${{IMAGE_CACHE_URL}}' $HELM_META_ARGS $PARAMS - cat deployment/.env >> ${{CF_VOLUME_PATH}}/env_vars_to_export - cat ${{CF_VOLUME_PATH}}/env_vars_to_export prepare_deployment_view: diff --git a/deployment-configuration/codefresh-template-prod.yaml b/deployment-configuration/codefresh-template-prod.yaml index 9d75bfed5..5511635a6 100644 --- a/deployment-configuration/codefresh-template-prod.yaml +++ b/deployment-configuration/codefresh-template-prod.yaml @@ -30,7 +30,8 @@ steps: working_directory: . commands: - bash cloud-harness/install.sh - - harness-deployment $PATHS -t ${{DEPLOYMENT_TAG}} -d ${{DOMAIN}} -r ${{REGISTRY}} -rs '${{REGISTRY_SECRET}}' -n ${{NAMESPACE}} -e $ENV --no-cd $PARAMS + - export HELM_META_ARGS="$( [ -n "${{HARNESS_CHART_NAME}}" ] && printf -- "--name %s " "${{HARNESS_CHART_NAME}}"; [ -n "${{HARNESS_CHART_VERSION}}" ] && printf -- "--chart-version %s " "${{HARNESS_CHART_VERSION}}"; [ -n "${{HARNESS_APP_VERSION}}" ] && printf -- "--app-version %s" "${{HARNESS_APP_VERSION}}" )" + - harness-deployment $PATHS -t ${{DEPLOYMENT_TAG}} -d ${{DOMAIN}} -r ${{REGISTRY}} -rs '${{REGISTRY_SECRET}}' -n ${{NAMESPACE}} -e $ENV --no-cd $HELM_META_ARGS $PARAMS prepare_deployment_view: commands: - "helm template ./deployment/helm --debug -n ${{NAMESPACE}}" diff --git a/deployment-configuration/codefresh-template-stage.yaml b/deployment-configuration/codefresh-template-stage.yaml index dc474651f..82acaabf2 100644 --- a/deployment-configuration/codefresh-template-stage.yaml +++ b/deployment-configuration/codefresh-template-stage.yaml @@ -33,7 +33,8 @@ steps: working_directory: . commands: - bash cloud-harness/install.sh - - harness-deployment $PATHS -t ${{DEPLOYMENT_TAG}} -d ${{DOMAIN}} -r ${{REGISTRY}} -rs ${{REGISTRY_SECRET}} -n ${{NAMESPACE}} -e $ENV --no-cd $PARAMS + - export HELM_META_ARGS="$( [ -n "${{HARNESS_CHART_NAME}}" ] && printf -- "--name %s " "${{HARNESS_CHART_NAME}}"; [ -n "${{HARNESS_CHART_VERSION}}" ] && printf -- "--chart-version %s " "${{HARNESS_CHART_VERSION}}"; [ -n "${{HARNESS_APP_VERSION}}" ] && printf -- "--app-version %s" "${{HARNESS_APP_VERSION}}" )" + - harness-deployment $PATHS -t ${{DEPLOYMENT_TAG}} -d ${{DOMAIN}} -r ${{REGISTRY}} -rs ${{REGISTRY_SECRET}} -n ${{NAMESPACE}} -e $ENV --no-cd $HELM_META_ARGS $PARAMS prepare_deployment_view: commands: - "helm template ./deployment/helm --debug -n ${{NAMESPACE}}" diff --git a/deployment-configuration/codefresh-template-test.yaml b/deployment-configuration/codefresh-template-test.yaml index 30b732451..8048bf470 100644 --- a/deployment-configuration/codefresh-template-test.yaml +++ b/deployment-configuration/codefresh-template-test.yaml @@ -33,7 +33,8 @@ steps: working_directory: . commands: - bash cloud-harness/install.sh - - harness-deployment $PATHS -n test-${{NAMESPACE_BASENAME}} -d ${{DOMAIN}} -r ${{REGISTRY}} -rs ${{REGISTRY_SECRET}} -e $ENV --write-env --cache-url '${{IMAGE_CACHE_URL}}' -N $PARAMS + - export HELM_META_ARGS="$( [ -n "${{HARNESS_CHART_NAME}}" ] && printf -- "--name %s " "${{HARNESS_CHART_NAME}}"; [ -n "${{HARNESS_CHART_VERSION}}" ] && printf -- "--chart-version %s " "${{HARNESS_CHART_VERSION}}"; [ -n "${{HARNESS_APP_VERSION}}" ] && printf -- "--app-version %s" "${{HARNESS_APP_VERSION}}" )" + - harness-deployment $PATHS -n test-${{NAMESPACE_BASENAME}} -d ${{DOMAIN}} -r ${{REGISTRY}} -rs ${{REGISTRY_SECRET}} -e $ENV --write-env --cache-url '${{IMAGE_CACHE_URL}}' -N $HELM_META_ARGS $PARAMS - cat deployment/.env >> ${{CF_VOLUME_PATH}}/env_vars_to_export - cat ${{CF_VOLUME_PATH}}/env_vars_to_export prepare_deployment_view: diff --git a/deployment/codefresh-test.yaml b/deployment/codefresh-test.yaml index 6840fe44b..458c33594 100644 --- a/deployment/codefresh-test.yaml +++ b/deployment/codefresh-test.yaml @@ -33,9 +33,13 @@ steps: working_directory: . commands: - bash cloud-harness/install.sh + - export HELM_META_ARGS="$( [ -n "${{HARNESS_CHART_NAME}}" ] && printf -- "--name + %s " "${{HARNESS_CHART_NAME}}"; [ -n "${{HARNESS_CHART_VERSION}}" ] && printf + -- "--chart-version %s " "${{HARNESS_CHART_VERSION}}"; [ -n "${{HARNESS_APP_VERSION}}" + ] && printf -- "--app-version %s" "${{HARNESS_APP_VERSION}}" )" - harness-deployment . -n test-${{NAMESPACE_BASENAME}} -d ${{DOMAIN}} -r ${{REGISTRY}} -rs ${{REGISTRY_SECRET}} -e test --write-env --cache-url '${{IMAGE_CACHE_URL}}' - -N -i samples + -N $HELM_META_ARGS -i accounts - cat deployment/.env >> ${{CF_VOLUME_PATH}}/env_vars_to_export - cat ${{CF_VOLUME_PATH}}/env_vars_to_export prepare_deployment_view: @@ -74,215 +78,6 @@ steps: == true forceNoCache: includes('${{TEST_E2E_TAG_FORCE_BUILD}}', '{{TEST_E2E_TAG_FORCE_BUILD}}') == false - cloudharness-frontend-build: - type: build - stage: build - dockerfile: infrastructure/base-images/cloudharness-frontend-build/Dockerfile - registry: '${{CODEFRESH_REGISTRY}}' - buildkit: true - build_arguments: - - NOCACHE=${{CF_BUILD_ID}} - image_name: cloud-harness/cloudharness-frontend-build - title: Cloudharness frontend build - working_directory: ./. - tags: - - '${{CLOUDHARNESS_FRONTEND_BUILD_TAG}}' - - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' - - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' - when: - condition: - any: - buildDoesNotExist: includes('${{CLOUDHARNESS_FRONTEND_BUILD_TAG_EXISTS}}', - '{{CLOUDHARNESS_FRONTEND_BUILD_TAG_EXISTS}}') == true - forceNoCache: includes('${{CLOUDHARNESS_FRONTEND_BUILD_TAG_FORCE_BUILD}}', - '{{CLOUDHARNESS_FRONTEND_BUILD_TAG_FORCE_BUILD}}') == false - cloudharness-base: - type: build - stage: build - dockerfile: infrastructure/base-images/cloudharness-base/Dockerfile - registry: '${{CODEFRESH_REGISTRY}}' - buildkit: true - build_arguments: - - NOCACHE=${{CF_BUILD_ID}} - image_name: cloud-harness/cloudharness-base - title: Cloudharness base - working_directory: ./. - tags: - - '${{CLOUDHARNESS_BASE_TAG}}' - - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' - - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' - when: - condition: - any: - buildDoesNotExist: includes('${{CLOUDHARNESS_BASE_TAG_EXISTS}}', '{{CLOUDHARNESS_BASE_TAG_EXISTS}}') - == true - forceNoCache: includes('${{CLOUDHARNESS_BASE_TAG_FORCE_BUILD}}', '{{CLOUDHARNESS_BASE_TAG_FORCE_BUILD}}') - == false - accounts: - type: build - stage: build - dockerfile: Dockerfile - registry: '${{CODEFRESH_REGISTRY}}' - buildkit: true - build_arguments: - - NOCACHE=${{CF_BUILD_ID}} - image_name: cloud-harness/accounts - title: Accounts - working_directory: ./applications/accounts - tags: - - '${{ACCOUNTS_TAG}}' - - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' - - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' - when: - condition: - any: - buildDoesNotExist: includes('${{ACCOUNTS_TAG_EXISTS}}', '{{ACCOUNTS_TAG_EXISTS}}') - == true - forceNoCache: includes('${{ACCOUNTS_TAG_FORCE_BUILD}}', '{{ACCOUNTS_TAG_FORCE_BUILD}}') - == false - title: Build parallel step 1 - build_application_images_1: - type: parallel - stage: build - steps: - cloudharness-flask: - type: build - stage: build - dockerfile: Dockerfile - registry: '${{CODEFRESH_REGISTRY}}' - buildkit: true - build_arguments: - - NOCACHE=${{CF_BUILD_ID}} - - CLOUDHARNESS_BASE=${{REGISTRY}}/cloud-harness/cloudharness-base:${{CLOUDHARNESS_BASE_TAG}} - image_name: cloud-harness/cloudharness-flask - title: Cloudharness flask - working_directory: ./infrastructure/common-images/cloudharness-flask - tags: - - '${{CLOUDHARNESS_FLASK_TAG}}' - - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' - - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' - when: - condition: - any: - buildDoesNotExist: includes('${{CLOUDHARNESS_FLASK_TAG_EXISTS}}', '{{CLOUDHARNESS_FLASK_TAG_EXISTS}}') - == true - forceNoCache: includes('${{CLOUDHARNESS_FLASK_TAG_FORCE_BUILD}}', '{{CLOUDHARNESS_FLASK_TAG_FORCE_BUILD}}') - == false - jupyterhub: - type: build - stage: build - dockerfile: Dockerfile - registry: '${{CODEFRESH_REGISTRY}}' - buildkit: true - build_arguments: - - NOCACHE=${{CF_BUILD_ID}} - - CLOUDHARNESS_BASE=${{REGISTRY}}/cloud-harness/cloudharness-base:${{CLOUDHARNESS_BASE_TAG}} - image_name: cloud-harness/jupyterhub - title: Jupyterhub - working_directory: ./applications/jupyterhub - tags: - - '${{JUPYTERHUB_TAG}}' - - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' - - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' - when: - condition: - any: - buildDoesNotExist: includes('${{JUPYTERHUB_TAG_EXISTS}}', '{{JUPYTERHUB_TAG_EXISTS}}') - == true - forceNoCache: includes('${{JUPYTERHUB_TAG_FORCE_BUILD}}', '{{JUPYTERHUB_TAG_FORCE_BUILD}}') - == false - workflows-extract-download: - type: build - stage: build - dockerfile: Dockerfile - registry: '${{CODEFRESH_REGISTRY}}' - buildkit: true - build_arguments: - - NOCACHE=${{CF_BUILD_ID}} - - CLOUDHARNESS_BASE=${{REGISTRY}}/cloud-harness/cloudharness-base:${{CLOUDHARNESS_BASE_TAG}} - image_name: cloud-harness/workflows-extract-download - title: Workflows extract download - working_directory: ./applications/workflows/tasks/extract-download - tags: - - '${{WORKFLOWS_EXTRACT_DOWNLOAD_TAG}}' - - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' - - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' - when: - condition: - any: - buildDoesNotExist: includes('${{WORKFLOWS_EXTRACT_DOWNLOAD_TAG_EXISTS}}', - '{{WORKFLOWS_EXTRACT_DOWNLOAD_TAG_EXISTS}}') == true - forceNoCache: includes('${{WORKFLOWS_EXTRACT_DOWNLOAD_TAG_FORCE_BUILD}}', - '{{WORKFLOWS_EXTRACT_DOWNLOAD_TAG_FORCE_BUILD}}') == false - samples-secret: - type: build - stage: build - dockerfile: Dockerfile - registry: '${{CODEFRESH_REGISTRY}}' - buildkit: true - build_arguments: - - NOCACHE=${{CF_BUILD_ID}} - - CLOUDHARNESS_BASE=${{REGISTRY}}/cloud-harness/cloudharness-base:${{CLOUDHARNESS_BASE_TAG}} - image_name: cloud-harness/samples-secret - title: Samples secret - working_directory: ./applications/samples/tasks/secret - tags: - - '${{SAMPLES_SECRET_TAG}}' - - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' - - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' - when: - condition: - any: - buildDoesNotExist: includes('${{SAMPLES_SECRET_TAG_EXISTS}}', '{{SAMPLES_SECRET_TAG_EXISTS}}') - == true - forceNoCache: includes('${{SAMPLES_SECRET_TAG_FORCE_BUILD}}', '{{SAMPLES_SECRET_TAG_FORCE_BUILD}}') - == false - samples-print-file: - type: build - stage: build - dockerfile: Dockerfile - registry: '${{CODEFRESH_REGISTRY}}' - buildkit: true - build_arguments: - - NOCACHE=${{CF_BUILD_ID}} - - CLOUDHARNESS_BASE=${{REGISTRY}}/cloud-harness/cloudharness-base:${{CLOUDHARNESS_BASE_TAG}} - image_name: cloud-harness/samples-print-file - title: Samples print file - working_directory: ./applications/samples/tasks/print-file - tags: - - '${{SAMPLES_PRINT_FILE_TAG}}' - - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' - - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' - when: - condition: - any: - buildDoesNotExist: includes('${{SAMPLES_PRINT_FILE_TAG_EXISTS}}', '{{SAMPLES_PRINT_FILE_TAG_EXISTS}}') - == true - forceNoCache: includes('${{SAMPLES_PRINT_FILE_TAG_FORCE_BUILD}}', '{{SAMPLES_PRINT_FILE_TAG_FORCE_BUILD}}') - == false - workflows-send-result-event: - type: build - stage: build - dockerfile: Dockerfile - registry: '${{CODEFRESH_REGISTRY}}' - buildkit: true - build_arguments: - - NOCACHE=${{CF_BUILD_ID}} - - CLOUDHARNESS_BASE=${{REGISTRY}}/cloud-harness/cloudharness-base:${{CLOUDHARNESS_BASE_TAG}} - image_name: cloud-harness/workflows-send-result-event - title: Workflows send result event - working_directory: ./applications/workflows/tasks/send-result-event - tags: - - '${{WORKFLOWS_SEND_RESULT_EVENT_TAG}}' - - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' - - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' - when: - condition: - any: - buildDoesNotExist: includes('${{WORKFLOWS_SEND_RESULT_EVENT_TAG_EXISTS}}', - '{{WORKFLOWS_SEND_RESULT_EVENT_TAG_EXISTS}}') == true - forceNoCache: includes('${{WORKFLOWS_SEND_RESULT_EVENT_TAG_FORCE_BUILD}}', - '{{WORKFLOWS_SEND_RESULT_EVENT_TAG_FORCE_BUILD}}') == false test-api: type: build stage: build @@ -307,165 +102,7 @@ steps: == true forceNoCache: includes('${{TEST_API_TAG_FORCE_BUILD}}', '{{TEST_API_TAG_FORCE_BUILD}}') == false - workflows-notify-queue: - type: build - stage: build - dockerfile: Dockerfile - registry: '${{CODEFRESH_REGISTRY}}' - buildkit: true - build_arguments: - - NOCACHE=${{CF_BUILD_ID}} - - CLOUDHARNESS_BASE=${{REGISTRY}}/cloud-harness/cloudharness-base:${{CLOUDHARNESS_BASE_TAG}} - image_name: cloud-harness/workflows-notify-queue - title: Workflows notify queue - working_directory: ./applications/workflows/tasks/notify-queue - tags: - - '${{WORKFLOWS_NOTIFY_QUEUE_TAG}}' - - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' - - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' - when: - condition: - any: - buildDoesNotExist: includes('${{WORKFLOWS_NOTIFY_QUEUE_TAG_EXISTS}}', - '{{WORKFLOWS_NOTIFY_QUEUE_TAG_EXISTS}}') == true - forceNoCache: includes('${{WORKFLOWS_NOTIFY_QUEUE_TAG_FORCE_BUILD}}', - '{{WORKFLOWS_NOTIFY_QUEUE_TAG_FORCE_BUILD}}') == false - title: Build parallel step 2 - build_application_images_2: - type: parallel - stage: build - steps: - common: - type: build - stage: build - dockerfile: Dockerfile - registry: '${{CODEFRESH_REGISTRY}}' - buildkit: true - build_arguments: - - NOCACHE=${{CF_BUILD_ID}} - - CLOUDHARNESS_FLASK=${{REGISTRY}}/cloud-harness/cloudharness-flask:${{CLOUDHARNESS_FLASK_TAG}} - image_name: cloud-harness/common - title: Common - working_directory: ./applications/common/server - tags: - - '${{COMMON_TAG}}' - - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' - - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' - when: - condition: - any: - buildDoesNotExist: includes('${{COMMON_TAG_EXISTS}}', '{{COMMON_TAG_EXISTS}}') - == true - forceNoCache: includes('${{COMMON_TAG_FORCE_BUILD}}', '{{COMMON_TAG_FORCE_BUILD}}') - == false - volumemanager: - type: build - stage: build - dockerfile: Dockerfile - registry: '${{CODEFRESH_REGISTRY}}' - buildkit: true - build_arguments: - - NOCACHE=${{CF_BUILD_ID}} - - CLOUDHARNESS_FLASK=${{REGISTRY}}/cloud-harness/cloudharness-flask:${{CLOUDHARNESS_FLASK_TAG}} - image_name: cloud-harness/volumemanager - title: Volumemanager - working_directory: ./applications/volumemanager/server - tags: - - '${{VOLUMEMANAGER_TAG}}' - - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' - - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' - when: - condition: - any: - buildDoesNotExist: includes('${{VOLUMEMANAGER_TAG_EXISTS}}', '{{VOLUMEMANAGER_TAG_EXISTS}}') - == true - forceNoCache: includes('${{VOLUMEMANAGER_TAG_FORCE_BUILD}}', '{{VOLUMEMANAGER_TAG_FORCE_BUILD}}') - == false - workflows: - type: build - stage: build - dockerfile: Dockerfile - registry: '${{CODEFRESH_REGISTRY}}' - buildkit: true - build_arguments: - - NOCACHE=${{CF_BUILD_ID}} - - CLOUDHARNESS_FLASK=${{REGISTRY}}/cloud-harness/cloudharness-flask:${{CLOUDHARNESS_FLASK_TAG}} - image_name: cloud-harness/workflows - title: Workflows - working_directory: ./applications/workflows/server - tags: - - '${{WORKFLOWS_TAG}}' - - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' - - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' - when: - condition: - any: - buildDoesNotExist: includes('${{WORKFLOWS_TAG_EXISTS}}', '{{WORKFLOWS_TAG_EXISTS}}') - == true - forceNoCache: includes('${{WORKFLOWS_TAG_FORCE_BUILD}}', '{{WORKFLOWS_TAG_FORCE_BUILD}}') - == false - samples: - type: build - stage: build - dockerfile: Dockerfile - registry: '${{CODEFRESH_REGISTRY}}' - buildkit: true - build_arguments: - - NOCACHE=${{CF_BUILD_ID}} - - CLOUDHARNESS_FRONTEND_BUILD=${{REGISTRY}}/cloud-harness/cloudharness-frontend-build:${{CLOUDHARNESS_FRONTEND_BUILD_TAG}} - - CLOUDHARNESS_FLASK=${{REGISTRY}}/cloud-harness/cloudharness-flask:${{CLOUDHARNESS_FLASK_TAG}} - image_name: cloud-harness/samples - title: Samples - working_directory: ./applications/samples - tags: - - '${{SAMPLES_TAG}}' - - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' - - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' - when: - condition: - any: - buildDoesNotExist: includes('${{SAMPLES_TAG_EXISTS}}', '{{SAMPLES_TAG_EXISTS}}') - == true - forceNoCache: includes('${{SAMPLES_TAG_FORCE_BUILD}}', '{{SAMPLES_TAG_FORCE_BUILD}}') - == false - title: Build parallel step 3 - build_application_images_3: - type: parallel - stage: build - steps: - samples-sum: - type: build - stage: build - dockerfile: Dockerfile - registry: '${{CODEFRESH_REGISTRY}}' - buildkit: true - build_arguments: - - NOCACHE=${{CF_BUILD_ID}} - - SAMPLES=${{REGISTRY}}/cloud-harness/samples:${{SAMPLES_TAG}} - image_name: cloud-harness/samples-sum - title: Samples sum - working_directory: ./applications/samples/tasks/sum - tags: - - '${{SAMPLES_SUM_TAG}}' - - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' - - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' - when: - condition: - any: - buildDoesNotExist: includes('${{SAMPLES_SUM_TAG_EXISTS}}', '{{SAMPLES_SUM_TAG_EXISTS}}') - == true - forceNoCache: includes('${{SAMPLES_SUM_TAG_FORCE_BUILD}}', '{{SAMPLES_SUM_TAG_FORCE_BUILD}}') - == false - title: Build parallel step 4 - tests_unit: - stage: unittest - type: parallel - steps: - samples_ut: - title: Unit tests for samples - commands: - - pytest /usr/src/app/samples/test - image: '${{REGISTRY}}/cloud-harness/samples:${{SAMPLES_TAG}}' + title: Build parallel step 1 deployment: stage: deploy type: helm @@ -481,8 +118,7 @@ steps: cmd_ps: --timeout 600s --create-namespace custom_value_files: - ./deployment/helm/values.yaml - custom_values: - - apps_samples_harness_secrets_asecret=${{ASECRET}} + custom_values: [] wait_deployment: stage: qa title: Wait deployment to be ready @@ -491,98 +127,11 @@ steps: - kubectl config use-context ${{CLUSTER_NAME}} - kubectl config set-context --current --namespace=test-${{NAMESPACE_BASENAME}} - kubectl rollout status deployment/accounts - - kubectl rollout status deployment/common - - kubectl rollout status deployment/volumemanager - - kubectl rollout status deployment/argo-gk - - kubectl rollout status deployment/workflows - - kubectl rollout status deployment/samples - - kubectl rollout status deployment/samples-gk - sleep 60 - tests_api: - stage: qa - title: Api tests - working_directory: /home/test - image: '${{REGISTRY}}/cloud-harness/test-api:${{TEST_API_TAG}}' - fail_fast: false - commands: - - echo $APP_NAME - scale: - samples_api_test: - title: samples api test - volumes: - - '${{CF_REPO_NAME}}/applications/samples:/home/test' - - '${{CF_REPO_NAME}}/deployment/helm/values.yaml:/opt/cloudharness/resources/allvalues.yaml' - environment: - - APP_URL=https://samples.${{DOMAIN}}/api - - USERNAME=sample@testuser.com - - PASSWORD=test - commands: - - st --pre-run cloudharness_test.apitest_init run api/openapi.yaml --base-url - https://samples.${{DOMAIN}}/api -c all --skip-deprecated-operations --exclude-operation-id=submit_sync - --exclude-operation-id=submit_sync_with_results --exclude-operation-id=error - --hypothesis-suppress-health-check=too_slow --hypothesis-deadline=180000 - --request-timeout=180000 --hypothesis-max-examples=2 --show-trace --exclude-checks=ignored_auth - - pytest -v test/api - common_api_test: - title: common api test - volumes: - - '${{CF_REPO_NAME}}/applications/common:/home/test' - - '${{CF_REPO_NAME}}/deployment/helm/values.yaml:/opt/cloudharness/resources/allvalues.yaml' - environment: - - APP_URL=https://common.${{DOMAIN}}/api - commands: - - st --pre-run cloudharness_test.apitest_init run api/openapi.yaml --base-url - https://common.${{DOMAIN}}/api -c all - workflows_api_test: - title: workflows api test - volumes: - - '${{CF_REPO_NAME}}/applications/workflows:/home/test' - - '${{CF_REPO_NAME}}/deployment/helm/values.yaml:/opt/cloudharness/resources/allvalues.yaml' - environment: - - APP_URL=https://workflows.${{DOMAIN}}/api - commands: - - st --pre-run cloudharness_test.apitest_init run api/openapi.yaml --base-url - https://workflows.${{DOMAIN}}/api -c all - hooks: - on_fail: - exec: - image: alpine - commands: - - cf_export FAILED=failed - tests_e2e: - stage: qa - title: End to end tests - working_directory: /home/test - image: '${{REGISTRY}}/cloud-harness/test-e2e:${{TEST_E2E_TAG}}' - fail_fast: false - commands: - - npx puppeteer browsers install chrome - - yarn test - scale: - jupyterhub_e2e_test: - title: jupyterhub e2e test - volumes: - - '${{CF_REPO_NAME}}/applications/jupyterhub/test/e2e:/home/test/__tests__/jupyterhub' - environment: - - APP_URL=https://hub.${{DOMAIN}} - samples_e2e_test: - title: samples e2e test - volumes: - - '${{CF_REPO_NAME}}/applications/samples/test/e2e:/home/test/__tests__/samples' - environment: - - APP_URL=https://samples.${{DOMAIN}} - - USERNAME=sample@testuser.com - - PASSWORD=test - hooks: - on_fail: - exec: - image: alpine - commands: - - cf_export FAILED=failed approval: type: pending-approval stage: qa - title: Approve anyway + title: Approve anyway and delete deployment description: The pipeline will fail after ${{WAIT_ON_FAIL}} minutes timeout: timeUnit: minutes diff --git a/docs/model/ApplicationHarnessConfig.md b/docs/model/ApplicationHarnessConfig.md index eb4795303..a589c11ab 100644 --- a/docs/model/ApplicationHarnessConfig.md +++ b/docs/model/ApplicationHarnessConfig.md @@ -32,6 +32,7 @@ Name | Type | Description | Notes **dockerfile** | [**DockerfileConfig**](DockerfileConfig.md) | | [optional] **sentry** | **bool** | | [optional] **proxy** | [**ProxyConf**](ProxyConf.md) | | [optional] +**image_name** | **str** | Use this name for the image in place of the default directory name | [optional] ## Example diff --git a/libraries/models/api/openapi.yaml b/libraries/models/api/openapi.yaml index a378751c1..eb5546103 100644 --- a/libraries/models/api/openapi.yaml +++ b/libraries/models/api/openapi.yaml @@ -1020,4 +1020,7 @@ components: proxy: $ref: '#/components/schemas/ProxyConf' description: '' + image_name: + description: Use this name for the image in place of the default directory name + type: string additionalProperties: true diff --git a/libraries/models/cloudharness_model/models/application_harness_config.py b/libraries/models/cloudharness_model/models/application_harness_config.py index 5fd4473f2..f4a74d88f 100644 --- a/libraries/models/cloudharness_model/models/application_harness_config.py +++ b/libraries/models/cloudharness_model/models/application_harness_config.py @@ -70,8 +70,9 @@ class ApplicationHarnessConfig(CloudHarnessBaseModel): dockerfile: Optional[DockerfileConfig] = None sentry: Optional[StrictBool] = None proxy: Optional[ProxyConf] = None + image_name: Optional[StrictStr] = Field(default=None, description="Use this name for the image in place of the default directory name") additional_properties: Dict[str, Any] = {} - __properties: ClassVar[List[str]] = ["deployment", "service", "subdomain", "aliases", "domain", "dependencies", "secured", "uri_role_mapping", "secrets", "use_services", "database", "resources", "readinessProbe", "startupProbe", "livenessProbe", "sourceRoot", "name", "jupyterhub", "accounts", "test", "quotas", "env", "envmap", "dockerfile", "sentry", "proxy"] + __properties: ClassVar[List[str]] = ["deployment", "service", "subdomain", "aliases", "domain", "dependencies", "secured", "uri_role_mapping", "secrets", "use_services", "database", "resources", "readinessProbe", "startupProbe", "livenessProbe", "sourceRoot", "name", "jupyterhub", "accounts", "test", "quotas", "env", "envmap", "dockerfile", "sentry", "proxy", "image_name"] @field_validator('source_root') def source_root_validate_regular_expression(cls, value): @@ -214,7 +215,8 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: "envmap": obj.get("envmap"), "dockerfile": DockerfileConfig.from_dict(obj["dockerfile"]) if obj.get("dockerfile") is not None else None, "sentry": obj.get("sentry"), - "proxy": ProxyConf.from_dict(obj["proxy"]) if obj.get("proxy") is not None else None + "proxy": ProxyConf.from_dict(obj["proxy"]) if obj.get("proxy") is not None else None, + "image_name": obj.get("image_name") }) # store additional fields in additional_properties for _key in obj.keys(): diff --git a/libraries/models/docs/ApplicationHarnessConfig.md b/libraries/models/docs/ApplicationHarnessConfig.md index eb4795303..a589c11ab 100644 --- a/libraries/models/docs/ApplicationHarnessConfig.md +++ b/libraries/models/docs/ApplicationHarnessConfig.md @@ -32,6 +32,7 @@ Name | Type | Description | Notes **dockerfile** | [**DockerfileConfig**](DockerfileConfig.md) | | [optional] **sentry** | **bool** | | [optional] **proxy** | [**ProxyConf**](ProxyConf.md) | | [optional] +**image_name** | **str** | Use this name for the image in place of the default directory name | [optional] ## Example diff --git a/libraries/models/pyproject.toml b/libraries/models/pyproject.toml index 91bcda0f4..a5a8a7122 100644 --- a/libraries/models/pyproject.toml +++ b/libraries/models/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "cloudharness_model" -version = "3.0.0" +version = "1.0.0" description = "cloudharness" authors = ["OpenAPI Generator Community "] license = "NoLicense" diff --git a/libraries/models/test-requirements.txt b/libraries/models/test-requirements.txt index 5bc5ac9c9..8e6d8cb13 100644 --- a/libraries/models/test-requirements.txt +++ b/libraries/models/test-requirements.txt @@ -3,4 +3,3 @@ pytest-cov>=2.8.1 pytest-randomly>=3.12.0 mypy>=1.4.1 types-python-dateutil>=2.8.19 -oyaml \ No newline at end of file diff --git a/tools/deployment-cli-tools/ch_cli_tools/codefresh.py b/tools/deployment-cli-tools/ch_cli_tools/codefresh.py index f544da710..87cf8dc95 100644 --- a/tools/deployment-cli-tools/ch_cli_tools/codefresh.py +++ b/tools/deployment-cli-tools/ch_cli_tools/codefresh.py @@ -482,6 +482,9 @@ def codefresh_app_publish_spec(app_name, build_tag, base_name=None): app_name, base_name), build_tag or '${{DEPLOYMENT_TAG}}'), title=title, ) + step_spec["when"] = existing_publish_when_condition( + app_specific_publish_skip_variable(app_name) + ) step_spec['tags'].append('latest') return step_spec @@ -495,6 +498,10 @@ def app_specific_tag_variable(app_name): return "%s_TAG" % app_name.replace('-', '_').upper().strip() +def app_specific_publish_skip_variable(app_name): + return "%s_PUBLISH_SKIP" % app_name.replace('-', '_').upper().strip() + + 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 @@ -513,3 +520,15 @@ def existing_build_when_condition(tag): } return when_condition + + +def existing_publish_when_condition(skip_publish_variable): + return { + "condition": { + "all": { + "skipPublish": "includes('${{%s}}', '{{%s}}') == false" % ( + skip_publish_variable, skip_publish_variable + ), + } + } + } diff --git a/tools/deployment-cli-tools/ch_cli_tools/helm.py b/tools/deployment-cli-tools/ch_cli_tools/helm.py index 8e4f90ba9..b3ffdf41d 100644 --- a/tools/deployment-cli-tools/ch_cli_tools/helm.py +++ b/tools/deployment-cli-tools/ch_cli_tools/helm.py @@ -9,7 +9,7 @@ import subprocess from cloudharness_utils.constants import VALUES_MANUAL_PATH, HELM_CHART_PATH -from .utils import get_cluster_ip, get_git_commit_hash, image_name_from_dockerfile_path, \ +from .utils import get_cluster_ip, get_git_commit_hash, get_image_name, image_name_from_dockerfile_path, \ get_template, merge_to_yaml_file, dict_merge, app_name_from_path, \ find_dockerfiles_paths @@ -31,16 +31,46 @@ def deploy(namespace, output_path='./deployment'): def create_helm_chart(root_paths, tag: Union[str, int, None] = 'latest', registry='', local=True, domain=None, exclude=(), secured=True, output_path='./deployment', include=None, registry_secret=None, tls=True, env=None, - namespace=None) -> HarnessMainConfig: + namespace=None, name=None, chart_version=None, app_version=None) -> HarnessMainConfig: if (type(env)) == str: env = [env] return CloudHarnessHelm(root_paths, tag=tag, registry=registry, local=local, domain=domain, exclude=exclude, secured=secured, output_path=output_path, include=include, registry_secret=registry_secret, tls=tls, env=env, - namespace=namespace).process_values() + namespace=namespace, name=name, chart_version=chart_version, + app_version=app_version).process_values() class CloudHarnessHelm(ConfigurationGenerator): + def __init__(self, root_paths, tag: Union[str, int, None] = 'latest', registry='', local=True, domain=None, exclude=(), secured=True, + output_path='./deployment', include=None, registry_secret=None, tls=True, env=None, + namespace=None, name=None, chart_version=None, app_version=None): + super().__init__(root_paths, tag=tag, registry=registry, local=local, domain=domain, exclude=exclude, secured=secured, + output_path=output_path, include=include, registry_secret=registry_secret, tls=tls, env=env, + namespace=namespace) + self.chart_name = name + self.chart_version = chart_version + self.app_version = app_version + + def _merge_chart_metadata(self, values_name=None): + metadata = {} + + resolved_name = self.chart_name or self.namespace or values_name + if resolved_name: + metadata['name'] = resolved_name + + if self.namespace: + metadata['metadata'] = {'namespace': self.namespace} + + if self.chart_version: + metadata['version'] = self.chart_version + + if self.app_version: + metadata['appVersion'] = self.app_version + + if metadata: + merge_to_yaml_file(metadata, self.helm_chart_path) + def process_values(self) -> HarnessMainConfig: """ Creates values file for the helm chart @@ -77,9 +107,7 @@ def process_values(self) -> HarnessMainConfig: # Save values file for manual helm chart merged_values = merge_to_yaml_file(helm_values, os.path.join( self.dest_deployment_path, VALUES_MANUAL_PATH)) - if self.namespace: - merge_to_yaml_file({'metadata': {'namespace': self.namespace}, - 'name': helm_values['name']}, self.helm_chart_path) + self._merge_chart_metadata(helm_values['name']) validate_helm_values(merged_values) return HarnessMainConfig.from_dict(merged_values) @@ -223,15 +251,18 @@ def create_app_values_spec(self, app_name, app_path, base_image_name=None, helm_ deployment_values = values.get(KEY_HARNESS, {}).get(KEY_DEPLOYMENT, {}) deployment_image = deployment_values.get('image', None) or values.get('image', None) values['build'] = not bool(deployment_image) # Used by skaffold and ci/cd to determine if the image should be built + + image_name = get_image_name(values.get(KEY_HARNESS, {}).get('image_name', None) , base_image_name) if len(image_paths) > 0 and not deployment_image: - image_name = image_name_from_dockerfile_path(os.path.relpath( - image_paths[0], os.path.dirname(app_path)), base_image_name) + + image_name = image_name or 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 deployment_image and values[ KEY_HARNESS].get(KEY_DEPLOYMENT, {}).get('auto', False): raise Exception(f"At least one Dockerfile must be specified on application {app_name}. " f"Specify harness.deployment.image value if you intend to use a prebuilt image.") + task_images_paths = [path for path in find_dockerfiles_paths( app_path) if 'tasks/' in path] @@ -245,9 +276,9 @@ def create_app_values_spec(self, app_name, app_path, base_image_name=None, helm_ for task_path in task_images_paths: task_name = app_name_from_path(os.path.relpath( task_path, os.path.dirname(app_path))) - img_name = image_name_from_dockerfile_path(task_name, base_image_name) + task_img_name = "-".join([image_name, os.path.basename(task_path)] ) if image_name else image_name_from_dockerfile_path(task_path, base_image_name) values[KEY_TASK_IMAGES][task_name] = self.image_tag( - img_name, build_context_path=task_path, dependencies=values[KEY_TASK_IMAGES].keys()) + task_img_name, build_context_path=task_path, dependencies=values[KEY_TASK_IMAGES].keys()) return values diff --git a/tools/deployment-cli-tools/ch_cli_tools/utils.py b/tools/deployment-cli-tools/ch_cli_tools/utils.py index 4809998e5..9697c61d3 100644 --- a/tools/deployment-cli-tools/ch_cli_tools/utils.py +++ b/tools/deployment-cli-tools/ch_cli_tools/utils.py @@ -85,6 +85,8 @@ def get_parent_app_name(app_relative_path): def get_image_name(app_name, base_name=None): + if not app_name: + return None return (base_name + '/' + app_name) if base_name else app_name diff --git a/tools/deployment-cli-tools/harness-deployment b/tools/deployment-cli-tools/harness-deployment index 4a587afaa..19d714d08 100644 --- a/tools/deployment-cli-tools/harness-deployment +++ b/tools/deployment-cli-tools/harness-deployment @@ -36,6 +36,12 @@ if __name__ == "__main__": parser.add_argument('-n', '--namespace', dest='namespace', action="store", default=None, help='Specify the namespace of the deployment (default taken from values.yaml)') + parser.add_argument('--name', dest='name', action="store", default=None, + help='Specify the helm chart name (default: namespace when provided, otherwise chart default)') + parser.add_argument('--chart-version', dest='chart_version', action="store", default=None, + help='Specify the helm chart version') + parser.add_argument('--app-version', dest='app_version', action="store", default=None, + help='Specify the helm chart appVersion') parser.add_argument('-r', '--registry', dest='registry', action="store", default='', help='Specify image registry prefix') @@ -97,8 +103,8 @@ if __name__ == "__main__": chart_fn = create_helm_chart if not args.docker_compose else create_docker_compose_configuration - helm_values = chart_fn( - root_paths, + chart_args = dict( + root_paths=root_paths, tag=args.tag, registry=args.registry, domain=args.domain, @@ -110,9 +116,18 @@ if __name__ == "__main__": registry_secret=args.registry_secret, tls=not args.no_tls, env=envs, - namespace=args.namespace + namespace=args.namespace, ) + if not args.docker_compose: + chart_args.update(dict( + name=args.name, + chart_version=args.chart_version, + app_version=args.app_version, + )) + + helm_values = chart_fn(**chart_args) + merged_root_paths = preprocess_build_overrides( root_paths=root_paths, helm_values=helm_values) diff --git a/tools/deployment-cli-tools/tests/resources/applications/myapp/deploy/values-imagename.yaml b/tools/deployment-cli-tools/tests/resources/applications/myapp/deploy/values-imagename.yaml new file mode 100644 index 000000000..25be78aa6 --- /dev/null +++ b/tools/deployment-cli-tools/tests/resources/applications/myapp/deploy/values-imagename.yaml @@ -0,0 +1,2 @@ +harness: + image_name: custom-myapp \ 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 a558f7cc0..823e52e98 100644 --- a/tools/deployment-cli-tools/tests/test_codefresh.py +++ b/tools/deployment-cli-tools/tests/test_codefresh.py @@ -140,6 +140,9 @@ def test_create_codefresh_configuration(): 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_STEP_CLONE_DEPENDENCIES]['steps']) == 3, "3 clone steps should be included as we have 2 dependencies from myapp, plus cloudharness" + + publish_base_step = l1_steps[CD_STEP_PUBLISH]['steps']['publish_cloudharness-base'] + assert publish_base_step['when']['condition']['all']['skipPublish'] == "includes('${{CLOUDHARNESS_BASE_PUBLISH_SKIP}}', '{{CLOUDHARNESS_BASE_PUBLISH_SKIP}}') == false" finally: shutil.rmtree(BUILD_MERGE_DIR) @@ -339,3 +342,44 @@ def test_app_depends_on_app(): envs=[], base_image_name=values['name'], helm_values=values, save=False) + + +def test_prepare_deployment_includes_optional_helm_metadata_args(): + values = create_helm_chart( + [CLOUDHARNESS_ROOT, RESOURCES], + output_path=OUT, + include=['myapp'], + domain="my.local", + namespace='test', + env='dev', + local=False, + tag=1, + registry='reg' + ) + try: + root_paths = preprocess_build_overrides( + root_paths=[CLOUDHARNESS_ROOT, RESOURCES], + helm_values=values, + merge_build_path=BUILD_MERGE_DIR + ) + build_included = [app['harness']['name'] + for app in values['apps'].values() if 'harness' in app] + + cf = create_codefresh_deployment_scripts(root_paths, include=build_included, + envs=['dev'], + base_image_name=values['name'], + helm_values=values, save=False) + + commands = cf['steps']['prepare_deployment']['commands'] + export_cmd = next(c for c in commands if c.startswith('export HELM_META_ARGS=')) + run_cmd = next(c for c in commands if 'harness-deployment' in c) + + assert 'HARNESS_CHART_NAME' in export_cmd + assert 'HARNESS_CHART_VERSION' in export_cmd + assert 'HARNESS_APP_VERSION' in export_cmd + assert '--name %s' in export_cmd + assert '--chart-version %s' in export_cmd + assert '--app-version %s' in export_cmd + assert '$HELM_META_ARGS' in run_cmd + finally: + shutil.rmtree(BUILD_MERGE_DIR) diff --git a/tools/deployment-cli-tools/tests/test_helm.py b/tools/deployment-cli-tools/tests/test_helm.py index 2e4b93917..6ceaff199 100644 --- a/tools/deployment-cli-tools/tests/test_helm.py +++ b/tools/deployment-cli-tools/tests/test_helm.py @@ -94,6 +94,19 @@ def test_collect_nobuild(tmp_path): assert values[KEY_APPS]['myapp']['build'] == False +def test_collect_helm_values_harness_image_name_override(tmp_path): + out_folder = tmp_path / 'test_collect_helm_values_harness_image_name_override' + + + + values = create_helm_chart([CLOUDHARNESS_ROOT, RESOURCES], output_path=out_folder, include=['myapp'], + domain="my.local", namespace='test', env='imagename', local=False, tag=1, registry='reg') + + assert values[KEY_APPS]['myapp'][KEY_HARNESS]['deployment']['image'] == 'reg/testprojectname/custom-myapp:1' + assert values[KEY_APPS]['myapp'][KEY_TASK_IMAGES]['myapp-mytask'] == 'reg/testprojectname/custom-myapp-mytask:1' + + + def test_collect_helm_values_noreg_noinclude(tmp_path): out_path = tmp_path / 'test_collect_helm_values_noreg_noinclude' values = create_helm_chart([CLOUDHARNESS_ROOT, RESOURCES], output_path=out_path, domain="my.local", @@ -375,6 +388,51 @@ def create(): fname.unlink() +def test_chart_metadata_defaults_to_namespace_name(tmp_path): + out_folder = tmp_path / 'test_chart_metadata_defaults_to_namespace_name' + create_helm_chart( + [CLOUDHARNESS_ROOT, RESOURCES], + output_path=out_folder, + include=['myapp'], + domain="my.local", + namespace='custom-ns', + env='dev', + local=False, + tag=1, + registry='reg' + ) + + chart_path = out_folder / HELM_CHART_PATH / 'Chart.yaml' + chart = yaml.safe_load(open(chart_path, 'r')) + assert chart['name'] == 'custom-ns' + assert chart['metadata']['namespace'] == 'custom-ns' + + +def test_chart_metadata_optional_overrides(tmp_path): + out_folder = tmp_path / 'test_chart_metadata_optional_overrides' + create_helm_chart( + [CLOUDHARNESS_ROOT, RESOURCES], + output_path=out_folder, + include=['myapp'], + domain="my.local", + namespace='custom-ns', + name='custom-chart', + chart_version='9.8.7', + app_version='4.5.6', + env='dev', + local=False, + tag=1, + registry='reg' + ) + + chart_path = out_folder / HELM_CHART_PATH / 'Chart.yaml' + chart = yaml.safe_load(open(chart_path, 'r')) + assert chart['name'] == 'custom-chart' + assert chart['version'] == '9.8.7' + assert chart['appVersion'] == '4.5.6' + assert chart['metadata']['namespace'] == 'custom-ns' + + def test_exclude_single_task(tmp_path): out_folder = tmp_path / 'test_exclude_single_task' diff --git a/tools/deployment-cli-tools/tests/test_skaffold.py b/tools/deployment-cli-tools/tests/test_skaffold.py index a52de3489..cf7dd06b8 100644 --- a/tools/deployment-cli-tools/tests/test_skaffold.py +++ b/tools/deployment-cli-tools/tests/test_skaffold.py @@ -76,7 +76,7 @@ def test_create_skaffold_configuration(tmp_path): assert len(cloudharness_flask_artifact['requires']) == 1 samples_artifact = next( - a for a in sk['build']['artifacts'] if a['image'] == f'reg/testprojectname/samples' + a for a in sk['build']['artifacts'] if a['image'] == f'reg/testprojectname/sample' ) assert os.path.samefile(samples_artifact['context'], join(CLOUDHARNESS_ROOT, 'applications/samples')) assert 'TEST_ARGUMENT' in samples_artifact['docker']['buildArgs'] @@ -96,7 +96,7 @@ def test_create_skaffold_configuration(tmp_path): assert len(sk['test']) == 2, 'Unit tests should be included' samples_test = sk['test'][0] - assert samples_test['image'] == f'reg/testprojectname/samples', 'Unit tests for samples should be included' + assert samples_test['image'] == f'reg/testprojectname/sample', 'Unit tests for samples should be included' assert "samples/test" in samples_test['custom'][0]['command'], "The test command must come from values.yaml test/unit/commands" assert len(sk['test'][1]['custom']) == 2 From 40b2a3a51eb6cddf0cc8bf5787f8cfebe25372d2 Mon Sep 17 00:00:00 2001 From: Filippo Ledda Date: Fri, 20 Feb 2026 18:22:30 +0100 Subject: [PATCH 06/12] CH-239 test fix --- tools/deployment-cli-tools/tests/test_skaffold.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/deployment-cli-tools/tests/test_skaffold.py b/tools/deployment-cli-tools/tests/test_skaffold.py index cf7dd06b8..5898e37ea 100644 --- a/tools/deployment-cli-tools/tests/test_skaffold.py +++ b/tools/deployment-cli-tools/tests/test_skaffold.py @@ -75,8 +75,10 @@ def test_create_skaffold_configuration(tmp_path): assert len(cloudharness_flask_artifact['requires']) == 1 + expected_samples_image = values[KEY_APPS]['samples'][KEY_HARNESS][KEY_DEPLOYMENT]['image'].split(':')[0] + samples_artifact = next( - a for a in sk['build']['artifacts'] if a['image'] == f'reg/testprojectname/sample' + a for a in sk['build']['artifacts'] if a['image'] == expected_samples_image ) assert os.path.samefile(samples_artifact['context'], join(CLOUDHARNESS_ROOT, 'applications/samples')) assert 'TEST_ARGUMENT' in samples_artifact['docker']['buildArgs'] @@ -96,7 +98,7 @@ def test_create_skaffold_configuration(tmp_path): assert len(sk['test']) == 2, 'Unit tests should be included' samples_test = sk['test'][0] - assert samples_test['image'] == f'reg/testprojectname/sample', 'Unit tests for samples should be included' + assert samples_test['image'] == expected_samples_image, 'Unit tests for samples should be included' assert "samples/test" in samples_test['custom'][0]['command'], "The test command must come from values.yaml test/unit/commands" assert len(sk['test'][1]['custom']) == 2 From a69836a8c333f6ff0646738738664af5cf2c1326 Mon Sep 17 00:00:00 2001 From: Filippo Ledda Date: Fri, 20 Feb 2026 18:27:38 +0100 Subject: [PATCH 07/12] CH-239 fix test pipeline --- deployment/codefresh-test.yaml | 499 +++++++++++++++++++++++++++++++-- 1 file changed, 482 insertions(+), 17 deletions(-) diff --git a/deployment/codefresh-test.yaml b/deployment/codefresh-test.yaml index 458c33594..50707cf9f 100644 --- a/deployment/codefresh-test.yaml +++ b/deployment/codefresh-test.yaml @@ -13,33 +13,20 @@ steps: repo: '${{CF_REPO_OWNER}}/${{CF_REPO_NAME}}' revision: '${{CF_BRANCH}}' git: github - post_main_clone: - title: Post main clone - type: parallel - stage: prepare - steps: - clone_cloud_harness: - title: Cloning cloud-harness repository... - type: git-clone - stage: prepare - repo: https://github.com/MetaCell/cloud-harness.git - revision: '${{CLOUDHARNESS_BRANCH}}' - working_directory: . - git: github prepare_deployment: title: Prepare helm chart image: python:3.12 stage: prepare working_directory: . commands: - - bash cloud-harness/install.sh + - bash ./install.sh - export HELM_META_ARGS="$( [ -n "${{HARNESS_CHART_NAME}}" ] && printf -- "--name %s " "${{HARNESS_CHART_NAME}}"; [ -n "${{HARNESS_CHART_VERSION}}" ] && printf -- "--chart-version %s " "${{HARNESS_CHART_VERSION}}"; [ -n "${{HARNESS_APP_VERSION}}" ] && printf -- "--app-version %s" "${{HARNESS_APP_VERSION}}" )" - harness-deployment . -n test-${{NAMESPACE_BASENAME}} -d ${{DOMAIN}} -r ${{REGISTRY}} -rs ${{REGISTRY_SECRET}} -e test --write-env --cache-url '${{IMAGE_CACHE_URL}}' - -N $HELM_META_ARGS -i accounts + -N $HELM_META_ARGS -i samples - cat deployment/.env >> ${{CF_VOLUME_PATH}}/env_vars_to_export - cat ${{CF_VOLUME_PATH}}/env_vars_to_export prepare_deployment_view: @@ -78,6 +65,146 @@ steps: == true forceNoCache: includes('${{TEST_E2E_TAG_FORCE_BUILD}}', '{{TEST_E2E_TAG_FORCE_BUILD}}') == false + accounts: + type: build + stage: build + dockerfile: Dockerfile + registry: '${{CODEFRESH_REGISTRY}}' + buildkit: true + build_arguments: + - NOCACHE=${{CF_BUILD_ID}} + image_name: cloud-harness/accounts + title: Accounts + working_directory: ./applications/accounts + tags: + - '${{ACCOUNTS_TAG}}' + - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' + - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' + when: + condition: + any: + buildDoesNotExist: includes('${{ACCOUNTS_TAG_EXISTS}}', '{{ACCOUNTS_TAG_EXISTS}}') + == true + forceNoCache: includes('${{ACCOUNTS_TAG_FORCE_BUILD}}', '{{ACCOUNTS_TAG_FORCE_BUILD}}') + == false + cloudharness-base: + type: build + stage: build + dockerfile: infrastructure/base-images/cloudharness-base/Dockerfile + registry: '${{CODEFRESH_REGISTRY}}' + buildkit: true + build_arguments: + - NOCACHE=${{CF_BUILD_ID}} + image_name: cloud-harness/cloudharness-base + title: Cloudharness base + working_directory: ./. + tags: + - '${{CLOUDHARNESS_BASE_TAG}}' + - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' + - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' + when: + condition: + any: + buildDoesNotExist: includes('${{CLOUDHARNESS_BASE_TAG_EXISTS}}', '{{CLOUDHARNESS_BASE_TAG_EXISTS}}') + == true + forceNoCache: includes('${{CLOUDHARNESS_BASE_TAG_FORCE_BUILD}}', '{{CLOUDHARNESS_BASE_TAG_FORCE_BUILD}}') + == false + cloudharness-frontend-build: + type: build + stage: build + dockerfile: infrastructure/base-images/cloudharness-frontend-build/Dockerfile + registry: '${{CODEFRESH_REGISTRY}}' + buildkit: true + build_arguments: + - NOCACHE=${{CF_BUILD_ID}} + image_name: cloud-harness/cloudharness-frontend-build + title: Cloudharness frontend build + working_directory: ./. + tags: + - '${{CLOUDHARNESS_FRONTEND_BUILD_TAG}}' + - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' + - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' + when: + condition: + any: + buildDoesNotExist: includes('${{CLOUDHARNESS_FRONTEND_BUILD_TAG_EXISTS}}', + '{{CLOUDHARNESS_FRONTEND_BUILD_TAG_EXISTS}}') == true + forceNoCache: includes('${{CLOUDHARNESS_FRONTEND_BUILD_TAG_FORCE_BUILD}}', + '{{CLOUDHARNESS_FRONTEND_BUILD_TAG_FORCE_BUILD}}') == false + title: Build parallel step 1 + build_application_images_1: + type: parallel + stage: build + steps: + cloudharness-flask: + type: build + stage: build + dockerfile: Dockerfile + registry: '${{CODEFRESH_REGISTRY}}' + buildkit: true + build_arguments: + - NOCACHE=${{CF_BUILD_ID}} + - CLOUDHARNESS_BASE=${{REGISTRY}}/cloud-harness/cloudharness-base:${{CLOUDHARNESS_BASE_TAG}} + image_name: cloud-harness/cloudharness-flask + title: Cloudharness flask + working_directory: ./infrastructure/common-images/cloudharness-flask + tags: + - '${{CLOUDHARNESS_FLASK_TAG}}' + - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' + - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' + when: + condition: + any: + buildDoesNotExist: includes('${{CLOUDHARNESS_FLASK_TAG_EXISTS}}', '{{CLOUDHARNESS_FLASK_TAG_EXISTS}}') + == true + forceNoCache: includes('${{CLOUDHARNESS_FLASK_TAG_FORCE_BUILD}}', '{{CLOUDHARNESS_FLASK_TAG_FORCE_BUILD}}') + == false + workflows-notify-queue: + type: build + stage: build + dockerfile: Dockerfile + registry: '${{CODEFRESH_REGISTRY}}' + buildkit: true + build_arguments: + - NOCACHE=${{CF_BUILD_ID}} + - CLOUDHARNESS_BASE=${{REGISTRY}}/cloud-harness/cloudharness-base:${{CLOUDHARNESS_BASE_TAG}} + image_name: cloud-harness/workflows-notify-queue + title: Workflows notify queue + working_directory: ./applications/workflows/tasks/notify-queue + tags: + - '${{WORKFLOWS_NOTIFY_QUEUE_TAG}}' + - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' + - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' + when: + condition: + any: + buildDoesNotExist: includes('${{WORKFLOWS_NOTIFY_QUEUE_TAG_EXISTS}}', + '{{WORKFLOWS_NOTIFY_QUEUE_TAG_EXISTS}}') == true + forceNoCache: includes('${{WORKFLOWS_NOTIFY_QUEUE_TAG_FORCE_BUILD}}', + '{{WORKFLOWS_NOTIFY_QUEUE_TAG_FORCE_BUILD}}') == false + cloudharness-django: + type: build + stage: build + dockerfile: Dockerfile + registry: '${{CODEFRESH_REGISTRY}}' + buildkit: true + build_arguments: + - NOCACHE=${{CF_BUILD_ID}} + - CLOUDHARNESS_BASE=${{REGISTRY}}/cloud-harness/cloudharness-base:${{CLOUDHARNESS_BASE_TAG}} + image_name: cloud-harness/cloudharness-django + title: Cloudharness django + working_directory: ./infrastructure/common-images/cloudharness-django + tags: + - '${{CLOUDHARNESS_DJANGO_TAG}}' + - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' + - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' + when: + condition: + any: + buildDoesNotExist: includes('${{CLOUDHARNESS_DJANGO_TAG_EXISTS}}', '{{CLOUDHARNESS_DJANGO_TAG_EXISTS}}') + == true + forceNoCache: includes('${{CLOUDHARNESS_DJANGO_TAG_FORCE_BUILD}}', '{{CLOUDHARNESS_DJANGO_TAG_FORCE_BUILD}}') + == false test-api: type: build stage: build @@ -102,7 +229,257 @@ steps: == true forceNoCache: includes('${{TEST_API_TAG_FORCE_BUILD}}', '{{TEST_API_TAG_FORCE_BUILD}}') == false - title: Build parallel step 1 + jupyterhub: + type: build + stage: build + dockerfile: Dockerfile + registry: '${{CODEFRESH_REGISTRY}}' + buildkit: true + build_arguments: + - NOCACHE=${{CF_BUILD_ID}} + - CLOUDHARNESS_BASE=${{REGISTRY}}/cloud-harness/cloudharness-base:${{CLOUDHARNESS_BASE_TAG}} + image_name: cloud-harness/jupyterhub + title: Jupyterhub + working_directory: ./applications/jupyterhub + tags: + - '${{JUPYTERHUB_TAG}}' + - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' + - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' + when: + condition: + any: + buildDoesNotExist: includes('${{JUPYTERHUB_TAG_EXISTS}}', '{{JUPYTERHUB_TAG_EXISTS}}') + == true + forceNoCache: includes('${{JUPYTERHUB_TAG_FORCE_BUILD}}', '{{JUPYTERHUB_TAG_FORCE_BUILD}}') + == false + workflows-send-result-event: + type: build + stage: build + dockerfile: Dockerfile + registry: '${{CODEFRESH_REGISTRY}}' + buildkit: true + build_arguments: + - NOCACHE=${{CF_BUILD_ID}} + - CLOUDHARNESS_BASE=${{REGISTRY}}/cloud-harness/cloudharness-base:${{CLOUDHARNESS_BASE_TAG}} + image_name: cloud-harness/workflows-send-result-event + title: Workflows send result event + working_directory: ./applications/workflows/tasks/send-result-event + tags: + - '${{WORKFLOWS_SEND_RESULT_EVENT_TAG}}' + - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' + - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' + when: + condition: + any: + buildDoesNotExist: includes('${{WORKFLOWS_SEND_RESULT_EVENT_TAG_EXISTS}}', + '{{WORKFLOWS_SEND_RESULT_EVENT_TAG_EXISTS}}') == true + forceNoCache: includes('${{WORKFLOWS_SEND_RESULT_EVENT_TAG_FORCE_BUILD}}', + '{{WORKFLOWS_SEND_RESULT_EVENT_TAG_FORCE_BUILD}}') == false + workflows-extract-download: + type: build + stage: build + dockerfile: Dockerfile + registry: '${{CODEFRESH_REGISTRY}}' + buildkit: true + build_arguments: + - NOCACHE=${{CF_BUILD_ID}} + - CLOUDHARNESS_BASE=${{REGISTRY}}/cloud-harness/cloudharness-base:${{CLOUDHARNESS_BASE_TAG}} + image_name: cloud-harness/workflows-extract-download + title: Workflows extract download + working_directory: ./applications/workflows/tasks/extract-download + tags: + - '${{WORKFLOWS_EXTRACT_DOWNLOAD_TAG}}' + - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' + - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' + when: + condition: + any: + buildDoesNotExist: includes('${{WORKFLOWS_EXTRACT_DOWNLOAD_TAG_EXISTS}}', + '{{WORKFLOWS_EXTRACT_DOWNLOAD_TAG_EXISTS}}') == true + forceNoCache: includes('${{WORKFLOWS_EXTRACT_DOWNLOAD_TAG_FORCE_BUILD}}', + '{{WORKFLOWS_EXTRACT_DOWNLOAD_TAG_FORCE_BUILD}}') == false + samples-secret: + type: build + stage: build + dockerfile: Dockerfile + registry: '${{CODEFRESH_REGISTRY}}' + buildkit: true + build_arguments: + - NOCACHE=${{CF_BUILD_ID}} + - CLOUDHARNESS_BASE=${{REGISTRY}}/cloud-harness/cloudharness-base:${{CLOUDHARNESS_BASE_TAG}} + image_name: cloud-harness/samples-secret + title: Samples secret + working_directory: ./applications/samples/tasks/secret + tags: + - '${{SAMPLES_SECRET_TAG}}' + - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' + - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' + when: + condition: + any: + buildDoesNotExist: includes('${{SAMPLES_SECRET_TAG_EXISTS}}', '{{SAMPLES_SECRET_TAG_EXISTS}}') + == true + forceNoCache: includes('${{SAMPLES_SECRET_TAG_FORCE_BUILD}}', '{{SAMPLES_SECRET_TAG_FORCE_BUILD}}') + == false + samples-print-file: + type: build + stage: build + dockerfile: Dockerfile + registry: '${{CODEFRESH_REGISTRY}}' + buildkit: true + build_arguments: + - NOCACHE=${{CF_BUILD_ID}} + - CLOUDHARNESS_BASE=${{REGISTRY}}/cloud-harness/cloudharness-base:${{CLOUDHARNESS_BASE_TAG}} + image_name: cloud-harness/samples-print-file + title: Samples print file + working_directory: ./applications/samples/tasks/print-file + tags: + - '${{SAMPLES_PRINT_FILE_TAG}}' + - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' + - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' + when: + condition: + any: + buildDoesNotExist: includes('${{SAMPLES_PRINT_FILE_TAG_EXISTS}}', '{{SAMPLES_PRINT_FILE_TAG_EXISTS}}') + == true + forceNoCache: includes('${{SAMPLES_PRINT_FILE_TAG_FORCE_BUILD}}', '{{SAMPLES_PRINT_FILE_TAG_FORCE_BUILD}}') + == false + title: Build parallel step 2 + build_application_images_2: + type: parallel + stage: build + steps: + workflows: + type: build + stage: build + dockerfile: Dockerfile + registry: '${{CODEFRESH_REGISTRY}}' + buildkit: true + build_arguments: + - NOCACHE=${{CF_BUILD_ID}} + - CLOUDHARNESS_FLASK=${{REGISTRY}}/cloud-harness/cloudharness-flask:${{CLOUDHARNESS_FLASK_TAG}} + image_name: cloud-harness/workflows + title: Workflows + working_directory: ./applications/workflows/server + tags: + - '${{WORKFLOWS_TAG}}' + - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' + - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' + when: + condition: + any: + buildDoesNotExist: includes('${{WORKFLOWS_TAG_EXISTS}}', '{{WORKFLOWS_TAG_EXISTS}}') + == true + forceNoCache: includes('${{WORKFLOWS_TAG_FORCE_BUILD}}', '{{WORKFLOWS_TAG_FORCE_BUILD}}') + == false + volumemanager: + type: build + stage: build + dockerfile: Dockerfile + registry: '${{CODEFRESH_REGISTRY}}' + buildkit: true + build_arguments: + - NOCACHE=${{CF_BUILD_ID}} + - CLOUDHARNESS_FLASK=${{REGISTRY}}/cloud-harness/cloudharness-flask:${{CLOUDHARNESS_FLASK_TAG}} + image_name: cloud-harness/volumemanager + title: Volumemanager + working_directory: ./applications/volumemanager/server + tags: + - '${{VOLUMEMANAGER_TAG}}' + - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' + - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' + when: + condition: + any: + buildDoesNotExist: includes('${{VOLUMEMANAGER_TAG_EXISTS}}', '{{VOLUMEMANAGER_TAG_EXISTS}}') + == true + forceNoCache: includes('${{VOLUMEMANAGER_TAG_FORCE_BUILD}}', '{{VOLUMEMANAGER_TAG_FORCE_BUILD}}') + == false + samples: + type: build + stage: build + dockerfile: Dockerfile + registry: '${{CODEFRESH_REGISTRY}}' + buildkit: true + build_arguments: + - NOCACHE=${{CF_BUILD_ID}} + - CLOUDHARNESS_FRONTEND_BUILD=${{REGISTRY}}/cloud-harness/cloudharness-frontend-build:${{CLOUDHARNESS_FRONTEND_BUILD_TAG}} + - CLOUDHARNESS_FLASK=${{REGISTRY}}/cloud-harness/cloudharness-flask:${{CLOUDHARNESS_FLASK_TAG}} + image_name: cloud-harness/samples + title: Samples + working_directory: ./applications/samples + tags: + - '${{SAMPLES_TAG}}' + - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' + - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' + when: + condition: + any: + buildDoesNotExist: includes('${{SAMPLES_TAG_EXISTS}}', '{{SAMPLES_TAG_EXISTS}}') + == true + forceNoCache: includes('${{SAMPLES_TAG_FORCE_BUILD}}', '{{SAMPLES_TAG_FORCE_BUILD}}') + == false + common: + type: build + stage: build + dockerfile: Dockerfile + registry: '${{CODEFRESH_REGISTRY}}' + buildkit: true + build_arguments: + - NOCACHE=${{CF_BUILD_ID}} + - CLOUDHARNESS_FLASK=${{REGISTRY}}/cloud-harness/cloudharness-flask:${{CLOUDHARNESS_FLASK_TAG}} + image_name: cloud-harness/common + title: Common + working_directory: ./applications/common/server + tags: + - '${{COMMON_TAG}}' + - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' + - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' + when: + condition: + any: + buildDoesNotExist: includes('${{COMMON_TAG_EXISTS}}', '{{COMMON_TAG_EXISTS}}') + == true + forceNoCache: includes('${{COMMON_TAG_FORCE_BUILD}}', '{{COMMON_TAG_FORCE_BUILD}}') + == false + title: Build parallel step 3 + build_application_images_3: + type: parallel + stage: build + steps: + samples-sum: + type: build + stage: build + dockerfile: Dockerfile + registry: '${{CODEFRESH_REGISTRY}}' + buildkit: true + build_arguments: + - NOCACHE=${{CF_BUILD_ID}} + - SAMPLES=${{REGISTRY}}/cloud-harness/samples:${{SAMPLES_TAG}} + image_name: cloud-harness/samples-sum + title: Samples sum + working_directory: ./applications/samples/tasks/sum + tags: + - '${{SAMPLES_SUM_TAG}}' + - '${{DEPLOYMENT_PUBLISH_TAG}}-dev' + - '${{CF_BRANCH_TAG_NORMALIZED_LOWER_CASE}}' + when: + condition: + any: + buildDoesNotExist: includes('${{SAMPLES_SUM_TAG_EXISTS}}', '{{SAMPLES_SUM_TAG_EXISTS}}') + == true + forceNoCache: includes('${{SAMPLES_SUM_TAG_FORCE_BUILD}}', '{{SAMPLES_SUM_TAG_FORCE_BUILD}}') + == false + title: Build parallel step 4 + tests_unit: + stage: unittest + type: parallel + steps: + samples_ut: + title: Unit tests for samples + commands: + - pytest /usr/src/app/samples/test + image: '${{REGISTRY}}/cloud-harness/samples:${{SAMPLES_TAG}}' deployment: stage: deploy type: helm @@ -118,7 +495,8 @@ steps: cmd_ps: --timeout 600s --create-namespace custom_value_files: - ./deployment/helm/values.yaml - custom_values: [] + custom_values: + - apps_samples_harness_secrets_asecret=${{ASECRET}} wait_deployment: stage: qa title: Wait deployment to be ready @@ -126,8 +504,95 @@ steps: commands: - kubectl config use-context ${{CLUSTER_NAME}} - kubectl config set-context --current --namespace=test-${{NAMESPACE_BASENAME}} + - kubectl rollout status deployment/workflows + - kubectl rollout status deployment/argo-gk + - kubectl rollout status deployment/volumemanager + - kubectl rollout status deployment/samples + - kubectl rollout status deployment/samples-gk + - kubectl rollout status deployment/common - kubectl rollout status deployment/accounts - sleep 60 + tests_api: + stage: qa + title: Api tests + working_directory: /home/test + image: '${{REGISTRY}}/cloud-harness/test-api:${{TEST_API_TAG}}' + fail_fast: false + commands: + - echo $APP_NAME + scale: + samples_api_test: + title: samples api test + volumes: + - '${{CF_REPO_NAME}}/applications/samples:/home/test' + - '${{CF_REPO_NAME}}/deployment/helm/values.yaml:/opt/cloudharness/resources/allvalues.yaml' + environment: + - APP_URL=https://samples.${{DOMAIN}}/api + - USERNAME=sample@testuser.com + - PASSWORD=test + commands: + - st --pre-run cloudharness_test.apitest_init run api/openapi.yaml --base-url + https://samples.${{DOMAIN}}/api -c all --skip-deprecated-operations --exclude-operation-id=submit_sync + --exclude-operation-id=submit_sync_with_results --exclude-operation-id=error + --hypothesis-suppress-health-check=too_slow --hypothesis-deadline=180000 + --request-timeout=180000 --hypothesis-max-examples=2 --show-trace --exclude-checks=ignored_auth + - pytest -v test/api + common_api_test: + title: common api test + volumes: + - '${{CF_REPO_NAME}}/applications/common:/home/test' + - '${{CF_REPO_NAME}}/deployment/helm/values.yaml:/opt/cloudharness/resources/allvalues.yaml' + environment: + - APP_URL=https://common.${{DOMAIN}}/api + commands: + - st --pre-run cloudharness_test.apitest_init run api/openapi.yaml --base-url + https://common.${{DOMAIN}}/api -c all + workflows_api_test: + title: workflows api test + volumes: + - '${{CF_REPO_NAME}}/applications/workflows:/home/test' + - '${{CF_REPO_NAME}}/deployment/helm/values.yaml:/opt/cloudharness/resources/allvalues.yaml' + environment: + - APP_URL=https://workflows.${{DOMAIN}}/api + commands: + - st --pre-run cloudharness_test.apitest_init run api/openapi.yaml --base-url + https://workflows.${{DOMAIN}}/api -c all + hooks: + on_fail: + exec: + image: alpine + commands: + - cf_export FAILED=failed + tests_e2e: + stage: qa + title: End to end tests + working_directory: /home/test + image: '${{REGISTRY}}/cloud-harness/test-e2e:${{TEST_E2E_TAG}}' + fail_fast: false + commands: + - npx puppeteer browsers install chrome + - yarn test + scale: + jupyterhub_e2e_test: + title: jupyterhub e2e test + volumes: + - '${{CF_REPO_NAME}}/applications/jupyterhub/test/e2e:/home/test/__tests__/jupyterhub' + environment: + - APP_URL=https://hub.${{DOMAIN}} + samples_e2e_test: + title: samples e2e test + volumes: + - '${{CF_REPO_NAME}}/applications/samples/test/e2e:/home/test/__tests__/samples' + environment: + - APP_URL=https://samples.${{DOMAIN}} + - USERNAME=sample@testuser.com + - PASSWORD=test + hooks: + on_fail: + exec: + image: alpine + commands: + - cf_export FAILED=failed approval: type: pending-approval stage: qa From a94b168d091d21e3b27a459958c45207435a9323 Mon Sep 17 00:00:00 2001 From: Filippo Ledda Date: Fri, 20 Feb 2026 18:32:01 +0100 Subject: [PATCH 08/12] chore: add oyaml to test requirements --- libraries/models/test-requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/models/test-requirements.txt b/libraries/models/test-requirements.txt index 8e6d8cb13..5bc5ac9c9 100644 --- a/libraries/models/test-requirements.txt +++ b/libraries/models/test-requirements.txt @@ -3,3 +3,4 @@ pytest-cov>=2.8.1 pytest-randomly>=3.12.0 mypy>=1.4.1 types-python-dateutil>=2.8.19 +oyaml \ No newline at end of file From acf747a264ca61aae11300e103f0e4862834a135 Mon Sep 17 00:00:00 2001 From: Filippo Ledda Date: Fri, 20 Feb 2026 18:44:10 +0100 Subject: [PATCH 09/12] lint: correct formatting issues in multiple files --- .../cloudharness/workflows/operations.py | 2 +- .../ch_cli_tools/configurationgenerator.py | 6 +++--- tools/deployment-cli-tools/ch_cli_tools/helm.py | 9 ++++----- tools/deployment-cli-tools/tests/test_helm.py | 5 +---- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/libraries/cloudharness-common/cloudharness/workflows/operations.py b/libraries/cloudharness-common/cloudharness/workflows/operations.py index 22a71667e..d97ea5919 100644 --- a/libraries/cloudharness-common/cloudharness/workflows/operations.py +++ b/libraries/cloudharness-common/cloudharness/workflows/operations.py @@ -157,7 +157,7 @@ def spec(self): volumes_mounts = list(self.volumes) or [] # Tasks volumes must be declared at workflow level volumes_mounts += list({v for task in self.task_list() - for v in task.volume_mounts if task.volume_mounts}) + for v in task.volume_mounts if task.volume_mounts}) spec['volumeClaimTemplates'] = [self.spec_volumeclaim(volume) for volume in volumes_mounts if # without PVC prefix (e.g. /location) diff --git a/tools/deployment-cli-tools/ch_cli_tools/configurationgenerator.py b/tools/deployment-cli-tools/ch_cli_tools/configurationgenerator.py index ed521650e..f1f6eff64 100644 --- a/tools/deployment-cli-tools/ch_cli_tools/configurationgenerator.py +++ b/tools/deployment-cli-tools/ch_cli_tools/configurationgenerator.py @@ -328,17 +328,17 @@ def image_tag(self, image_name, build_context_path=None, dependencies=()): logging.info(f"Ignoring {ignore}") tag = generate_tag_from_content(build_context_path, ignore) logging.info(f"Content hash: {tag}") - + # Get dependencies from build context if not provided dependencies = dependencies or guess_build_dependencies_from_dockerfile(build_context_path) - + # Combine with dependency tags dep_tags = "".join(self.all_images.get(n, '') for n in dependencies) if dep_tags: logging.info(f"Dependency tags: {[(n, self.all_images.get(n, '')) for n in dependencies]}") tag = sha1((tag + dep_tags).encode("utf-8")).hexdigest() logging.info(f"Generated tag (with dependencies): {tag}") - + 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 '') diff --git a/tools/deployment-cli-tools/ch_cli_tools/helm.py b/tools/deployment-cli-tools/ch_cli_tools/helm.py index b3ffdf41d..4f0f25fed 100644 --- a/tools/deployment-cli-tools/ch_cli_tools/helm.py +++ b/tools/deployment-cli-tools/ch_cli_tools/helm.py @@ -251,10 +251,10 @@ def create_app_values_spec(self, app_name, app_path, base_image_name=None, helm_ deployment_values = values.get(KEY_HARNESS, {}).get(KEY_DEPLOYMENT, {}) deployment_image = deployment_values.get('image', None) or values.get('image', None) values['build'] = not bool(deployment_image) # Used by skaffold and ci/cd to determine if the image should be built - - image_name = get_image_name(values.get(KEY_HARNESS, {}).get('image_name', None) , base_image_name) + + image_name = get_image_name(values.get(KEY_HARNESS, {}).get('image_name', None), base_image_name) if len(image_paths) > 0 and not deployment_image: - + image_name = image_name or 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) @@ -262,7 +262,6 @@ def create_app_values_spec(self, app_name, app_path, base_image_name=None, helm_ KEY_HARNESS].get(KEY_DEPLOYMENT, {}).get('auto', False): raise Exception(f"At least one Dockerfile must be specified on application {app_name}. " f"Specify harness.deployment.image value if you intend to use a prebuilt image.") - task_images_paths = [path for path in find_dockerfiles_paths( app_path) if 'tasks/' in path] @@ -276,7 +275,7 @@ def create_app_values_spec(self, app_name, app_path, base_image_name=None, helm_ for task_path in task_images_paths: task_name = app_name_from_path(os.path.relpath( task_path, os.path.dirname(app_path))) - task_img_name = "-".join([image_name, os.path.basename(task_path)] ) if image_name else image_name_from_dockerfile_path(task_path, base_image_name) + task_img_name = "-".join([image_name, os.path.basename(task_path)]) if image_name else image_name_from_dockerfile_path(task_path, base_image_name) values[KEY_TASK_IMAGES][task_name] = self.image_tag( task_img_name, build_context_path=task_path, dependencies=values[KEY_TASK_IMAGES].keys()) diff --git a/tools/deployment-cli-tools/tests/test_helm.py b/tools/deployment-cli-tools/tests/test_helm.py index 6ceaff199..809beffce 100644 --- a/tools/deployment-cli-tools/tests/test_helm.py +++ b/tools/deployment-cli-tools/tests/test_helm.py @@ -97,16 +97,13 @@ def test_collect_nobuild(tmp_path): def test_collect_helm_values_harness_image_name_override(tmp_path): out_folder = tmp_path / 'test_collect_helm_values_harness_image_name_override' - - values = create_helm_chart([CLOUDHARNESS_ROOT, RESOURCES], output_path=out_folder, include=['myapp'], - domain="my.local", namespace='test', env='imagename', local=False, tag=1, registry='reg') + domain="my.local", namespace='test', env='imagename', local=False, tag=1, registry='reg') assert values[KEY_APPS]['myapp'][KEY_HARNESS]['deployment']['image'] == 'reg/testprojectname/custom-myapp:1' assert values[KEY_APPS]['myapp'][KEY_TASK_IMAGES]['myapp-mytask'] == 'reg/testprojectname/custom-myapp-mytask:1' - def test_collect_helm_values_noreg_noinclude(tmp_path): out_path = tmp_path / 'test_collect_helm_values_noreg_noinclude' values = create_helm_chart([CLOUDHARNESS_ROOT, RESOURCES], output_path=out_path, domain="my.local", From 9bc3eaab84a98242c85cbe406edc339b57b78037 Mon Sep 17 00:00:00 2001 From: Filippo Ledda <46561561+filippomc@users.noreply.github.com> Date: Fri, 20 Feb 2026 18:45:23 +0100 Subject: [PATCH 10/12] Update libraries/models/pyproject.toml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- libraries/models/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/models/pyproject.toml b/libraries/models/pyproject.toml index a5a8a7122..91bcda0f4 100644 --- a/libraries/models/pyproject.toml +++ b/libraries/models/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "cloudharness_model" -version = "1.0.0" +version = "3.0.0" description = "cloudharness" authors = ["OpenAPI Generator Community "] license = "NoLicense" From 9741ff88f6a997b74d3da062cf8c9b034ebd861f Mon Sep 17 00:00:00 2001 From: Filippo Ledda <46561561+filippomc@users.noreply.github.com> Date: Fri, 20 Feb 2026 18:51:42 +0100 Subject: [PATCH 11/12] Update .github/instructions/tools.instructions.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/instructions/tools.instructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/instructions/tools.instructions.md b/.github/instructions/tools.instructions.md index 2ab5d6a12..ec4a3789e 100644 --- a/.github/instructions/tools.instructions.md +++ b/.github/instructions/tools.instructions.md @@ -65,7 +65,7 @@ Take the following best practices into account when writing code for the project ### Package Management Rules - **Frontend**: ONLY use yarn, NEVER npm - **Backend**: ONLY use pip within conda environment -- **ALWAYS** activate `mnp` conda environment before any backend work +- **ALWAYS** activate `ch` conda environment before any backend work ### CloudHarness Considerations - Dependencies may need special handling in development environment From 4da5e4eefb360d159d4edc81507a2eaca3b27258 Mon Sep 17 00:00:00 2001 From: Filippo Ledda Date: Fri, 20 Feb 2026 18:57:09 +0100 Subject: [PATCH 12/12] #836 CH-239 fix: handle empty image_name in get_image_name function --- tools/deployment-cli-tools/ch_cli_tools/helm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/deployment-cli-tools/ch_cli_tools/helm.py b/tools/deployment-cli-tools/ch_cli_tools/helm.py index 4f0f25fed..6a9c3eca1 100644 --- a/tools/deployment-cli-tools/ch_cli_tools/helm.py +++ b/tools/deployment-cli-tools/ch_cli_tools/helm.py @@ -252,7 +252,7 @@ def create_app_values_spec(self, app_name, app_path, base_image_name=None, helm_ deployment_image = deployment_values.get('image', None) or values.get('image', None) values['build'] = not bool(deployment_image) # Used by skaffold and ci/cd to determine if the image should be built - image_name = get_image_name(values.get(KEY_HARNESS, {}).get('image_name', None), base_image_name) + image_name = get_image_name(values.get(KEY_HARNESS, {}).get('image_name', ''), base_image_name) if len(image_paths) > 0 and not deployment_image: image_name = image_name or image_name_from_dockerfile_path(os.path.relpath(image_paths[0], os.path.dirname(app_path)), base_image_name)