From fae8819c444d5dc3d1211a6d4964cf1eb0b735ad Mon Sep 17 00:00:00 2001 From: "John C. Bland II" Date: Fri, 28 Aug 2026 15:15:21 -0500 Subject: [PATCH] feat(action): add deployment-id input for per-attempt notification dedupe ArgoCD's notification `oncePer` trigger dedupes on whichever field it is pointed at. Keying it on the application commit means a rollback -- replaying a commit ArgoCD has already notified for -- is silently suppressed, so the rollback reports no status at all. Add an optional `deployment-id` input whose value is written to the generated `config.yaml` as `deployment_id`, giving `oncePer` a field that is unique per deploy attempt rather than per code state. The value is stamped with `yq` and `strenv` rather than interpolated into the YAML literal, so IDs that would otherwise be type-coerced or break the document (`01234`, `true`, values containing `:`) round-trip correctly, and the input never reaches the shell command string. Left at its empty default the step is skipped entirely and the key is omitted, keeping `config.yaml` byte-identical for existing callers. This matters because the action only commits to the GitOps repo when the rendered output actually changes -- always emitting the key would push a config-only commit, and an ArgoCD sync, for every app on upgrade. Covered by the helm raw tests in both directions: the value round-trips through the committed `config.yaml` when passed, and the key is absent when it is not. --- .../test-helm-raw-default-kube-version.yml | 15 ++++++++ .github/workflows/test-helm-raw.yml | 16 +++++++++ README.md | 35 +++++++++++++++++-- README.yaml | 34 ++++++++++++++++++ action.yml | 15 +++++++- 5 files changed, 112 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-helm-raw-default-kube-version.yml b/.github/workflows/test-helm-raw-default-kube-version.yml index e817e2b..a453385 100644 --- a/.github/workflows/test-helm-raw-default-kube-version.yml +++ b/.github/workflows/test-helm-raw-default-kube-version.yml @@ -170,6 +170,21 @@ jobs: expected: "networking.k8s.io/v1" actual: "${{ steps.ingress.outputs.value }}" + - name: Get Deployment ID presence + id: deployment_id + shell: bash + run: |- + has_deployment_id=$( \ + yq eval-all 'has("deployment_id")' \ + ./assert/plat/ue2-sandbox/apps/staging/test-app/config.yaml \ + ) + echo "value=${has_deployment_id}" >> $GITHUB_OUTPUT + + - uses: nick-fields/assert-action@0efd6166067d9c59d89c710fab4f79fb066f8985 # v4.0.1 + with: + expected: "false" + actual: "${{ steps.deployment_id.outputs.value }}" + teardown: runs-on: ubuntu-latest needs: [assert] diff --git a/.github/workflows/test-helm-raw.yml b/.github/workflows/test-helm-raw.yml index 814105b..a7f1996 100644 --- a/.github/workflows/test-helm-raw.yml +++ b/.github/workflows/test-helm-raw.yml @@ -96,6 +96,7 @@ jobs: github-pat: ${{ steps.github-app.outputs.token }} image: nginx image-tag: ${{ needs.setup.outputs.random }} + deployment-id: ${{ github.run_id }}-${{ github.run_attempt }} env: AWS_ACCESS_KEY_ID: test AWS_SECRET_ACCESS_KEY: test @@ -191,6 +192,21 @@ jobs: expected: "staging.test-app" actual: "${{ steps.name.outputs.value }}" + - name: Get Deployment ID + id: deployment_id + shell: bash + run: |- + deployment_id=$( \ + yq eval-all '.deployment_id' \ + ./assert/plat/ue2-sandbox/apps/staging/test-app/config.yaml \ + ) + echo "value=${deployment_id}" >> $GITHUB_OUTPUT + + - uses: nick-fields/assert-action@0efd6166067d9c59d89c710fab4f79fb066f8985 # v4.0.1 + with: + expected: "${{ github.run_id }}-${{ github.run_attempt }}" + actual: "${{ steps.deployment_id.outputs.value }}" + teardown: runs-on: ubuntu-latest needs: [assert] diff --git a/README.md b/README.md index 336e839..c0923ae 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,7 @@ Deploy environment operation: deploy debug: false synchronously: true + deployment-id: ${{ github.run_id }}-${{ github.run_attempt }} ``` @@ -125,13 +126,42 @@ Destroy environment ``` +### Deduping ArgoCD notifications per deploy attempt + +ArgoCD's notification `oncePer` trigger dedupes on whichever field you point it at. Keying it on the +application commit means a rollback — replaying a commit ArgoCD has already notified for — is silently +suppressed, and the rollback reports no status at all. + +Pass `deployment-id` a value that is unique per *deploy attempt* rather than per code state, and point +`oncePer` at it instead: + +```yaml + - name: Deploy + uses: cloudposse/github-action-deploy-argocd@main + with: + # ... + deployment-id: ${{ github.run_id }}-${{ github.run_attempt }} +``` + +The value is written to the generated `config.yaml` alongside the rest of the deploy metadata: + +```yaml + app_repository: acme/example-app + app_commit: 6e6a0e1b0e0c4b2a9f1d3c5e7a9b1d3f5a7c9e1b + app_hostname: https://example-app.example.com + name: preview.example-app + namespace: preview + manifests: plat/ue2-sandbox/apps/preview/example-app/manifests + deployment_id: "12345678901-2" +``` + +The input is optional. Left at its empty default, the `deployment_id` key is omitted from `config.yaml` +entirely, so callers that do not set it get byte-identical output. - - ## Inputs @@ -147,6 +177,7 @@ Destroy environment | commit-status-github-token | Github token to access the app repository. Defaults to github-pat if not set. | N/A | false | | commit-timeout | Commit timeout (in seconds) | 60 | false | | debug | Debug mode | false | false | +| deployment-id | Unique identifier for this deploy attempt (for example, the GitHub run ID joined with the run attempt). When set, it is written to `config.yaml` as `deployment\_id`, which lets ArgoCD dedupe notifications per deploy attempt instead of per commit. Left unset, the key is omitted from `config.yaml`. | | false | | environment | Helmfile environment | preview | false | | github-pat | Github PAT to access argocd configuration repository | N/A | true | | gitref-sha | Git SHA (Depricated. Use `ref` instead) | | false | diff --git a/README.yaml b/README.yaml index 705e99e..3fec7e8 100644 --- a/README.yaml +++ b/README.yaml @@ -88,6 +88,7 @@ usage: |- operation: deploy debug: false synchronously: true + deployment-id: ${{ github.run_id }}-${{ github.run_attempt }} ``` @@ -128,5 +129,38 @@ usage: |- debug: false ``` + + ### Deduping ArgoCD notifications per deploy attempt + + ArgoCD's notification `oncePer` trigger dedupes on whichever field you point it at. Keying it on the + application commit means a rollback — replaying a commit ArgoCD has already notified for — is silently + suppressed, and the rollback reports no status at all. + + Pass `deployment-id` a value that is unique per *deploy attempt* rather than per code state, and point + `oncePer` at it instead: + + ```yaml + - name: Deploy + uses: cloudposse/github-action-deploy-argocd@main + with: + # ... + deployment-id: ${{ github.run_id }}-${{ github.run_attempt }} + ``` + + The value is written to the generated `config.yaml` alongside the rest of the deploy metadata: + + ```yaml + app_repository: acme/example-app + app_commit: 6e6a0e1b0e0c4b2a9f1d3c5e7a9b1d3f5a7c9e1b + app_hostname: https://example-app.example.com + name: preview.example-app + namespace: preview + manifests: plat/ue2-sandbox/apps/preview/example-app/manifests + deployment_id: "12345678901-2" + ``` + + The input is optional. Left at its empty default, the `deployment_id` key is omitted from `config.yaml` + entirely, so callers that do not set it get byte-identical output. + include: [] contributors: [] diff --git a/action.yml b/action.yml index 5d05ff6..e42166a 100644 --- a/action.yml +++ b/action.yml @@ -117,7 +117,11 @@ inputs: kube-version: description: "Kubernetes version for helm/helmfile rendering (e.g. `1.28`). When provided, skips SSM metadata lookup via chamber." required: false - default: "" + default: "" + deployment-id: + description: "Unique identifier for this deploy attempt (for example, the GitHub run ID joined with the run attempt). When set, it is written to `config.yaml` as `deployment_id`, which lets ArgoCD dedupe notifications per deploy attempt instead of per commit. Left unset, the key is omitted from `config.yaml`." + required: false + default: "" outputs: webapp-url: description: "Web Application url" @@ -378,6 +382,15 @@ runs: namespace: ${{ inputs.namespace }} manifests: ${{ steps.config.outputs.path }}/manifests + - name: Stamp deployment id + if: ${{ inputs.operation == 'deploy' && inputs.deployment-id != '' }} + shell: bash + env: + DEPLOYMENT_ID: ${{ inputs.deployment-id }} + CONFIG_FILE: ${{ steps.config.outputs.tmp }}/config.yaml + run: |- + yq --exit-status --no-colors --inplace eval '.deployment_id = strenv(DEPLOYMENT_ID)' "${CONFIG_FILE}" + - name: Push to Github uses: nick-fields/retry@v4 id: git