1- name : CI
1+ name : CICD
22
33on :
44 workflow_dispatch :
55 pull_request :
6+ types :
7+ - opened
8+ - synchronize
9+ - reopened
10+ - labeled
11+ - unlabeled
612 push :
713 branches :
814 - main
4248
4349 build-test-push :
4450 name : Build, Test and Push
45- environment : staging
51+ environment : build
4652 runs-on : ubuntu-latest
4753 needs : [lint, check-yarn-lock]
4854 outputs :
@@ -100,6 +106,7 @@ jobs:
100106 run : yarn test
101107
102108 - name : " Authenticate with GCP"
109+ if : github.event_name == 'pull_request' && (github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.action == 'reopened' || github.event.action == 'labeled' || github.event.action == 'unlabeled')
103110 id : gcp-auth
104111 uses : google-github-actions/auth@v2
105112 with :
@@ -108,20 +115,23 @@ jobs:
108115 service_account :
[email protected] 109116
110117 - name : " Login to GCP Artifact Registry"
118+ if : github.event_name == 'pull_request' && (github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.action == 'reopened' || github.event.action == 'labeled' || github.event.action == 'unlabeled')
111119 uses : docker/login-action@v3
112120 with :
113121 registry : europe-docker.pkg.dev
114122 username : oauth2accesstoken
115123 password : ${{ steps.gcp-auth.outputs.access_token }}
116124
117125 - name : " Push ActivityPub Docker Image"
126+ if : github.event_name == 'pull_request' && (github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.action == 'reopened' || github.event.action == 'labeled' || github.event.action == 'unlabeled')
118127 uses : docker/build-push-action@v6
119128 with :
120129 context : .
121130 push : true
122131 tags : ${{ steps.activitypub-docker-metadata.outputs.tags }}
123132
124133 - name : " Push Migrations Docker Image"
134+ if : github.event_name == 'pull_request' && (github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.action == 'reopened' || github.event.action == 'labeled' || github.event.action == 'unlabeled')
125135 uses : docker/build-push-action@v6
126136 with :
127137 context : migrate
@@ -135,10 +145,136 @@ jobs:
135145 env :
136146 SLACK_WEBHOOK_URL : ${{ secrets.SLACK_WEBHOOK_URL }}
137147
148+ deploy-pr :
149+ if : github.event_name == 'pull_request' && (github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.action == 'reopened' || github.event.action == 'labeled' || github.event.action == 'unlabeled')
150+ name : (ephemeral staging) Deploy
151+ runs-on : ubuntu-latest
152+ needs : [build-test-push]
153+ environment : staging
154+ steps :
155+ - name : " Check if any label matches *.ghost.is"
156+ id : check-labels
157+ env :
158+ LABELS : ${{ toJson(github.event.pull_request.labels) }}
159+ run : |
160+ export LABEL_NAMES=$(echo "$LABELS" | jq -r '[.[] | select(.name | test("\\.ghost\\.is$")) | .name] | join(",")')
161+ echo "Label names: $LABEL_NAMES"
162+ if [ "$LABEL_NAMES" != "" ]; then
163+ echo "Label matching *.ghost.is found."
164+ echo "is_ephemeral_staging=true" >> "$GITHUB_OUTPUT"
165+ else
166+ echo "No label matching .*.ghost.is found."
167+ echo "is_ephemeral_staging=false" >> "$GITHUB_OUTPUT"
168+ fi
169+
170+ - name : " Checkout activitypub-infra repo"
171+ if : ${{ steps.check-labels.outputs.is_ephemeral_staging == 'true' }}
172+ uses : actions/checkout@v4
173+ with :
174+ repository : TryGhost/activitypub-infra
175+ ssh-key : ${{ secrets.ACTIVITYPUB_INFRA_DEPLOY_KEY }}
176+ path : activitypub-infra
177+
178+ - name : " Checkout terraform repo"
179+ if : ${{ steps.check-labels.outputs.is_ephemeral_staging == 'true' }}
180+ uses : actions/checkout@v4
181+ with :
182+ repository : TryGhost/terraform
183+ ssh-key : ${{ secrets.TERRAFORM_DEPLOY_KEY }}
184+ path : terraform
185+
186+ - name : " Get terraform version"
187+ if : ${{ steps.check-labels.outputs.is_ephemeral_staging == 'true' }}
188+ id : terraform-version
189+ run : |
190+ echo "terraform_version=$(cat activitypub-infra/infrastructure/activitypub-staging-environments/.terraform-version)" >> "$GITHUB_OUTPUT"
191+
192+ - name : " Setup terraform"
193+ if : ${{ steps.check-labels.outputs.is_ephemeral_staging == 'true' }}
194+ uses : hashicorp/setup-terraform@v3
195+ with :
196+ terraform_version : ${{ steps.terraform-version.outputs.terraform_version }}
197+
198+ - name : " Change github.com url in modules to local directories and add backend prefix"
199+ if : ${{ steps.check-labels.outputs.is_ephemeral_staging == 'true' }}
200+ run : |
201+ cd activitypub-infra/infrastructure/activitypub-staging-environments
202+ sed -i 's/github\.com\/TryGhost/\.\.\/\.\.\/\.\./gI' main.tf
203+ sed -i 's/\?ref=main//g' main.tf
204+ sed -i 's/REPLACE_ME/${{ github.event.pull_request.number }}/g' terraform.tf
205+
206+ - name : " Authenticate with GCP"
207+ if : ${{ steps.check-labels.outputs.is_ephemeral_staging == 'true' }}
208+ uses : google-github-actions/auth@v2
209+ with :
210+ token_format : access_token
211+ workload_identity_provider : projects/687476608778/locations/global/workloadIdentityPools/github-oidc-activitypub/providers/github-provider-activitypub
212+ service_account : stg-activitypub-cicd-stg-envs@ghost-activitypub.iam.gserviceaccount.com
213+
214+ - name : " Terraform init"
215+ if : ${{ steps.check-labels.outputs.is_ephemeral_staging == 'true' }}
216+ run : |
217+ cd activitypub-infra/infrastructure/activitypub-staging-environments
218+ terraform init
219+
220+ - name : " Terraform apply"
221+ if : ${{ steps.check-labels.outputs.is_ephemeral_staging == 'true' }}
222+ run : |
223+ cd activitypub-infra/infrastructure/activitypub-staging-environments
224+ export TF_VAR_github_pr_number=${{ github.event.pull_request.number }}
225+ export TF_VAR_primary_region_name=netherlands
226+ export TF_VAR_migrations_image=europe-docker.pkg.dev/ghost-activitypub/activitypub/migrations:pr-${{ github.event.pull_request.number }}
227+ export TF_VAR_api_image=europe-docker.pkg.dev/ghost-activitypub/activitypub/activitypub:pr-${{ github.event.pull_request.number }}
228+ export TF_VAR_queue_image=europe-docker.pkg.dev/ghost-activitypub/activitypub/activitypub:pr-${{ github.event.pull_request.number }}
229+ terraform apply -auto-approve
230+
231+ - name : " Deploy Migrations to Cloud Run"
232+ if : ${{ steps.check-labels.outputs.is_ephemeral_staging == 'true' }}
233+ uses : google-github-actions/deploy-cloudrun@v2
234+ with :
235+ image : europe-docker.pkg.dev/ghost-activitypub/activitypub/migrations:pr-${{ github.event.pull_request.number }}
236+ region : europe-west4
237+ job : stg-pr-${{ github.event.pull_request.number }}-migrations
238+ flags : --wait --execute-now
239+ skip_default_labels : true
240+ labels : |-
241+ commit-sha=${{ github.sha }}
242+
243+ - name : " Update Load Balancer"
244+ if : ${{ steps.check-labels.outputs.is_ephemeral_staging == 'true' }}
245+ env :
246+ LABELS : ${{ toJson(github.event.pull_request.labels) }}
247+ GCP_PROJECT : ghost-activitypub
248+ run : |
249+ set -euo pipefail
250+ # Get current config
251+ gcloud compute url-maps export stg-activitypub --global --project ${GCP_PROJECT} > config.yml
252+ # Delete unnecessary fields
253+ yq 'del(.fingerprint)' config.yml -i
254+ yq 'del(.creationTimestamp)' config.yml -i
255+ export DEFAULT_SERVICE="https://www.googleapis.com/compute/v1/projects/ghost-activitypub/global/backendServices/stg-netherlands-activitypub-api"
256+ export PR_SERVICE="https://www.googleapis.com/compute/v1/projects/ghost-activitypub/global/backendServices/stg-pr-${{ github.event.pull_request.number }}-api"
257+ # Add host rules and path matchers if they don't exist
258+ yq '.hostRules = (.hostRules // [{"hosts": ["activitypub.infra.ghost.is"], "pathMatcher": "staging-environments"}])' config.yml > config.yml.tmp
259+ mv config.yml.tmp config.yml
260+ yq '.pathMatchers = (.pathMatchers // [{"name": "staging-environments", "defaultService": "'"$DEFAULT_SERVICE"'", "routeRules": []}])' config.yml > config.yml.tmp
261+ mv config.yml.tmp config.yml
262+ # Remove existing route rules for the PR service
263+ yq '.pathMatchers[] |= (.routeRules |= map(select((.routeAction.weightedBackendServices // []) | length == 0 or .routeAction.weightedBackendServices[0].backendService != env(PR_SERVICE))))' config.yml > config.yml.tmp
264+ mv config.yml.tmp config.yml
265+ # Add new route rules for the PR service
266+ export MAX_PRIORITY=$(yq '[.pathMatchers[] | select(.name == "staging-environments") | .routeRules[]?.priority] | max // 0' config.yml)
267+ export NEXT_PRIORITY=$((MAX_PRIORITY + 1))
268+ export HEADER_MATCHES=$(echo "$LABELS" | jq -c '[.[] | select(.name | test("\\.ghost\\.is$")) | { "headerName": "X-Forwarded-Host", "exactMatch": "\(.name)" }'])
269+ yq '.pathMatchers[0].routeRules += [{"priority": '"$NEXT_PRIORITY"', "matchRules": [{"prefixMatch": "/", "headerMatches": '$HEADER_MATCHES'}], "routeAction": {"weightedBackendServices": [ { "backendService": "'$PR_SERVICE'", "weight": 100 } ] } }]' config.yml > config.yml.tmp
270+ mv config.yml.tmp config.yml
271+ echo "Updating url map with:"
272+ cat config.yml
273+ gcloud compute url-maps import stg-activitypub --source=config.yml --global --project ${GCP_PROJECT} --quiet
274+
138275 deploy-staging :
139276 if : github.ref == 'refs/heads/main'
140277 name : (staging) Deploy
141- environment : staging
142278 runs-on : ubuntu-latest
143279 needs : [build-test-push]
144280 strategy :
@@ -193,7 +329,6 @@ jobs:
193329 deploy-production :
194330 if : github.ref == 'refs/heads/main'
195331 name : (production) Deploy
196- environment : production
197332 runs-on : ubuntu-latest
198333 needs : [build-test-push, deploy-staging]
199334 strategy :
0 commit comments