name: Deploy on: push: branches: [main] permissions: id-token: write contents: read concurrency: group: deploy cancel-in-progress: false jobs: deploy: uses: Sea-Haven-Industries/.github/.github/workflows/cd-sam.yaml@main with: stack-name: afterhours-shift-manager cfn-role-arn: arn:aws:iam::328440206208:role/github-cfn-execution-role secrets: deploy-role-arn: ${{ secrets.AWS_DEPLOY_ROLE_ARN }} parameter-overrides: ${{ secrets.SAM_PARAMETER_OVERRIDES }} # Tag + announce a release once the deploy succeeds. This lives in the deploy # workflow (gated on `needs: deploy`) rather than a separate workflow_run- # triggered job on purpose: a push-to-main run is a trusted context, so # checking out and running repo code with write/OIDC is safe here — unlike # workflow_run, which CodeQL (rightly) flags for untrusted checkout + cache # poisoning. Gating on `needs: deploy` still guarantees we never announce a # version that isn't live, and the `deploy` concurrency group serializes # releases. When the top CHANGELOG version already has a Release, this no-ops. release: needs: deploy runs-on: ubuntu-latest permissions: contents: write # create the tag + GitHub Release id-token: write # OIDC to assume the notifier-invoke role steps: - uses: actions/checkout@v7 with: fetch-depth: 0 fetch-tags: true - uses: actions/setup-python@v6 with: python-version: "3.12" - name: Determine release id: rel env: GH_TOKEN: ${{ github.token }} run: | TOP=$(python scripts/changelog_cli.py top-version CHANGELOG.md) if [ -z "$TOP" ]; then echo "No version entry in CHANGELOG.md — nothing to release." echo "release=false" >> "$GITHUB_OUTPUT"; exit 0 fi PREV=$(git tag -l 'v*' --sort=-v:refname | head -1) PREV="${PREV:-v0.0.0}" KIND=$(python scripts/changelog_cli.py bump-kind CHANGELOG.md "$PREV") RELEASE_EXISTS=false gh release view "v$TOP" >/dev/null 2>&1 && RELEASE_EXISTS=true echo "version=$TOP" >> "$GITHUB_OUTPUT" echo "kind=$KIND" >> "$GITHUB_OUTPUT" # Act only on a clean SemVer bump whose Release isn't published yet. if [ "$KIND" != "none" ] && [ "$RELEASE_EXISTS" = "false" ]; then echo "release=true" >> "$GITHUB_OUTPUT" else echo "release=false" >> "$GITHUB_OUTPUT" echo "v$TOP: kind=$KIND release_exists=$RELEASE_EXISTS — no action." fi - name: Build release notes if: ${{ steps.rel.outputs.release == 'true' }} run: | python scripts/changelog_cli.py payload CHANGELOG.md "${{ steps.rel.outputs.version }}" > payload.json python -c "import json; print(json.load(open('payload.json'))['notes'])" > notes.md # Announce BEFORE publishing the Release: the Release is the durable "done" # marker (the step above skips once it exists), so announcing first keeps # this retryable. Minor/major only, and only once the invoke-role variable # has been bootstrapped (see README). - name: Configure AWS credentials if: ${{ steps.rel.outputs.release == 'true' && steps.rel.outputs.kind != 'patch' && vars.RELEASE_NOTIFY_INVOKE_ROLE_ARN != '' }} uses: aws-actions/configure-aws-credentials@254c19bd240aabef8777f48595e9d2d7b972184b # v6 with: role-to-assume: ${{ vars.RELEASE_NOTIFY_INVOKE_ROLE_ARN }} aws-region: us-east-1 - name: Announce in Slack if: ${{ steps.rel.outputs.release == 'true' && steps.rel.outputs.kind != 'patch' && vars.RELEASE_NOTIFY_INVOKE_ROLE_ARN != '' }} run: | aws lambda invoke \ --function-name afterhours-release-notifier \ --cli-binary-format raw-in-base64-out \ --payload file://payload.json \ --output json response.json > invoke-meta.json # aws lambda invoke only emits a FunctionError key when the handler errored. if grep -q '"FunctionError"' invoke-meta.json; then echo "::error::release-notifier returned an error"; cat response.json; exit 1 fi echo "Announced v${{ steps.rel.outputs.version }}." - name: Warn if announcement skipped (not bootstrapped) if: ${{ steps.rel.outputs.release == 'true' && steps.rel.outputs.kind != 'patch' && vars.RELEASE_NOTIFY_INVOKE_ROLE_ARN == '' }} run: echo "::warning::RELEASE_NOTIFY_INVOKE_ROLE_ARN is unset — tagging + releasing but not announcing. Set the repo variable from the stack output." - name: Publish GitHub Release if: ${{ steps.rel.outputs.release == 'true' }} env: GH_TOKEN: ${{ github.token }} run: | # gh creates the tag at the deployed commit and the Release together. gh release create "v${{ steps.rel.outputs.version }}" \ --repo "${{ github.repository }}" \ --title "v${{ steps.rel.outputs.version }}" \ --notes-file notes.md \ --target "${{ github.sha }}"