Skip to content

Commit b9cba7c

Browse files
committed
Merge branch 'main' into refactor-get-account-to-use-view
2 parents 556b54a + 4e52812 commit b9cba7c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+2199
-255
lines changed
Lines changed: 139 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1-
name: CI
1+
name: CICD
22

33
on:
44
workflow_dispatch:
55
pull_request:
6+
types:
7+
- opened
8+
- synchronize
9+
- reopened
10+
- labeled
11+
- unlabeled
612
push:
713
branches:
814
- main
@@ -42,7 +48,7 @@ jobs:
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.ref == 'refs/heads/main' || (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: stg-activitypub-cicd@ghost-activitypub.iam.gserviceaccount.com
109116

110117
- name: "Login to GCP Artifact Registry"
118+
if: github.ref == 'refs/heads/main' || (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.ref == 'refs/heads/main' || (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.ref == 'refs/heads/main' || (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: "Add route to GCP 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:
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
name: Ephemeral Staging Tear Down
2+
3+
on:
4+
workflow_dispatch:
5+
schedule:
6+
- cron: '0 */4 * * *'
7+
8+
permissions:
9+
id-token: write
10+
contents: read
11+
pull-requests: read
12+
13+
jobs:
14+
destroy-pr:
15+
runs-on: ubuntu-latest
16+
environment: staging
17+
steps:
18+
- name: "Checkout"
19+
uses: actions/checkout@v4
20+
21+
- name: "Authenticate with GCP"
22+
uses: google-github-actions/auth@v2
23+
with:
24+
token_format: access_token
25+
workload_identity_provider: projects/687476608778/locations/global/workloadIdentityPools/github-oidc-activitypub/providers/github-provider-activitypub
26+
service_account: stg-activitypub-cicd-stg-envs@ghost-activitypub.iam.gserviceaccount.com
27+
28+
- name: "Check Closed PRs Deployed"
29+
id: check-closed-prs
30+
env:
31+
GCP_PROJECT: ghost-activitypub
32+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
33+
run: |
34+
export destroy_prs=""
35+
for PR_NUMBER in $(gcloud run services list --project ${GCP_PROJECT} --format=json \
36+
| jq -r '.[] | select(.metadata.name | test("stg-pr-\\d+-api")) | .metadata.name | capture("stg-pr-(?<num>\\d+)-api") | .num'); do
37+
PR_STATE=$(gh pr view $PR_NUMBER --json state | jq -r '.state')
38+
echo "PR $PR_NUMBER state is $PR_STATE."
39+
if [ "$PR_STATE" == "MERGED" ]; then
40+
echo "Deleting PR $PR_NUMBER environment."
41+
export destroy_prs="$destroy_prs $PR_NUMBER"
42+
fi
43+
done
44+
echo "destroy_prs=$destroy_prs" >> "$GITHUB_OUTPUT"
45+
46+
- name: "Checkout activitypub-infra repo"
47+
if: ${{ steps.check-closed-prs.outputs.destroy_prs != '' }}
48+
uses: actions/checkout@v4
49+
with:
50+
repository: TryGhost/activitypub-infra
51+
ssh-key: ${{ secrets.ACTIVITYPUB_INFRA_DEPLOY_KEY }}
52+
path: activitypub-infra
53+
54+
- name: "Checkout terraform repo"
55+
if: ${{ steps.check-closed-prs.outputs.destroy_prs != '' }}
56+
uses: actions/checkout@v4
57+
with:
58+
repository: TryGhost/terraform
59+
ssh-key: ${{ secrets.TERRAFORM_DEPLOY_KEY }}
60+
path: terraform
61+
62+
- name: "Get terraform version"
63+
if: ${{ steps.check-closed-prs.outputs.destroy_prs != '' }}
64+
id: terraform-version
65+
run: |
66+
echo "terraform_version=$(cat activitypub-infra/infrastructure/activitypub-staging-environments/.terraform-version)" >> "$GITHUB_OUTPUT"
67+
68+
- name: "Setup terraform"
69+
if: ${{ steps.check-closed-prs.outputs.destroy_prs != '' }}
70+
uses: hashicorp/setup-terraform@v3
71+
with:
72+
terraform_version: ${{ steps.terraform-version.outputs.terraform_version }}
73+
74+
- name: "Change github.com url in modules to local directories and add backend prefix"
75+
if: ${{ steps.check-closed-prs.outputs.destroy_prs != '' }}
76+
run: |
77+
cd activitypub-infra/infrastructure/activitypub-staging-environments
78+
sed -i 's/github\.com\/TryGhost/\.\.\/\.\.\/\.\./gI' main.tf
79+
sed -i 's/\?ref=main//g' main.tf
80+
81+
- name: "Authenticate with GCP"
82+
if: ${{ steps.check-closed-prs.outputs.destroy_prs != '' }}
83+
uses: google-github-actions/auth@v2
84+
with:
85+
token_format: access_token
86+
workload_identity_provider: projects/687476608778/locations/global/workloadIdentityPools/github-oidc-activitypub/providers/github-provider-activitypub
87+
service_account: stg-activitypub-cicd-stg-envs@ghost-activitypub.iam.gserviceaccount.com
88+
89+
- name: "Remove route from GCP Load Balancer"
90+
if: ${{ steps.check-closed-prs.outputs.destroy_prs != '' }}
91+
env:
92+
DESTROY_PRS: ${{ steps.check-closed-prs.outputs.destroy_prs }}
93+
GCP_PROJECT: ghost-activitypub
94+
run: |
95+
set -euo pipefail
96+
for PR_NUMBER in ${DESTROY_PRS}; do
97+
# Get current config
98+
gcloud compute url-maps export stg-activitypub --global --project ${GCP_PROJECT} > config.yml
99+
# Delete unnecessary fields
100+
yq 'del(.fingerprint)' config.yml -i
101+
yq 'del(.creationTimestamp)' config.yml -i
102+
export PR_SERVICE="https://www.googleapis.com/compute/v1/projects/ghost-activitypub/global/backendServices/stg-pr-${PR_NUMBER}-api"
103+
# Remove existing route rules for the PR service
104+
yq '.pathMatchers[] |= (.routeRules |= map(select((.routeAction.weightedBackendServices // []) | length == 0 or .routeAction.weightedBackendServices[0].backendService != env(PR_SERVICE))))' config.yml > config.yml.tmp
105+
mv config.yml.tmp config.yml
106+
echo "Updating url map with:"
107+
cat config.yml
108+
gcloud compute url-maps import stg-activitypub --source=config.yml --global --project ${GCP_PROJECT} --quiet
109+
done
110+
111+
- name: "Terraform destroy"
112+
if: ${{ steps.check-closed-prs.outputs.destroy_prs != '' }}
113+
env:
114+
DESTROY_PRS: ${{ steps.check-closed-prs.outputs.destroy_prs }}
115+
run: |
116+
cd activitypub-infra/infrastructure/activitypub-staging-environments
117+
for PR_NUMBER in ${DESTROY_PRS}; do
118+
echo "Destroying PR $PR_NUMBER staging environment."
119+
sed -i 's/REPLACE_ME/'${PR_NUMBER}'/g' terraform.tf
120+
terraform init
121+
export TF_VAR_github_pr_number=$PR_NUMBER
122+
export TF_VAR_primary_region_name=netherlands
123+
export TF_VAR_migrations_image=europe-docker.pkg.dev/ghost-activitypub/activitypub/migrations:edge
124+
export TF_VAR_api_image=europe-docker.pkg.dev/ghost-activitypub/activitypub/activitypub:edge
125+
export TF_VAR_queue_image=europe-docker.pkg.dev/ghost-activitypub/activitypub/activitypub:edge
126+
terraform destroy -auto-approve
127+
done

biome.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,7 @@
2828
"noNonNullAssertion": "off"
2929
},
3030
"suspicious": {
31-
"noConsoleLog": "off",
32-
"noExplicitAny": "warn",
33-
"noImplicitAnyLet": "warn"
31+
"noConsoleLog": "off"
3432
}
3533
}
3634
},

cedar/query-runner/src/app.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ const warmupPool = async () => {
4343
console.timeEnd('Warmup');
4444
};
4545

46+
// TODO: Clean up the any type
47+
// biome-ignore lint/suspicious/noExplicitAny: Legacy code needs proper typing
4648
const timeQuery = async (query: string, args: any[]) => {
4749
//: { [key: string]: string }) => {
4850
const start = performance.now();
@@ -51,6 +53,8 @@ const timeQuery = async (query: string, args: any[]) => {
5153
return end - start;
5254
};
5355

56+
// TODO: Clean up the any type
57+
// biome-ignore lint/suspicious/noExplicitAny: Legacy code needs proper typing
5458
const runQuery = async (query: string, args: any[]) => {
5559
// { [key: string]: string }) => {
5660
const runTimes: number[] = Array(SERIES_RUNS).fill(

0 commit comments

Comments
 (0)