44 push :
55 branches : [main]
66 paths :
7- - ' infra/prod.tfvars'
7+ - ' versions.yaml'
8+ - ' .github/workflows/release-prod.yml'
89 workflow_dispatch :
910
1011# CRITICAL: Concurrency Control
@@ -17,7 +18,7 @@ permissions:
1718 id-token : write
1819
1920jobs :
20- # Phase 1: Extract and verify configuration
21+ # Phase 1: Extract and verify configuration from versions.yaml
2122 config :
2223 runs-on : ubuntu-latest
2324 outputs :
@@ -29,31 +30,53 @@ jobs:
2930 steps :
3031 - uses : actions/checkout@v4
3132
32- - name : Parse Environment Config
33+ - name : Parse versions.yaml and Environment Config
3334 id : parse
3435 run : |
35- PROJECT_ID=$(grep -E '^\s*project_id\s*=' infra/prod.tfvars | head -1 | sed -E 's/.*=\s*"([^"]+)".*/\1/')
36- SA=$(grep -E '^\s*service_account\s*=' infra/prod.tfvars | head -1 | sed -E 's/.*=\s*"([^"]+)".*/\1/')
37- BUCKET=$(grep -E '^\s*state_bucket\s*=' infra/prod.tfvars | head -1 | sed -E 's/.*=\s*"([^"]+)".*/\1/')
38- VERSION=$(grep -E '^\s*image_version\s*=' infra/prod.tfvars | head -1 | sed -E 's/.*=\s*"([^"]+)".*/\1/')
36+ # Read the SHA from versions.yaml (dev section)
37+ DEV_SHA=$(grep -A 1 "^dev:" versions.yaml | grep "image_version:" | sed 's/.*image_version: *//' | tr -d '" ')
3938
40- # Derive dev project ID from prod service account
41- # Extract project ID from service account (e.g., tofu-provisioner@hubspoke-demo-prod-f01c.iam.gserviceaccount.com)
42- # and replace -prod- with -dev- to get the pattern, then use known dev suffix
43- PROD_SUFFIX=$(echo "$PROJECT_ID" | grep -oE '[a-f0-9]{4}$')
44- DEV_PROJECT_ID="hubspoke-demo-dev-b87d"
39+ # Read from prod.tfvars
40+ PROJECT_ID=$(grep -E '^\s*project_id\s*=' infra/prod.tfvars | head -1 | sed -E 's/.*=\s*"([^"+)".*/\1/')
41+ SA=$(grep -E '^\s*service_account\s*=' infra/prod.tfvars | head -1 | sed -E 's/.*=\s*"([^"+)".*/\1/')
42+ BUCKET=$(grep -E '^\s*state_bucket\s*=' infra/prod.tfvars | head -1 | sed -E 's/.*=\s*"([^"+)".*/\1/')
4543
46- echo "version=$VERSION" >> $GITHUB_OUTPUT
44+ # Derive dev project ID (strip -prod- suffix and add -dev- pattern)
45+ DEV_PROJECT_ID=$(echo "$PROJECT_ID" | sed 's/-prod-[a-f0-9]\{4\}/-dev-b87d/')
46+
47+ echo "version=$DEV_SHA" >> $GITHUB_OUTPUT
4748 echo "service_account=$SA" >> $GITHUB_OUTPUT
4849 echo "state_bucket=$BUCKET" >> $GITHUB_OUTPUT
4950 echo "project_id=$PROJECT_ID" >> $GITHUB_OUTPUT
5051 echo "dev_project_id=$DEV_PROJECT_ID" >> $GITHUB_OUTPUT
5152
5253 echo "📋 Configuration:"
53- echo " Version: $VERSION "
54+ echo " Version (from versions.yaml) : $DEV_SHA "
5455 echo " Prod Project: $PROJECT_ID"
5556 echo " Dev Project: $DEV_PROJECT_ID"
5657
58+ - name : Verify Artifacts Exist in Dev
59+ run : |
60+ VERSION="${{ steps.parse.outputs.version }}"
61+ DEV_PROJECT="${{ steps.parse.outputs.dev_project_id }}"
62+
63+ echo "🔍 Verifying artifacts exist in dev project: $DEV_PROJECT"
64+ echo " Version: $VERSION"
65+
66+ # Check container
67+ if ! gcloud artifacts docker images describe "us-central1-docker.pkg.dev/$DEV_PROJECT/repo/hubspoke-demo:$VERSION" --project=$DEV_PROJECT > /dev/null 2>&1; then
68+ echo "::error::Container image not found: us-central1-docker.pkg.dev/$DEV_PROJECT/repo/hubspoke-demo:$VERSION"
69+ exit 1
70+ fi
71+ echo "✅ Container verified"
72+
73+ # Check GCE image
74+ if ! gcloud storage ls "gs://hubspoke-demo-dev-nixos-images/nixos-image-$VERSION.tar.gz" > /dev/null 2>&1; then
75+ echo "::error::GCE image not found: gs://hubspoke-demo-dev-nixos-images/nixos-image-$VERSION.tar.gz"
76+ exit 1
77+ fi
78+ echo "✅ GCE image verified"
79+
5780 # Phase 2: Promote artifacts from dev to prod
5881 promote :
5982 needs : config
@@ -99,32 +122,46 @@ jobs:
99122 run : tofu init -backend-config="bucket=${{ needs.config.outputs.state_bucket }}"
100123
101124 - name : Tofu Plan
102- id : plan
103125 working-directory : ./infra
104126 run : |
105127 echo "📋 Running tofu plan..."
106128 tofu plan \
107129 -var-file="prod.tfvars" \
130+ -var="image_version=${{ needs.config.outputs.version }}" \
108131 -no-color \
109132 -out=tfplan
110133
111134 - name : Tofu Apply
112- id : apply
113135 if : github.ref == 'refs/heads/main'
114136 working-directory : ./infra
115137 run : |
116138 echo "🚀 Applying infrastructure changes..."
117139 tofu apply tfplan -no-color
118140
119- # NOTE: No automatic rollback on failure - artifacts are preserved
120- # for retry. Manual cleanup available via workflow_dispatch if needed.
141+ - name : Update versions.yaml Prod Section
142+ if : success()
143+ run : |
144+ VERSION="${{ needs.config.outputs.version }}"
145+ TIMESTAMP=$(date -Iseconds)
146+
147+ # Update versions.yaml with prod deployment info
148+ sed -i "/^prod:/,/^[^ ]/{s/image_version:.*/image_version: \"$VERSION\"/}" versions.yaml
149+ sed -i "/^prod:/,/^[^ ]/{s/promoted_at:.*/promoted_at: \"$TIMESTAMP\"/}" versions.yaml
150+
151+ git config --local user.email "github-actions[bot]@users.noreply.github.com"
152+ git config --local user.name "github-actions[bot]"
153+ git add versions.yaml
154+ git commit -m "docs: update prod version to $VERSION [skip ci]"
155+ git push
156+
121157 - name : Deployment Summary
122158 if : always()
123159 run : |
124160 if [ "${{ steps.apply.outcome }}" == "success" ]; then
125161 echo "✅ Deployment completed successfully"
162+ echo " Version: ${{ needs.config.outputs.version }}"
163+ echo " Prod URL: https://$(gcloud run services describe hubspoke-demo --project=${{ needs.config.outputs.project_id }} --region=us-central1 --format='value(status.url)' | sed 's|https://||')"
126164 else
127165 echo "❌ Deployment failed - artifacts preserved in prod for retry"
128- echo " Container: us-central1-docker.pkg.dev/${{ needs.config.outputs.project_id }}/repo/hubspoke-demo:${{ needs.config.outputs.version }}"
129- echo " GCE Image: gs://${{ needs.config.outputs.project_id }}-nixos-images/nixos-image-${{ needs.config.outputs.version }}.tar.gz"
166+ echo " Version: ${{ needs.config.outputs.version }}"
130167 fi
0 commit comments