From ceecab54381e463c98e78162ad11aeb55b0cbe83 Mon Sep 17 00:00:00 2001 From: Adam Moussa Date: Fri, 18 Sep 2026 15:04:51 -0400 Subject: [PATCH] fix(cd): ignore githubdeploy description and retire leftover pointer CD SCP denies UpdateRoleDescription on githubdeploy-*, so HCP apply cannot rewrite the role description. Pointer workflows must not land on main. --- .github/workflows/deploy-staging.yml | 148 --------- .github/workflows/deploy-web.yaml | 2 - .github/workflows/deploy.yml | 300 ------------------ README.md | 6 +- terraform/README.md | 5 +- .../live/modules/environment-owned/main.tf | 3 + 6 files changed, 8 insertions(+), 456 deletions(-) delete mode 100644 .github/workflows/deploy-staging.yml delete mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml deleted file mode 100644 index c00f749e..00000000 --- a/.github/workflows/deploy-staging.yml +++ /dev/null @@ -1,148 +0,0 @@ -name: Deploy staging - -# Standalone staging deployment (push to `staging` / manual dispatch), NOT a -# caller of the org reusable `cd-cdk.yaml` (that path is dev-only): staging -# trusts the exact GitHub-environment OIDC subject, which requires the deploy -# job to declare `environment: staging` and run in this repo, with the -# non-secret role ARN pinned below (created by the staging stack itself). -# -# Order is fixed: full `npm run verify` gates run BEFORE any deploy step. -# No secrets are used — OIDC + the static role ARN are the only credentials. - -on: - push: - branches: [staging] - workflow_dispatch: {} - -permissions: - id-token: write - contents: read - -concurrency: - group: deploy-staging - cancel-in-progress: false - -jobs: - deploy: - name: Deploy to staging - # Deploy only the exact staging branch ref, never a tag or other ref - # (workflow_dispatch can be invoked from arbitrary refs). - if: github.ref == 'refs/heads/staging' - runs-on: ubuntu-latest - environment: staging - env: - VITE_API_URL: https://api.staging.seahaven.com/api - VITE_SENTRY_ENVIRONMENT: staging - VITE_APP_COMMIT_SHA: ${{ github.sha }} - AWS_REGION: us-east-1 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - fetch-depth: 0 - - name: Resolve governance comparison ref - id: governance-ref - shell: bash - env: - EVENT_NAME: ${{ github.event_name }} - EVENT_BEFORE: ${{ github.event.before }} - run: | - set -euo pipefail - if [[ "${EVENT_NAME}" == "push" && -n "${EVENT_BEFORE}" && ! "${EVENT_BEFORE}" =~ ^0+$ ]]; then - base="${EVENT_BEFORE}" - else - base="origin/dev" - fi - printf 'base=%s\n' "${base}" >> "${GITHUB_OUTPUT}" - - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 - with: - node-version: "24" - cache: npm - # Node 24 bundles npm 11 (lockfileVersion 3); the packageManager pin - # (npm@11.16.0) matches this CI environment. - - name: Quality gates (full verify before any deploy) - run: npm ci && npm run verify - env: - GOVERNANCE_BASE: ${{ steps.governance-ref.outputs.base }} - - - name: Assume staging deploy role (OIDC) - uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3 - with: - role-to-assume: arn:aws:iam::396287094661:role/githubdeploy-shoc-frontend-new-staging - aws-region: us-east-1 - - # Builds the SPA with the staging VITE_API_URL and Sentry environment - # label (process env overrides the dev values committed in - # .env.production), syncs to the staging bucket, and invalidates - # CloudFront. - - name: Build and publish SPA - run: bash scripts/deploy-web.sh - env: - STACK_NAME: shoc-frontend-staging - WAIT_FOR_INVALIDATION: "true" - - - name: Upload private source maps - run: bash scripts/upload-sourcemaps.sh - env: - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - - - name: Verify deployment - run: | - set -euo pipefail - stack_output() { - aws cloudformation describe-stacks \ - --stack-name shoc-frontend-staging \ - --query "Stacks[0].Outputs[?OutputKey=='$1'].OutputValue" \ - --output text - } - BUCKET="$(stack_output BucketName)" - DIST_ID="$(stack_output DistributionId)" - DIST_DOMAIN="$(stack_output DistributionDomainName)" - SITE_URL="$(stack_output SiteUrl)" - if [[ -z "${BUCKET}" || "${BUCKET}" == "None" || -z "${DIST_ID}" || "${DIST_ID}" == "None" || -z "${DIST_DOMAIN}" || "${DIST_DOMAIN}" == "None" ]]; then - echo "::error::Could not resolve bucket/distribution from stack outputs." >&2 - exit 1 - fi - echo "Bucket=${BUCKET} Distribution=${DIST_ID} (${DIST_DOMAIN}) SiteUrl=${SITE_URL}" - - aws s3api head-bucket --bucket "${BUCKET}" - echo "Bucket exists." - # The distribution is proven to exist and serve by the HTTPS check - # below: the custom domain is an alias to this distribution, and the - # deploy role deliberately carries no cloudfront:GetDistribution - # (least privilege; the dev template is shared and must not drift). - - if grep -Rq "api.dev.seahaven.com" dist/; then - echo "::error::Built assets contain the dev API URL (api.dev.seahaven.com)." >&2 - grep -Rl "api.dev.seahaven.com" dist/ >&2 || true - exit 1 - fi - echo "Built assets carry no dev API URL." - grep -Rq "api.staging.seahaven.com" dist/ - echo "Built assets reference the staging API URL." - - # Verify the actual post-invalidation HTML and its referenced assets, - # not only the local build or a generic endpoint response. - remote_dir="$(mktemp -d)" - trap 'rm -rf "${remote_dir}"' EXIT - for i in 1 2 3 4 5 6; do - if curl -fsS --max-time 30 "${SITE_URL}" -o "${remote_dir}/index.html"; then - break - fi - echo "Endpoint not ready (attempt ${i}); retrying in 20s..." - sleep 20 - done - test -s "${remote_dir}/index.html" - grep -oE '(src|href)="/assets/[^"]+\.(js|css)"' "${remote_dir}/index.html" \ - | sed -E 's/^(src|href)="([^"]+)"$/\2/' \ - | sort -u > "${remote_dir}/asset-paths.txt" - test -s "${remote_dir}/asset-paths.txt" - while IFS= read -r asset_path; do - curl -fsS --max-time 30 "${SITE_URL%/}${asset_path}" \ - >> "${remote_dir}/assets.txt" - done < "${remote_dir}/asset-paths.txt" - if grep -q "api.dev.seahaven.com" "${remote_dir}/assets.txt"; then - echo "::error::Deployed assets contain the dev API URL." >&2 - exit 1 - fi - grep -q "api.staging.seahaven.com" "${remote_dir}/assets.txt" - echo "Deployed staging assets reference only the staging API URL." diff --git a/.github/workflows/deploy-web.yaml b/.github/workflows/deploy-web.yaml index 7fa28e70..96502f69 100644 --- a/.github/workflows/deploy-web.yaml +++ b/.github/workflows/deploy-web.yaml @@ -23,8 +23,6 @@ on: - "terraform/**" - "docs/**" - "**/*.md" - - ".github/workflows/deploy.yml" - - ".github/workflows/deploy-staging.yml" - ".github/workflows/ci.yaml" - ".github/workflows/ci-terraform.yaml" - ".github/workflows/deploy-web.yaml" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index a2894a61..00000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,300 +0,0 @@ -name: Deploy dev content - -# Dev content CD through Terraform (SH-300). GitHub uploads an immutable -# releases/--/ prefix. Terraform owns the pointer, origin -# group, and invalidation. Push-to-dev stays off until -# vars.TERRAFORM_CONTENT_CD_ENABLED is the string true. -# -# Quality gates live in Frontend checks (`ci.yaml`). This workflow does not -# re-run those gates on pull requests, pushes, or workflow_dispatch. - -on: - push: - branches: [dev] - paths-ignore: - - "terraform/**" - workflow_dispatch: {} - -permissions: - contents: read - -jobs: - deploy-dev: - name: Deploy shoc-frontend-new-dev through Terraform - if: > - (github.event_name == 'push' && github.ref == 'refs/heads/dev' && - vars.TERRAFORM_CONTENT_CD_ENABLED == 'true') || - (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/dev') - runs-on: ubuntu-latest - timeout-minutes: 180 - permissions: - contents: read - id-token: write - concurrency: - group: deploy-dev - cancel-in-progress: false - env: - AWS_REGION: us-east-1 - TF_CLOUD_ORGANIZATION: seahaven - TF_API_TOKEN: ${{ secrets.TF_API_TOKEN }} - SITE_BUCKET: seahaven-shoc-frontend-dev - DISTRIBUTION_ID: E2CWLM1AFB964P - SITE_URL: https://dev.seahaven.com - VITE_API_URL: https://api.dev.seahaven.com/api - VITE_APP_COMMIT_SHA: ${{ github.sha }} - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 - with: - node-version: "24" - cache: npm - - - name: Build SPA - run: | - set -euo pipefail - npm ci - npm run build - if grep -Rq "api.staging.seahaven.com" dist/; then - echo "::error::Built assets contain the staging API URL." >&2 - exit 1 - fi - if grep -Rq "localhost:5141" dist/; then - echo "::error::Built assets contain the Vite proxy target localhost:5141." >&2 - exit 1 - fi - grep -Rq "api.dev.seahaven.com" dist/ - - - name: Configure AWS credentials (OIDC) - uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3 - with: - role-to-assume: arn:aws:iam::396287094661:role/githubdeploy-shoc-frontend-new-dev - aws-region: us-east-1 - audience: sts.amazonaws.com - - - name: Assign immutable release identity - id: release - run: | - set -euo pipefail - version_label="${GITHUB_SHA}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" - prefix="releases/${version_label}" - { - echo "version_label=${version_label}" - echo "prefix=${prefix}" - } >> "${GITHUB_OUTPUT}" - - # Sentry release is shoc-frontend@${GITHUB_SHA} via VITE_APP_COMMIT_SHA, - # distinct from the S3/Terraform version_label. - - name: Upload private source maps - run: bash scripts/upload-sourcemaps.sh - env: - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - - - name: Read previous release pointer - id: pointer - run: | - set -euo pipefail - body="$(aws s3 cp "s3://${SITE_BUCKET}/.release/current" - --only-show-errors || true)" - printf '%s' "${body}" | python3 scripts/read-release-pointer.py - - - name: Upload immutable release prefix - run: | - set -euo pipefail - prefix="${{ steps.release.outputs.prefix }}" - aws s3 sync dist/ "s3://${SITE_BUCKET}/${prefix}/" \ - --exclude "index.html" \ - --exclude "*.map" \ - --cache-control "public,max-age=31536000,immutable" - aws s3 cp dist/index.html "s3://${SITE_BUCKET}/${prefix}/index.html" \ - --cache-control "no-cache,no-store,must-revalidate" \ - --content-type "text/html" - aws s3api head-object \ - --bucket "${SITE_BUCKET}" \ - --key "${prefix}/index.html" - index_sha="$(python3 -c 'import hashlib,pathlib; print(hashlib.sha256(pathlib.Path("dist/index.html").read_bytes()).hexdigest())')" - echo "INDEX_SHA256=${index_sha}" >> "${GITHUB_ENV}" - echo "Uploaded ${prefix}; index.html sha256=${index_sha}" - - - name: Capture previous served hash - id: previous-hash - run: | - set -euo pipefail - hash="$(curl -fsS --max-time 30 "${SITE_URL}/" | python3 -c 'import hashlib,sys; print(hashlib.sha256(sys.stdin.buffer.read()).hexdigest())' || true)" - echo "sha256=${hash}" >> "${GITHUB_OUTPUT}" - - - name: Discard blocking VCS run before GitHub CD - id: discard-vcs - env: - TF_API_TOKEN: ${{ secrets.TF_API_TOKEN }} - run: python3 scripts/hcp-run-guard.py check-and-discard --workspace shoc-frontend-new-dev - - - name: Create Terraform release run - id: release-run - uses: hashicorp/tfc-workflows-github/actions/create-run@8e08d1ba957673f5fbf971a22b3219639dc45661 # v1.3.2 - env: - TF_VAR_release_version_label: '"${{ steps.release.outputs.version_label }}"' - TF_VAR_previous_release_version_label: '"${{ steps.pointer.outputs.live_current }}"' - with: - workspace: shoc-frontend-new-dev - message: "Release ${{ steps.release.outputs.version_label }} from GitHub Actions" - - - name: Read Terraform release plan counts - id: release-plan - uses: hashicorp/tfc-workflows-github/actions/plan-output@8e08d1ba957673f5fbf971a22b3219639dc45661 # v1.3.2 - with: - plan: ${{ steps.release-run.outputs.plan_id }} - - - name: Reject non-release resource counts - env: - PLAN_ADD: ${{ steps.release-plan.outputs.add }} - PLAN_CHANGE: ${{ steps.release-plan.outputs.change }} - PLAN_DESTROY: ${{ steps.release-plan.outputs.destroy }} - run: | - set -euo pipefail - if [ "$PLAN_ADD" != "0" ] || [ "$PLAN_CHANGE" != "2" ] || [ "$PLAN_DESTROY" != "0" ]; then - echo "HCP plan counts are add=${PLAN_ADD} change=${PLAN_CHANGE} destroy=${PLAN_DESTROY}; expected 0/2/0." >&2 - exit 1 - fi - - - name: Guard pointer-and-origin-path Terraform plan - run: | - set -euo pipefail - # Flags must match check-terraform-release-plan.py. Pointer `before` - # and origin-ID-set stability are asserted from the plan JSON. - python3 scripts/check-terraform-release-plan.py \ - --plan-id "${{ steps.release-run.outputs.plan_id }}" \ - --expected-version-label "${{ steps.release.outputs.version_label }}" \ - --expected-previous-version-label "${{ steps.pointer.outputs.live_current }}" - - - name: Discard release run when the guard fails - if: failure() && steps.release-run.outcome == 'success' - uses: hashicorp/tfc-workflows-github/actions/discard-run@8e08d1ba957673f5fbf971a22b3219639dc45661 # v1.3.2 - with: - run: ${{ steps.release-run.outputs.run_id }} - comment: Rejected by the pointer-and-origin-path plan guard from GitHub Actions - - - name: Apply Terraform release run - id: release-apply - continue-on-error: true - uses: hashicorp/tfc-workflows-github/actions/apply-run@8e08d1ba957673f5fbf971a22b3219639dc45661 # v1.3.2 - with: - run: ${{ steps.release-run.outputs.run_id }} - comment: Apply pointer-and-origin-path release from GitHub Actions ${{ github.sha }} - - - name: Treat already-applied release run as success - env: - TF_API_TOKEN: ${{ secrets.TF_API_TOKEN }} - run: | - python3 scripts/hcp-run-guard.py reconcile-apply \ - --run-id "${{ steps.release-run.outputs.run_id }}" \ - --apply-outcome "${{ steps.release-apply.outcome }}" - - - name: Verify CloudFront release - env: - EXPECTED_LABEL: ${{ steps.release.outputs.version_label }} - EXPECTED_INDEX_SHA256: ${{ env.INDEX_SHA256 }} - PREVIOUS_INDEX_SHA256: ${{ steps.previous-hash.outputs.sha256 }} - run: bash scripts/verify-cloudfront-release.sh - - - name: Restore previous release on failure - if: failure() - id: rollback-prepare - run: | - set -euo pipefail - prev="${{ steps.pointer.outputs.live_current }}" - if [[ ! "${prev}" =~ ^[0-9a-f]{40}-[0-9]+-[0-9]+$ ]]; then - echo "No Terraform-managed previous label; cannot roll back through HCP." >&2 - exit 0 - fi - echo "rollback_label=${prev}" >> "${GITHUB_OUTPUT}" - echo "rollback_previous=${{ steps.release.outputs.version_label }}" >> "${GITHUB_OUTPUT}" - - - name: Discard blocking VCS run before GitHub rollback - id: rollback-discard-vcs - if: failure() && steps.rollback-prepare.outputs.rollback_label != '' - env: - TF_API_TOKEN: ${{ secrets.TF_API_TOKEN }} - run: python3 scripts/hcp-run-guard.py check-and-discard --workspace shoc-frontend-new-dev - - - name: Create Terraform rollback run - id: rollback-run - if: failure() && steps.rollback-prepare.outputs.rollback_label != '' && steps.rollback-discard-vcs.outcome == 'success' - uses: hashicorp/tfc-workflows-github/actions/create-run@8e08d1ba957673f5fbf971a22b3219639dc45661 # v1.3.2 - env: - TF_VAR_release_version_label: '"${{ steps.rollback-prepare.outputs.rollback_label }}"' - TF_VAR_previous_release_version_label: '"${{ steps.rollback-prepare.outputs.rollback_previous }}"' - with: - workspace: shoc-frontend-new-dev - message: "Rollback to ${{ steps.rollback-prepare.outputs.rollback_label }} from GitHub Actions" - - - name: Read Terraform rollback plan counts - id: rollback-plan - if: failure() && steps.rollback-run.outcome == 'success' - uses: hashicorp/tfc-workflows-github/actions/plan-output@8e08d1ba957673f5fbf971a22b3219639dc45661 # v1.3.2 - with: - plan: ${{ steps.rollback-run.outputs.plan_id }} - - - name: Reject non-release rollback counts - id: rollback-count-guard - if: failure() && steps.rollback-plan.outcome == 'success' - env: - PLAN_ADD: ${{ steps.rollback-plan.outputs.add }} - PLAN_CHANGE: ${{ steps.rollback-plan.outputs.change }} - PLAN_DESTROY: ${{ steps.rollback-plan.outputs.destroy }} - run: | - set -euo pipefail - if [ "$PLAN_ADD" != "0" ] || [ "$PLAN_CHANGE" != "2" ] || [ "$PLAN_DESTROY" != "0" ]; then - echo "Rollback HCP plan counts are add=${PLAN_ADD} change=${PLAN_CHANGE} destroy=${PLAN_DESTROY}; expected 0/2/0." >&2 - exit 1 - fi - - - name: Guard pointer-and-origin-path Terraform rollback plan - id: rollback-json-guard - if: failure() && steps.rollback-count-guard.outcome == 'success' - run: | - set -euo pipefail - python3 scripts/check-terraform-release-plan.py \ - --plan-id "${{ steps.rollback-run.outputs.plan_id }}" \ - --expected-version-label "${{ steps.rollback-prepare.outputs.rollback_label }}" \ - --expected-previous-version-label "${{ steps.rollback-prepare.outputs.rollback_previous }}" - - - name: Discard rollback run when the guard fails - if: failure() && steps.rollback-run.outcome == 'success' && steps.rollback-json-guard.outcome != 'success' - uses: hashicorp/tfc-workflows-github/actions/discard-run@8e08d1ba957673f5fbf971a22b3219639dc45661 # v1.3.2 - with: - run: ${{ steps.rollback-run.outputs.run_id }} - comment: Rejected by the pointer-and-origin-path rollback plan guard from GitHub Actions - - - name: Apply Terraform rollback run - id: rollback-apply - if: failure() && steps.rollback-json-guard.outcome == 'success' - continue-on-error: true - uses: hashicorp/tfc-workflows-github/actions/apply-run@8e08d1ba957673f5fbf971a22b3219639dc45661 # v1.3.2 - with: - run: ${{ steps.rollback-run.outputs.run_id }} - comment: Apply pointer-and-origin-path rollback from GitHub Actions ${{ github.sha }} - - - name: Treat already-applied rollback run as success - id: rollback-apply-result - if: failure() && steps.rollback-apply.outcome != 'skipped' - env: - TF_API_TOKEN: ${{ secrets.TF_API_TOKEN }} - run: | - python3 scripts/hcp-run-guard.py reconcile-apply \ - --run-id "${{ steps.rollback-run.outputs.run_id }}" \ - --apply-outcome "${{ steps.rollback-apply.outcome }}" - - - name: Verify CloudFront rollback - if: failure() && steps.rollback-apply-result.outcome == 'success' - env: - EXPECTED_LABEL: ${{ steps.rollback-prepare.outputs.rollback_label }} - run: | - set -euo pipefail - expected_sha="$(aws s3 cp "s3://${SITE_BUCKET}/releases/${EXPECTED_LABEL}/index.html" - | python3 -c 'import hashlib,sys; print(hashlib.sha256(sys.stdin.buffer.read()).hexdigest())')" - export EXPECTED_INDEX_SHA256="${expected_sha}" - bash scripts/verify-cloudfront-release.sh - - - name: Live-state summary - if: always() - continue-on-error: true - run: bash scripts/summarize-cloudfront-live-state.sh diff --git a/README.md b/README.md index 2aa29ace..7e2acaa2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # SHOC Frontend (`shoc-frontend-new`) [![CI](https://github.com/Sea-Haven-Industries/shoc-frontend-new/actions/workflows/ci.yaml/badge.svg?branch=dev)](https://github.com/Sea-Haven-Industries/shoc-frontend-new/actions/workflows/ci.yaml) -[![Deploy](https://github.com/Sea-Haven-Industries/shoc-frontend-new/actions/workflows/deploy.yml/badge.svg)](https://github.com/Sea-Haven-Industries/shoc-frontend-new/actions/workflows/deploy.yml) +[![Deploy](https://github.com/Sea-Haven-Industries/shoc-frontend-new/actions/workflows/deploy-web.yaml/badge.svg)](https://github.com/Sea-Haven-Industries/shoc-frontend-new/actions/workflows/deploy-web.yaml) ![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?logo=typescript&logoColor=white) ![React](https://img.shields.io/badge/React-087EA4?logo=react&logoColor=white) ![Vite](https://img.shields.io/badge/Vite-646CFF?logo=vite&logoColor=white) @@ -36,7 +36,7 @@ graph LR Dev and staging hosting live in `terraform/live/dev` and `terraform/live/staging`. GitHub `.github/workflows/deploy-web.yaml` syncs -content. The older pointer CD (`deploy.yml`) stays in the tree until cutover. +content. Frontend stack: React 19, TypeScript, Vite, Tailwind CSS 4 + MUI, TanStack Query, React Router (via `@generouted/react-router`), React Hook Form + Zod, @@ -160,8 +160,6 @@ No stored AWS keys — OIDC only. Infrastructure and content deploy separately: - **SPA content** ([`.github/workflows/deploy-web.yaml`](.github/workflows/deploy-web.yaml)) — push to `main` deploys `dev`; a published `vX.Y.Z-staging` release deploys `staging`. Syncs `dist/` to the bucket root and invalidates `/*`. -- **Legacy pointer CD** ([`.github/workflows/deploy.yml`](.github/workflows/deploy.yml)) - stays until cutover proof. Do not enable it on `main`. - **Infrastructure** — HCP workspaces `shoc-frontend-new-dev` and `shoc-frontend-new-staging` ([`terraform/README.md`](terraform/README.md)). diff --git a/terraform/README.md b/terraform/README.md index d44815c4..8bfd6bcb 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -70,5 +70,6 @@ create an HCP run or touch AWS. `--delete` root sync. 3. Create GitHub Environment `dev` (staging already exists). Set `DEPLOY_ROLE_ARN` on each. -4. Enable `deploy-web.yaml`. Then retire `deploy.yml`, `TF_API_TOKEN`, and - `TERRAFORM_CONTENT_CD_ENABLED`. +4. Enable `deploy-web.yaml`. Then delete leftover `deploy.yml` / + `deploy-staging.yml` and repo `TF_API_TOKEN`, `TERRAFORM_CONTENT_CD_ENABLED`, + and `AWS_DEPLOY_ROLE_ARN`. diff --git a/terraform/live/modules/environment-owned/main.tf b/terraform/live/modules/environment-owned/main.tf index bfb167a7..42573c80 100644 --- a/terraform/live/modules/environment-owned/main.tf +++ b/terraform/live/modules/environment-owned/main.tf @@ -370,6 +370,9 @@ resource "aws_iam_role" "github_deploy" { lifecycle { prevent_destroy = true + # SCP ProtectDeploymentPrincipalLifecycle denies UpdateRoleDescription on + # githubdeploy-* for HCP apply roles. + ignore_changes = [description] } }