From f590cf7ed476aa4b69582dd1b9fd342934da45b3 Mon Sep 17 00:00:00 2001 From: Muhammad Tahir Nawaz Date: Fri, 24 Jul 2026 13:06:40 +0500 Subject: [PATCH] chore: fix env-file download to not require actions: read PR #41 merged a version that requested actions: read and used gh run download. Reusable workflows can't request a permission the caller doesn't grant, so callers cap the nested job at actions: none and the workflow is rejected as invalid (deploy job "requesting actions: read, but is only allowed actions: none"). Switch back to actions/download-artifact@v4, which authenticates via the injected runtime token and needs no actions: permission, and get retry behavior by re-running the step up to 3 times with backoff (continue-on-error, gated on the prior attempt's outcome). deployment.yaml keeps a single stable env_artifact outcome (a final step that succeeds iff a .env was downloaded) so the existing downstream gates are unchanged and a genuinely absent artifact still skips cleanly. --- .github/workflows/deployment.yaml | 71 +++++++++++++++++------- .github/workflows/integration-tests.yaml | 55 +++++++++++------- 2 files changed, 87 insertions(+), 39 deletions(-) diff --git a/.github/workflows/deployment.yaml b/.github/workflows/deployment.yaml index 4da0cdb..9fe0100 100644 --- a/.github/workflows/deployment.yaml +++ b/.github/workflows/deployment.yaml @@ -116,7 +116,6 @@ jobs: # in their own workflow, otherwise GitHub will not issue an OIDC token and # the Authenticate with GCP step will fail. permissions: - actions: read # needed by `gh run download` to fetch the env-file artifact contents: write id-token: write @@ -145,29 +144,61 @@ jobs: # Secret Manager secrets (Cloud Run), so one image serves all environments. # The image itself only contains the repo's committed .env (common vars). # download-artifact@v4 resolves the artifact through the run-scoped Artifacts - # v4 API. On ephemeral ARC pods the results service is occasionally not yet - # consistent for the run when the pod starts, returning a transient 404 that - # the action would surface as a hard miss. That would make this step "fail" - # and, via the outcome gate below, silently deploy WITHOUT the env secrets. - # Retry the download so a transient 404 doesn't get mistaken for "no artifact". - # A genuinely absent artifact (caller has no prepare-env job) still exhausts - # the loop and leaves outcome != success, which the gates below tolerate. - - name: Download .env artifact + # v4 API (authenticated by the injected runtime token, so no `actions:` + # permission is needed). On ephemeral ARC pods the results service is + # occasionally not yet consistent for the run when the pod starts, returning + # a transient 404. Without a retry that 404 is mistaken for "no artifact" and, + # via the outcome gate below, the service deploys WITHOUT its env secrets. + # Re-run the action a few times with a gap; each attempt is continue-on-error. + # The final `env_artifact` step (whose outcome the gates below key on) then + # succeeds iff a .env was actually downloaded, so a genuinely absent artifact + # still leaves outcome != success and the gates skip cleanly as before. + - name: Download .env artifact (attempt 1) + id: dl1 + if: ${{ inputs.deploy_type != 'release-only' }} + continue-on-error: true + uses: actions/download-artifact@v4 + with: + name: env-file + path: ${{ runner.temp }}/env-file + + - name: Wait before .env retry 2 + if: ${{ inputs.deploy_type != 'release-only' && steps.dl1.outcome == 'failure' }} + run: sleep 15 + - name: Download .env artifact (attempt 2) + id: dl2 + if: ${{ inputs.deploy_type != 'release-only' && steps.dl1.outcome == 'failure' }} + continue-on-error: true + uses: actions/download-artifact@v4 + with: + name: env-file + path: ${{ runner.temp }}/env-file + + - name: Wait before .env retry 3 + if: ${{ inputs.deploy_type != 'release-only' && steps.dl1.outcome == 'failure' && steps.dl2.outcome == 'failure' }} + run: sleep 30 + - name: Download .env artifact (attempt 3) + id: dl3 + if: ${{ inputs.deploy_type != 'release-only' && steps.dl1.outcome == 'failure' && steps.dl2.outcome == 'failure' }} + continue-on-error: true + uses: actions/download-artifact@v4 + with: + name: env-file + path: ${{ runner.temp }}/env-file + + # Single, stable signal the downstream gates key on: success iff a .env was + # actually downloaded by any attempt above. A genuinely absent artifact + # leaves this at 'failure' (continue-on-error keeps the deploy going). + - name: Resolve env-file download outcome id: env_artifact if: ${{ inputs.deploy_type != 'release-only' }} continue-on-error: true - env: - GH_TOKEN: ${{ github.token }} run: | - for i in 1 2 3 4 5; do - if gh run download "${{ github.run_id }}" -n env-file -D "${{ runner.temp }}/env-file"; then - echo "Downloaded env-file on attempt $i" - exit 0 - fi - echo "env-file download attempt $i failed (transient 404 or artifact absent), retrying in 15s..." - sleep 15 - done - echo "env-file artifact not downloadable after 5 attempts; deploy will proceed without attached env secrets" + if find "${{ runner.temp }}/env-file" -name '.env' -type f | grep -q .; then + echo "env-file present" + exit 0 + fi + echo "env-file not downloaded after 3 attempts; deploy will proceed without attached env secrets" exit 1 - name: Build Docker image diff --git a/.github/workflows/integration-tests.yaml b/.github/workflows/integration-tests.yaml index 66661d7..37d815b 100644 --- a/.github/workflows/integration-tests.yaml +++ b/.github/workflows/integration-tests.yaml @@ -121,7 +121,6 @@ jobs: runs-on: ${{ inputs.runner != '' && inputs.runner || 'arc-shared-dev' }} environment: development permissions: - actions: read # needed by `gh run download` to fetch the env-file artifact contents: read id-token: write pull-requests: write @@ -131,24 +130,42 @@ jobs: uses: actions/checkout@v3 # download-artifact@v4 resolves the artifact through the run-scoped Artifacts - # v4 API. On ephemeral ARC pods the results service is occasionally not yet - # consistent for the run when the pod starts, returning a *non-retryable* - # "404 workflow run not found". That window closes within seconds, so retry - # the download instead of failing the whole job on the first miss. - - name: Download .env artifact - env: - GH_TOKEN: ${{ github.token }} - run: | - for i in 1 2 3 4 5; do - if gh run download "${{ github.run_id }}" -n env-file -D .; then - echo "Downloaded env-file on attempt $i" - exit 0 - fi - echo "env-file download attempt $i failed (artifact backend not consistent yet), retrying in 15s..." - sleep 15 - done - echo "env-file artifact never became downloadable after 5 attempts" - exit 1 + # v4 API (authenticated by the injected runtime token, so no `actions:` + # permission is needed). On ephemeral ARC pods the results service is + # occasionally not yet consistent for the run when the pod starts, returning + # a *non-retryable* "404 workflow run not found" that fails the whole job. + # The window closes within seconds, so re-run the action a few times with a + # gap. Each attempt is continue-on-error and gated on the previous outcome; + # the final attempt is not, so genuine failures still fail the job. + - name: Download .env artifact (attempt 1) + id: dl1 + continue-on-error: true + uses: actions/download-artifact@v4 + with: + name: env-file + path: . + + - name: Wait before .env retry 2 + if: ${{ steps.dl1.outcome == 'failure' }} + run: sleep 15 + - name: Download .env artifact (attempt 2) + id: dl2 + if: ${{ steps.dl1.outcome == 'failure' }} + continue-on-error: true + uses: actions/download-artifact@v4 + with: + name: env-file + path: . + + - name: Wait before .env retry 3 + if: ${{ steps.dl1.outcome == 'failure' && steps.dl2.outcome == 'failure' }} + run: sleep 30 + - name: Download .env artifact (attempt 3, final) + if: ${{ steps.dl1.outcome == 'failure' && steps.dl2.outcome == 'failure' }} + uses: actions/download-artifact@v4 + with: + name: env-file + path: . - name: Load environment variables from .env run: |