From 55c88e67b4b0f9efd57ac89339b99ca56973068e Mon Sep 17 00:00:00 2001 From: Henrik Brautaset Aronsen Date: Fri, 12 Jun 2026 13:56:11 +0200 Subject: [PATCH 1/2] fix: collapse Slack notification jobs into helm-deploy steps The deploy workflow graph showed five Slack jobs (slack-pending, slack-pending-notify, prepare-slack, slack-notify, slack-approved-react) as skipped boxes on every run without Slack enabled. - Merge pending-approval check, message build and post into a single slack-pending job using the gha-slack composite actions. It must stay a separate job: the environment approval gate blocks helm-deploy's steps, so a pre-approval notification cannot run inside it. - Move the approval reaction and deploy status message into conditional steps in helm-deploy. - Make Slack failures non-blocking via continue-on-error, so a Slack outage no longer fails a successful deploy. Inputs, outputs and the secrets: inherit requirement are unchanged. --- .github/workflows/deploy.yml | 107 +++++++++++++++++------------------ 1 file changed, 52 insertions(+), 55 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index bd7bcbb..b31953b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -68,13 +68,16 @@ on: description: "Helm Deploy Status 0: Success, 1: Failed, 2: Rollback, 3: Rollback Failed" value: ${{ jobs.helm-deploy.outputs.HELM_DEPLOY_STATUS }} jobs: + # Posts the "Approval Needed" Slack message. This must be a separate job: + # the environment approval gate on helm-deploy blocks its steps from + # starting, so a pre-approval notification can never run inside it. slack-pending: if: inputs.slack_channel_id != '' runs-on: ubuntu-24.04 - timeout-minutes: 1 + timeout-minutes: 5 + continue-on-error: true outputs: - message: ${{ steps.build-message.outputs.message }} - blocks: ${{ steps.build-message.outputs.blocks }} + message_ts: ${{ steps.slack-post.outputs.message_ts }} steps: - id: check-approval name: check if environment has approval gate @@ -127,16 +130,18 @@ jobs: echo "blocks<> "$GITHUB_OUTPUT" echo "$BLOCKS" >> "$GITHUB_OUTPUT" echo "GHA_SLACK_BLOCKS_EOF" >> "$GITHUB_OUTPUT" - slack-pending-notify: - if: needs.slack-pending.outputs.message != '' - needs: [slack-pending] - uses: entur/gha-slack/.github/workflows/post.yml@v3 - with: - channel_id: ${{ inputs.slack_channel_id }} - message: ${{ needs.slack-pending.outputs.message }} - blocks: ${{ needs.slack-pending.outputs.blocks }} - secrets: inherit + - id: slack-post + name: Post approval message to Slack + if: steps.check-approval.outputs.has_approval_gate == 'true' + uses: entur/gha-slack/.github/actions/post@v3 + with: + channel_id: ${{ inputs.slack_channel_id }} + message: ${{ steps.build-message.outputs.message }} + blocks: ${{ steps.build-message.outputs.blocks }} + token: ${{ secrets.SLACK_BOT_TOKEN }} helm-deploy: + needs: [slack-pending] + if: ${{ !cancelled() }} runs-on: ubuntu-24.04 timeout-minutes: ${{ inputs.timeout_minutes }} name: Helm deploy @@ -171,6 +176,18 @@ jobs: outputs: HELM_DEPLOY_STATUS: ${{ steps.helm-deploy.outputs.HELM_DEPLOY_STATUS }} steps: + # This job only starts once the environment approval gate (if any) has + # been passed, so reaching this step means the deployment was approved. + - id: slack-approved-react + name: React to Slack approval message + if: needs.slack-pending.outputs.message_ts != '' + continue-on-error: true + uses: entur/gha-slack/.github/actions/react@v3 + with: + channel_id: ${{ inputs.slack_channel_id }} + message_ts: ${{ needs.slack-pending.outputs.message_ts }} + emoji: white_check_mark + token: ${{ secrets.SLACK_BOT_TOKEN }} - id: verify-cloud-provider if: env.GHA_HELM_DEPLOY_CLOUD_PROVIDER != 'gcp' && env.GHA_HELM_DEPLOY_CLOUD_PROVIDER != 'az' shell: bash @@ -260,28 +277,12 @@ jobs: image_set_path: ${{ env.GHA_HELM_DEPLOY_IMAGE_SET_PATH }} container_name: ${{ env.GHA_HELM_DEPLOY_CONTAINER_NAME }} registry: ${{ env.GHA_HELM_DEPLOY_REGISTRY }} - - uses: entur/gha-meta/.github/actions/posthog@v1 - id: send-analytics - name: Send analytics to PostHog - if: always() - with: - api_key: ${{ vars.POSTHOG_API_TOKEN_PLATTFORM }} - gha_repository: entur/gha-helm # This must be hardcoded to the repository where the workflow is defined. - workflow_inputs: ${{ toJSON(inputs) }} - workflow_name: helm-deploy - prepare-slack: - if: always() && inputs.slack_channel_id != '' - needs: [helm-deploy] - runs-on: ubuntu-24.04 - timeout-minutes: 1 - outputs: - message: ${{ steps.build-message.outputs.message }} - blocks: ${{ steps.build-message.outputs.blocks }} - steps: - - id: build-message + - id: build-slack-message + name: Build Slack deploy status message + if: always() && inputs.slack_channel_id != '' shell: bash env: - DEPLOY_STATUS: ${{ needs.helm-deploy.outputs.HELM_DEPLOY_STATUS }} + DEPLOY_STATUS: ${{ steps.helm-deploy.outputs.HELM_DEPLOY_STATUS }} REPOSITORY: ${{ github.repository }} ENVIRONMENT: ${{ inputs.environment }} IMAGE: ${{ inputs.image }} @@ -343,26 +344,22 @@ jobs: echo "blocks<> "$GITHUB_OUTPUT" echo "$BLOCKS" >> "$GITHUB_OUTPUT" echo "GHA_SLACK_BLOCKS_EOF" >> "$GITHUB_OUTPUT" - slack-notify: - if: always() && inputs.slack_channel_id != '' && needs.prepare-slack.outputs.message != '' - needs: [prepare-slack] - uses: entur/gha-slack/.github/workflows/post.yml@v3 - with: - channel_id: ${{ inputs.slack_channel_id }} - message: ${{ needs.prepare-slack.outputs.message }} - blocks: ${{ needs.prepare-slack.outputs.blocks }} - secrets: inherit - slack-approved-react: - if: >- - always() && - inputs.slack_channel_id != '' && - needs.slack-pending-notify.result == 'success' && - needs.helm-deploy.result != 'cancelled' && - needs.helm-deploy.result != 'skipped' - needs: [slack-pending-notify, helm-deploy] - uses: entur/gha-slack/.github/workflows/react.yml@v3 - with: - channel_id: ${{ inputs.slack_channel_id }} - message_ts: ${{ needs.slack-pending-notify.outputs.message_ts }} - emoji: white_check_mark - secrets: inherit + - id: slack-notify + name: Post deploy status to Slack + if: always() && inputs.slack_channel_id != '' + continue-on-error: true + uses: entur/gha-slack/.github/actions/post@v3 + with: + channel_id: ${{ inputs.slack_channel_id }} + message: ${{ steps.build-slack-message.outputs.message }} + blocks: ${{ steps.build-slack-message.outputs.blocks }} + token: ${{ secrets.SLACK_BOT_TOKEN }} + - uses: entur/gha-meta/.github/actions/posthog@v1 + id: send-analytics + name: Send analytics to PostHog + if: always() + with: + api_key: ${{ vars.POSTHOG_API_TOKEN_PLATTFORM }} + gha_repository: entur/gha-helm # This must be hardcoded to the repository where the workflow is defined. + workflow_inputs: ${{ toJSON(inputs) }} + workflow_name: helm-deploy From 297f9fda6a5baaee70887f16a4649540f9c8c3c8 Mon Sep 17 00:00:00 2001 From: Henrik Brautaset Aronsen Date: Mon, 15 Jun 2026 09:16:49 +0200 Subject: [PATCH 2/2] fix: harden Slack steps and surface delivery failures Follow-up to the job collapse, addressing review findings: - Bound each external Slack call with timeout-minutes: 1 so a hung or retrying Slack API (gha-slack defaults to ~5 retries over 5 min) cannot consume the deploy/rollback timeout budget. - Add continue-on-error to the status-message builder and gate the result post on its success, so a Slack-side failure can never fail an otherwise-successful deploy. - Warn (::warning::) when a deploy-status or approval notification fails or returns an empty message_ts, surfacing otherwise-silent channel / token / bot-invite misconfiguration. --- .github/workflows/deploy.yml | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index b31953b..c30e96e 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -133,14 +133,29 @@ jobs: - id: slack-post name: Post approval message to Slack if: steps.check-approval.outputs.has_approval_gate == 'true' + timeout-minutes: 1 uses: entur/gha-slack/.github/actions/post@v3 with: channel_id: ${{ inputs.slack_channel_id }} message: ${{ steps.build-message.outputs.message }} blocks: ${{ steps.build-message.outputs.blocks }} token: ${{ secrets.SLACK_BOT_TOKEN }} + # The approval post lives in this separate job, so its silent failures + # (bad channel / missing invite / no secrets: inherit) aren't visible to + # the warning in helm-deploy. post@v3 returns empty message_ts on an API + # error, so treat a non-success outcome OR an empty ts as not-delivered. + - name: Warn if approval message was not delivered + if: >- + always() && steps.check-approval.outputs.has_approval_gate == 'true' && + (steps.slack-post.outcome != 'success' || steps.slack-post.outputs.message_ts == '') + shell: bash + run: | + echo "::warning::Slack approval notification was not delivered — check slack_channel_id, that the workflow is called with 'secrets: inherit', and that the Slack bot is invited to the channel." helm-deploy: needs: [slack-pending] + # !cancelled() (not the default success()) is required: slack-pending is + # skipped when Slack is disabled, and a skipped needed job would otherwise + # skip this job too — breaking every deploy without slack_channel_id. if: ${{ !cancelled() }} runs-on: ubuntu-24.04 timeout-minutes: ${{ inputs.timeout_minutes }} @@ -182,6 +197,7 @@ jobs: name: React to Slack approval message if: needs.slack-pending.outputs.message_ts != '' continue-on-error: true + timeout-minutes: 1 uses: entur/gha-slack/.github/actions/react@v3 with: channel_id: ${{ inputs.slack_channel_id }} @@ -280,6 +296,7 @@ jobs: - id: build-slack-message name: Build Slack deploy status message if: always() && inputs.slack_channel_id != '' + continue-on-error: true shell: bash env: DEPLOY_STATUS: ${{ steps.helm-deploy.outputs.HELM_DEPLOY_STATUS }} @@ -346,14 +363,25 @@ jobs: echo "GHA_SLACK_BLOCKS_EOF" >> "$GITHUB_OUTPUT" - id: slack-notify name: Post deploy status to Slack - if: always() && inputs.slack_channel_id != '' + if: always() && steps.build-slack-message.outcome == 'success' continue-on-error: true + timeout-minutes: 1 uses: entur/gha-slack/.github/actions/post@v3 with: channel_id: ${{ inputs.slack_channel_id }} message: ${{ steps.build-slack-message.outputs.message }} blocks: ${{ steps.build-slack-message.outputs.blocks }} token: ${{ secrets.SLACK_BOT_TOKEN }} + - name: Warn if Slack notification was not delivered + if: >- + always() && inputs.slack_channel_id != '' && + (steps.slack-approved-react.outcome == 'failure' || + steps.build-slack-message.outcome == 'failure' || + steps.slack-notify.outcome == 'failure' || + (steps.slack-notify.outcome == 'success' && steps.slack-notify.outputs.message_ts == '')) + shell: bash + run: | + echo "::warning::Slack notification failed or was not delivered — check slack_channel_id, that the workflow is called with 'secrets: inherit', and that the Slack bot is invited to the channel." - uses: entur/gha-meta/.github/actions/posthog@v1 id: send-analytics name: Send analytics to PostHog