diff --git a/.github/actions/helm-generic/action.yml b/.github/actions/helm-generic/action.yml index cd8f92e..31a70a4 100644 --- a/.github/actions/helm-generic/action.yml +++ b/.github/actions/helm-generic/action.yml @@ -1,27 +1,20 @@ -name: 'Helm Deploy' +name: 'Helm Generic Deploy' description: 'Deploy a Helm chart to a Kubernetes cluster' +author: 'Simplify9' inputs: - registry_profile: - description: "Profile code for dynamic kubeconfig/registry vars & secrets (e.g., S9, WISEWELL)" - required: false - default: S9 app_name: description: "Application name (Helm release name)" required: true - version: - description: "Logical version label (may be used in image tag selection)" - required: false - default: staging namespace: description: "Kubernetes namespace to deploy into" required: true chart: - description: "Chart name (local or from repo)" + description: "Chart name, resolved from chart_repo (this action does not check out the repo, so local chart paths are not supported)" required: false default: s9genericchart chart_repo: - description: "Helm repo URL providing the chart (ignored if chart is a path)" + description: "Helm repo URL providing the chart" required: false default: "https://charts.sf9.io" helm_timeout: @@ -37,35 +30,54 @@ inputs: description: "Raw extra args appended to helm upgrade (e.g. --debug)" required: false default: '' - ingress_paths: - description: |- - YAML list of ingress paths. Provide as a multi-line YAML sequence, e.g.: - - / - - /api - - /docs - Defaults to just the root path '/'. Each entry becomes --set ingress.paths[index]=. + atomic: + description: "If 'true', pass --atomic to helm upgrade so a failed deploy is rolled back automatically (implies --wait)." required: false - default: |- - - / + default: 'true' kubeconfig_data: description: |- - Raw kubeconfig YAML or base64-encoded kubeconfig data. This replaces the dynamic secret (_KUBECONFIG). - IMPORTANT: Passing sensitive data as a plain input may expose it in logs. Prefer keeping this workflow reusable only - in controlled contexts. Provide either the full YAML (containing apiVersion, clusters, users, etc.) or a base64 string - that decodes to the kubeconfig file contents. + Kubeconfig used to reach the target cluster, as either raw YAML (containing apiVersion, clusters, + users, contexts, ...) or a base64-encoded string of that YAML. + Provide it from a secret; do not pass cluster credentials as a plaintext literal. required: true init_job_image: description: |- - If set, a Kubernetes Job is created before the Helm deploy that runs this image with the - argument '--migrate'. The Job uses the connection string from init_job_secret_name to run - EF Core migrations with elevated (doadmin) credentials before the main container starts. - Example: registry.digitalocean.com/mealiverycr/mealivery-api:latest + If set, a Kubernetes Job runs this image to completion before the Helm deploy (e.g. for database + migrations). The command it runs is controlled by init_job_command, and a connection string is + injected from init_job_secret_name. Leave empty to skip the pre-deploy Job. + Example: registry.digitalocean.com/acme/api:latest required: false default: '' init_job_secret_name: description: |- - Name of the Kubernetes Secret (in the target namespace) whose 'connection-string' key is - injected as ConnectionStrings__ShanapDb into the migration Job. Required when init_job_image is set. + Name of the Kubernetes Secret (in the target namespace) whose key (see init_job_secret_key) + is injected as the connection-string env var (see init_job_connstring_env) into the migration + Job. Required when init_job_image is set. + required: false + default: '' + init_job_command: + description: |- + Command (and args) the migration Job container runs, as a space-separated string. + Converted to a JSON exec array, e.g. "dotnet App.dll --migrate" -> ["dotnet","App.dll","--migrate"]. + Leave empty to keep the historical default ('dotnet SW.Shanap.Web.dll --migrate'). + required: false + default: '' + init_job_secret_key: + description: |- + Key within init_job_secret_name whose value holds the DB connection string. + Leave empty to keep the historical default ('connection-string'). + required: false + default: '' + init_job_connstring_env: + description: |- + Name of the environment variable the connection string is injected into inside the migration Job. + Leave empty to keep the historical default ('ConnectionStrings__ShanapDb'). + required: false + default: '' + init_job_environment_env: + description: |- + Name of the environment variable carrying the environment name (the 'environment' input) inside + the migration Job. Leave empty to keep the historical default ('ASPNETCORE_ENVIRONMENT'). required: false default: '' environment: @@ -78,15 +90,20 @@ inputs: runs: using: 'composite' steps: - - uses: actions/checkout@v6 - - name: Announce Helm deployment shell: bash + env: + APP_NAME: ${{ inputs.app_name }} + CHART: ${{ inputs.chart }} + NAMESPACE: ${{ inputs.namespace }} + ENVIRONMENT: ${{ inputs.environment }} + CHART_REPO: ${{ inputs.chart_repo }} + HELM_TIMEOUT: ${{ inputs.helm_timeout }} run: | - echo "::notice title=☸️ [HELM] Deploy Chart::App: ${{ inputs.app_name }}, chart: ${{ inputs.chart }}, namespace: ${{ inputs.namespace }}, env: ${{ inputs.environment }}" - echo "⚙️ [HELM] Chart repo: ${{ inputs.chart_repo != '' && inputs.chart_repo || 'OCI (inline)' }}, timeout: ${{ inputs.helm_timeout }}" - echo "CHECKPOINT_1_STATUS=⏳ Pending" >> "$GITHUB_ENV" - echo "CHECKPOINT_2_STATUS=⏳ Pending" >> "$GITHUB_ENV" + echo "::notice title=☸️ [HELM] Deploy Chart::App: ${APP_NAME}, chart: ${CHART}, namespace: ${NAMESPACE}, env: ${ENVIRONMENT}" + echo "⚙️ [HELM] Chart repo: ${CHART_REPO:-OCI (inline)}, timeout: ${HELM_TIMEOUT}" + echo "HELM_GENERIC_KUBECONFIG_STATUS=⏳ Pending" >> "$GITHUB_ENV" + echo "HELM_GENERIC_DEPLOY_STATUS=⏳ Pending" >> "$GITHUB_ENV" - name: Derive sanitized branch name id: branch @@ -111,6 +128,7 @@ runs: shell: bash env: KUBECONFIG_DATA: ${{ inputs.kubeconfig_data }} + NAMESPACE: ${{ inputs.namespace }} run: | echo "::group::☸️ [CHECKPOINT 1/2] Process Kubeconfig" set -euo pipefail @@ -119,20 +137,33 @@ runs: echo "kubeconfig_data input is required but empty." >&2 exit 1 fi + # Write to RUNNER_TEMP (not the workspace) so the credential is not left in the + # checked-out tree. Accept raw kubeconfig YAML or base64-encoded data, mirroring the + # generic-gateway-helm-template workflow's decoder: prefer raw when it already looks + # like a kubeconfig, otherwise base64-decode, falling back to raw. + KCFG_PATH="${RUNNER_TEMP}/kubeconfig.yaml" if echo "$DATA" | grep -q 'apiVersion:'; then - printf "%s" "$DATA" > kubeconfig.yaml + printf '%s' "$DATA" > "$KCFG_PATH" + elif printf '%s' "$DATA" | base64 -d > "$KCFG_PATH" 2>/dev/null; then + : # successfully decoded base64 else - echo "$DATA" | base64 -d > kubeconfig.yaml || { echo "Failed to base64 decode kubeconfig_data input" >&2; exit 1; } + printf '%s' "$DATA" > "$KCFG_PATH" fi - chmod 600 kubeconfig.yaml - echo "kubeconfig_path=${{ github.workspace }}/kubeconfig.yaml" >> "$GITHUB_OUTPUT" - echo "CHECKPOINT_1_STATUS=✅ PASSED" >> "$GITHUB_ENV" - echo "::notice title=✅ [HELM] Kubeconfig configured::namespace: ${{ inputs.namespace }}" + if ! grep -q 'apiVersion:' "$KCFG_PATH"; then + echo "kubeconfig_data did not resolve to a valid kubeconfig (no apiVersion found)." >&2 + exit 1 + fi + chmod 600 "$KCFG_PATH" + echo "kubeconfig_path=${KCFG_PATH}" >> "$GITHUB_OUTPUT" + echo "HELM_GENERIC_KUBECONFIG_STATUS=✅ PASSED" >> "$GITHUB_ENV" + echo "::notice title=✅ [HELM] Kubeconfig configured::namespace: ${NAMESPACE}" echo "::endgroup::" - name: Set KUBECONFIG env shell: bash - run: echo "KUBECONFIG=${{ steps.kube.outputs.kubeconfig_path }}" >> "$GITHUB_ENV" + env: + KUBECONFIG_PATH: ${{ steps.kube.outputs.kubeconfig_path }} + run: echo "KUBECONFIG=${KUBECONFIG_PATH}" >> "$GITHUB_ENV" - name: Install Helm uses: azure/setup-helm@v5 @@ -142,7 +173,10 @@ runs: - name: Install kubectl uses: azure/setup-kubectl@v5 with: - version: 'latest' + # Pinned for reproducibility (avoids a new kubectl silently changing CI behavior). + # Only basic core-resource ops are used here (apply/wait/get/create), so exact skew + # with the cluster is not critical; bump this to track your cluster's minor version. + version: 'v1.35.0' - name: Run DB migration Job (init container equivalent) if: ${{ inputs.init_job_image != '' }} @@ -152,12 +186,31 @@ runs: NAMESPACE: ${{ inputs.namespace }} INIT_JOB_IMAGE: ${{ inputs.init_job_image }} INIT_JOB_SECRET_NAME: ${{ inputs.init_job_secret_name }} + INIT_JOB_COMMAND: ${{ inputs.init_job_command }} + INIT_JOB_SECRET_KEY: ${{ inputs.init_job_secret_key }} + INIT_JOB_CONNSTRING_ENV: ${{ inputs.init_job_connstring_env }} + INIT_JOB_ENVIRONMENT_ENV: ${{ inputs.init_job_environment_env }} + ENVIRONMENT: ${{ inputs.environment }} run: | set -euo pipefail JOB_NAME="${APP_NAME}-db-migrate-${GITHUB_RUN_NUMBER}" - echo "Applying migration Job: $JOB_NAME" + # Ensure the target namespace exists before applying the Job. The Helm step uses + # --create-namespace, but it runs AFTER this Job, so on a first deploy to a fresh + # namespace the Job apply would otherwise fail. Idempotent (no-op if it already exists). + kubectl create namespace "${NAMESPACE}" --dry-run=client -o yaml | kubectl apply -f - + + # Coalesce to the historical defaults so callers that don't override keep identical behavior. + CMD_STR="${INIT_JOB_COMMAND:-dotnet SW.Shanap.Web.dll --migrate}" + SECRET_KEY="${INIT_JOB_SECRET_KEY:-connection-string}" + CONNSTRING_ENV="${INIT_JOB_CONNSTRING_ENV:-ConnectionStrings__ShanapDb}" + ENVIRONMENT_ENV="${INIT_JOB_ENVIRONMENT_ENV:-ASPNETCORE_ENVIRONMENT}" + + # Build a JSON exec array from the space-separated command string. + CMD_JSON=$(jq -cn --arg s "$CMD_STR" '$s | split(" ") | map(select(length > 0))') + + echo "Applying migration Job: $JOB_NAME (command: ${CMD_JSON})" kubectl apply -f - </dev/null || true) + FAILED=$(kubectl get job "${JOB_NAME}" -n "${NAMESPACE}" -o jsonpath='{.status.conditions[?(@.type=="Failed")].status}' 2>/dev/null || true) + if [ "$COMPLETE" = "True" ]; then + echo "Migration Job completed successfully." + break + fi + if [ "$FAILED" = "True" ] || [ "$SECONDS" -ge "$DEADLINE" ]; then + echo "Migration Job failed or timed out. Fetching logs:" POD=$(kubectl get pods -n "${NAMESPACE}" -l job-name="${JOB_NAME}" -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true) [ -n "$POD" ] && kubectl logs -n "${NAMESPACE}" "$POD" || true exit 1 - } - - echo "Migration Job completed successfully." + fi + sleep 5 + done - name: Helm upgrade / install shell: bash @@ -211,61 +271,59 @@ runs: HELM_TIMEOUT: ${{ inputs.helm_timeout }} EXTRA_SET_VALUES: ${{ inputs.extra_set_values }} EXTRA_ARGS: ${{ inputs.extra_args }} - INGRESS_PATHS: ${{ inputs.ingress_paths }} + ATOMIC: ${{ inputs.atomic }} BRANCH: ${{ steps.branch.outputs.name }} run: | echo "::group::☸️ [CHECKPOINT 2/2] Helm Upgrade / Install" set -euo pipefail if [ -n "$CHART_REPO" ]; then - helm repo add s9generic "$CHART_REPO" 2>/dev/null || true - helm repo update >/dev/null + # --force-update so a stale 's9generic' alias from a prior run (reused/self-hosted runner) + # is repointed at the current chart_repo instead of silently keeping the old URL. + helm repo add --force-update s9generic "$CHART_REPO" >/dev/null + helm repo update s9generic >/dev/null CHART_REF="s9generic/$CHART" else CHART_REF="$CHART" fi - # BASE_SET_ARGS now only covers mandatory intrinsic values; all other chart settings must be passed via - # extra_set_values (one per line) or extra_args. See modified input description for guidance. + # Intrinsic values the action always sets; all other chart settings come from + # extra_set_values (one "key=value" per line) or extra_args. BASE_SET_ARGS=( --set app.name="${APP_NAME}" --set app.version="github-${BRANCH}-${GITHUB_RUN_NUMBER}" ) - PATH_SET_ARGS=() - if [ -n "${INGRESS_PATHS// /}" ]; then - INDEX=0 - CLEAN_PATHS="$(printf '%s' "$INGRESS_PATHS" | tr -d '\r')" - while IFS= read -r line; do - line="$(printf '%s' "$line" | sed -E 's/#.*$//')" - line="$(printf '%s' "$line" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')" - [ -z "$line" ] && continue - P="$(printf '%s' "$line" | sed -E 's/^-[[:space:]]+//; s/^"//; s/"$//; s/^'\''//; s/'\''$//')" - [ -z "$P" ] && continue - PATH_SET_ARGS+=( --set "ingress.paths[$INDEX]=$P" ) - INDEX=$((INDEX+1)) - done <<< "$CLEAN_PATHS" - fi - - EXTRA_SET_ARGS="" + # Build "--set key=value" pairs as an array so values containing spaces stay intact + # (an unquoted string would word-split them). + EXTRA_SET_ARGS=() if [ -n "${EXTRA_SET_VALUES// /}" ]; then while IFS= read -r line; do - [ -z "${line}" ] && continue KV=$(echo "$line" | sed 's/^ *//;s/ *$//') [ -z "$KV" ] && continue - EXTRA_SET_ARGS+=" --set $KV" + EXTRA_SET_ARGS+=( --set "$KV" ) done <<< "$EXTRA_SET_VALUES" fi + # extra_args is raw CLI text (e.g. "--debug --wait"); split into argv tokens. + read -ra EXTRA_ARGS_ARR <<< "${EXTRA_ARGS:-}" + + ATOMIC_ARGS=() + if [ "${ATOMIC}" = "true" ]; then + ATOMIC_ARGS+=( --atomic ) + fi + echo "Executing Helm upgrade with base settings plus extras..." + # The ${arr[@]+"${arr[@]}"} guard expands to nothing when the array is empty, avoiding + # an "unbound variable" error under `set -u` on bash < 4.4 (BASE_SET_ARGS is always set). helm upgrade "${APP_NAME}" "$CHART_REF" \ --install \ --namespace "${NAMESPACE}" \ --create-namespace \ - "${BASE_SET_ARGS[@]}" "${PATH_SET_ARGS[@]}" ${EXTRA_SET_ARGS} \ + "${BASE_SET_ARGS[@]}" ${EXTRA_SET_ARGS[@]+"${EXTRA_SET_ARGS[@]}"} \ --timeout "${HELM_TIMEOUT}" \ - ${EXTRA_ARGS} - echo "CHECKPOINT_2_STATUS=✅ PASSED" >> "$GITHUB_ENV" + ${ATOMIC_ARGS[@]+"${ATOMIC_ARGS[@]}"} ${EXTRA_ARGS_ARR[@]+"${EXTRA_ARGS_ARR[@]}"} + echo "HELM_GENERIC_DEPLOY_STATUS=✅ PASSED" >> "$GITHUB_ENV" echo "::notice title=✅ [HELM] Helm deploy complete::App: ${APP_NAME}, namespace: ${NAMESPACE}" echo "::endgroup::" @@ -279,10 +337,30 @@ runs: set -euo pipefail helm status "$APP_NAME" -n "$NAMESPACE" || true echo "---" - kubectl get pods -n "$NAMESPACE" -l app.kubernetes.io/instance="$APP_NAME" || true + # Charts in use label pods differently (app.kubernetes.io/instance= on the gateway + # chart, app= on the ingress chart); show whichever selector matches. + SELECTOR="app.kubernetes.io/instance=$APP_NAME" + if [ -z "$(kubectl get pods -n "$NAMESPACE" -l "$SELECTOR" --no-headers 2>/dev/null)" ]; then + SELECTOR="app=$APP_NAME" + fi + echo "Pods (selector: ${SELECTOR}):" + kubectl get pods -n "$NAMESPACE" -l "$SELECTOR" || true - name: Report failure if: failure() shell: bash + env: + APP_NAME: ${{ inputs.app_name }} + NAMESPACE: ${{ inputs.namespace }} + run: | + echo "::error title=❌ [HELM] Deploy failed::App '${APP_NAME}' in namespace '${NAMESPACE}'. Checkpoints — 1) Kubeconfig: ${HELM_GENERIC_KUBECONFIG_STATUS:-⏭️ Not reached} | 2) Helm deploy: ${HELM_GENERIC_DEPLOY_STATUS:-⏭️ Not reached}." + + - name: Clean up kubeconfig + if: always() + shell: bash + env: + KCFG_PATH: ${{ steps.kube.outputs.kubeconfig_path }} run: | - echo "::error title=❌ [HELM] Deploy failed::App '${{ inputs.app_name }}' in namespace '${{ inputs.namespace }}'. Checkpoints — 1) Kubeconfig: ${CHECKPOINT_1_STATUS:-⏭️ Not reached} | 2) Helm deploy: ${CHECKPOINT_2_STATUS:-⏭️ Not reached}." + # Remove the decoded kubeconfig so the credential does not linger (matters on + # self-hosted/reused runners). No-op if the step never produced a path. + [ -n "${KCFG_PATH:-}" ] && rm -f "$KCFG_PATH" || true diff --git a/.github/workflows/generic-chart-helm.yml b/.github/workflows/generic-chart-helm.yml index b6b7d32..568eac7 100644 --- a/.github/workflows/generic-chart-helm.yml +++ b/.github/workflows/generic-chart-helm.yml @@ -85,6 +85,11 @@ on: required: false default: '' type: string + atomic: + description: "If 'true', pass --atomic to helm upgrade so a failed deploy rolls back automatically (implies --wait)." + required: false + default: 'true' + type: string helm-set-values: description: 'Development Helm set values (comma-separated: key1=value1,key2=value2)' required: false @@ -130,12 +135,34 @@ on: type: string init-job-secret-name: description: |- - Name of the Kubernetes Secret (in the target namespace) containing a 'connection-string' - key with the doadmin credentials. Required when init-job-image is set. + Name of the Kubernetes Secret (in the target namespace) containing the connection-string + key (see init-job-secret-key) with the doadmin credentials. Required when init-job-image is set. Example: mealivery-db-admin-secret required: false default: '' type: string + init-job-command: + description: |- + Command (space-separated) the migration Job runs, e.g. 'dotnet Mealivery.Api.dll --migrate'. + Leave empty to keep the historical default ('dotnet SW.Shanap.Web.dll --migrate'). + required: false + default: '' + type: string + init-job-secret-key: + description: Key in init-job-secret-name holding the connection string (default 'connection-string'). + required: false + default: '' + type: string + init-job-connstring-env: + description: Env var name the connection string is injected into (default 'ConnectionStrings__ShanapDb'). + required: false + default: '' + type: string + init-job-environment-env: + description: Env var name carrying the environment name in the migration Job (default 'ASPNETCORE_ENVIRONMENT'). + required: false + default: '' + type: string secrets: # Container registry secrets @@ -472,11 +499,11 @@ jobs: # Helm timing/flags helm_timeout: ${{ inputs.helm-timeout-minutes != '' && format('{0}m', inputs.helm-timeout-minutes) || '10m' }} extra_args: "--wait" + atomic: ${{ inputs.atomic }} # Translate your previous comma-joined --set into line-based entries extra_set_values: | image.repo=${{ env.CONTAINER_REGISTRY }} - app.name=${{ inputs.app-name }} app.version=${{ needs.version.outputs.version }} image.overrideName=${{ inputs.app-name }} image.overrideVersion=${{ needs.version.outputs.version }} @@ -492,6 +519,10 @@ jobs: kubeconfig_data: ${{ secrets.kubeconfig }} init_job_image: ${{ inputs.init-job-image }} init_job_secret_name: ${{ inputs.init-job-secret-name }} + init_job_command: ${{ inputs.init-job-command }} + init_job_secret_key: ${{ inputs.init-job-secret-key }} + init_job_connstring_env: ${{ inputs.init-job-connstring-env }} + init_job_environment_env: ${{ inputs.init-job-environment-env }} environment: ${{ inputs.environment }} - name: Confirm Helm deployed diff --git a/.github/workflows/generic-gateway-helm-template.yml b/.github/workflows/generic-gateway-helm-template.yml index 8078796..8de69ae 100644 --- a/.github/workflows/generic-gateway-helm-template.yml +++ b/.github/workflows/generic-gateway-helm-template.yml @@ -115,6 +115,11 @@ on: required: false default: '' type: string + atomic: + description: "If 'true', pass --atomic to helm upgrade so a failed deploy rolls back automatically (implies --wait)." + required: false + default: 'true' + type: string helm-set-values: description: Additional Helm set values line or comma separated required: false @@ -255,6 +260,28 @@ on: required: false default: '' type: string + init-job-command: + description: |- + Command (space-separated) the migration Job runs. Leave empty to keep the + historical default ('dotnet SW.Shanap.Web.dll --migrate'). + required: false + default: '' + type: string + init-job-secret-key: + description: Key in init-job-secret-name holding the connection string (default 'connection-string'). + required: false + default: '' + type: string + init-job-connstring-env: + description: Env var name the connection string is injected into (default 'ConnectionStrings__ShanapDb'). + required: false + default: '' + type: string + init-job-environment-env: + description: Env var name carrying the environment name in the migration Job (default 'ASPNETCORE_ENVIRONMENT'). + required: false + default: '' + type: string secrets: registry-username: @@ -695,6 +722,9 @@ jobs: fi KCFG_PATH="$RUNNER_TEMP/kubeconfig" + # Remove the decoded kubeconfig when this step exits so the credential does not linger + # (matters on self-hosted/reused runners). + trap 'rm -f "$KCFG_PATH"' EXIT if echo "$KUBECONFIG_DATA" | grep -q 'apiVersion:'; then printf "%s" "$KUBECONFIG_DATA" > "$KCFG_PATH" else @@ -866,9 +896,9 @@ jobs: chart_repo: ${{ inputs.chart-repo }} helm_timeout: ${{ inputs.helm-timeout-minutes != '' && format('{0}m', inputs.helm-timeout-minutes) || '10m' }} extra_args: --wait + atomic: ${{ inputs.atomic }} extra_set_values: | image.repo=${{ env.CONTAINER_REGISTRY }} - app.name=${{ inputs.app-name || github.repository }} app.version=${{ needs.version.outputs.version }} image.overrideName=${{ inputs.app-name }} image.overrideVersion=${{ needs.version.outputs.version }} @@ -882,6 +912,10 @@ jobs: kubeconfig_data: ${{ secrets.kubeconfig }} init_job_image: ${{ inputs.init-job-image }} init_job_secret_name: ${{ inputs.init-job-secret-name }} + init_job_command: ${{ inputs.init-job-command }} + init_job_secret_key: ${{ inputs.init-job-secret-key }} + init_job_connstring_env: ${{ inputs.init-job-connstring-env }} + init_job_environment_env: ${{ inputs.init-job-environment-env }} environment: ${{ inputs.environment }} - name: Confirm Helm deployed diff --git a/.github/workflows/sw-cicd.yml b/.github/workflows/sw-cicd.yml index c32d6a7..9baeb79 100644 --- a/.github/workflows/sw-cicd.yml +++ b/.github/workflows/sw-cicd.yml @@ -272,9 +272,11 @@ jobs: - name: Announce ci job run: | echo "::notice title=🐳 [DOCKER] CI Build — Job Started::Version: ${{ needs.version.outputs.version }} | Registry: ${{ inputs.container-registry }} | Triggered by: ${{ github.actor }}" - echo "CHECKPOINT_1_STATUS=⏳ Pending" >> "$GITHUB_ENV" - echo "CHECKPOINT_2_STATUS=⏳ Pending" >> "$GITHUB_ENV" - echo "CHECKPOINT_3_STATUS=⏳ Pending" >> "$GITHUB_ENV" + { + echo "CHECKPOINT_1_STATUS=⏳ Pending" + echo "CHECKPOINT_2_STATUS=⏳ Pending" + echo "CHECKPOINT_3_STATUS=⏳ Pending" + } >> "$GITHUB_ENV" - name: Checkout code uses: actions/checkout@v6 diff --git a/AGENTS.md b/AGENTS.md index 23987f3..9de2ca3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -272,7 +272,7 @@ steps: ### Helm - `helm-deploy` — Profile-based deploy (`registry_profile` selects dynamic secrets). Supports `init_job_image` for database migration Jobs before deploy. - `helm-deploy-s9generic` — Deploy using `s9genericchart` from `https://charts.sf9.io`. Handles `set-values` (`--set`) and `set-string-values` (`--set-string`) separately. -- `helm-generic` — Checkout + lint + package + push (single composite for chart CI). +- `helm-generic` — Deploy a Helm chart via `helm upgrade --install`, with an optional pre-deploy DB migration Job (`init_job_image` + `init_job_command`). Internal building block for the `generic-chart-helm` (ingress-nginx) and `generic-gateway-helm-template` (Gateway API) reusable workflows. - `helm-package-push` — Package a chart and push to OCI registry. ### .NET diff --git a/README.md b/README.md index cb96008..3d927d4 100644 --- a/README.md +++ b/README.md @@ -759,7 +759,7 @@ uses: simplify9/.github/.github/actions/@main |---|---|---| | `helm-deploy` | Profile-based deploy; supports `init_job_image` for pre-deploy DB migration Jobs | `app_name`, `namespace`, `kubeconfig_data` | | `helm-deploy-s9generic` | Deploy `s9genericchart` from `https://charts.sf9.io`; handles `set-values` (`--set`) and `set-string-values` (`--set-string`) separately | `chart-name`, `chart-version`, `kubeconfig` | -| `helm-generic` | Checkout + Helm lint + package + push in a single composite | `app-name`, `version` | +| `helm-generic` | Deploy a Helm chart (`helm upgrade --install`) with optional pre-deploy DB migration Job. Used by the `generic-chart-helm` and `generic-gateway-helm-template` reusable workflows | `app_name`, `namespace`, `kubeconfig_data` | | `helm-package-push` | Package chart and push to OCI registry | `chart-path`, `chart-name`, `chart-version` | ### .NET