Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cloud-function): setup review environment #12694

Open
wants to merge 42 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
33c2f48
feat(cloud-function): support review environment
caugner Feb 7, 2025
8ffd851
feat(workflows): add review-deploy workflow
caugner Feb 28, 2025
3acf49a
test(workflow/review-deploy): trigger on push to this branch
caugner Feb 28, 2025
246cf50
fixup! feat(workflows): add review-deploy workflow
caugner Feb 28, 2025
9618c7b
fix(review-deploy): set ORIGIN_REVIEW properly
caugner Feb 28, 2025
81a1f6f
fixup! fix(review-deploy): set ORIGIN_REVIEW properly
caugner Feb 28, 2025
8a235e2
chore(cloud-function): improve logging
caugner Feb 28, 2025
037a80f
fix(review-deploy): set SOURCE_REVIEW, not SOURCE_CONTENT
caugner Feb 28, 2025
4d3332f
fix(cloud-function): proxy assets from SOURCE_REVIEW
caugner Feb 28, 2025
bc2016f
wip
caugner Feb 28, 2025
0b6efdf
chore(git): ignore tsconfig.tsbuildinfo
caugner Feb 28, 2025
6d299d4
Merge branch 'main' into review-environment
caugner Feb 28, 2025
195735a
chore(cloud-function): revert to main
caugner Feb 28, 2025
9005ef5
chore(review-deploy): use ORIGIN_MAIN/SOURCE_CONTENT
caugner Feb 28, 2025
306b3ee
feat(cloud-function): implement WILDCARD_ENABLED flag
caugner Feb 28, 2025
ac6f5e8
fix(review-deploy): run with unprivileged service account
caugner Feb 28, 2025
510cfc8
chore(review-deploy): update branch name
caugner Feb 28, 2025
dca20d5
fixup! fix(review-deploy): run with unprivileged service account
caugner Feb 28, 2025
e5867ef
chore(review-deploy): increase min-instances to 1
caugner Feb 28, 2025
6f30f04
fixup! feat(cloud-function): implement WILDCARD_ENABLED flag
caugner Feb 28, 2025
b564b3b
Revert "chore(review-deploy): increase min-instances to 1"
caugner Feb 28, 2025
a5de311
chore(cloud-function,libs/play): inline ORIGIN_REVIEW
caugner Mar 3, 2025
5bb3d05
chore(deployer): use new review host
caugner Mar 3, 2025
e8ce2a1
chore(dev-build): deploy to GCP review environment
caugner Mar 3, 2025
c29f35c
chore(dev-build): deploy on push to this branch
caugner Mar 3, 2025
fa85113
fixup! chore(dev-build): deploy to GCP review environment
caugner Mar 3, 2025
ca9c7ea
fix(dev-build): ensure non-empty PREFIX
caugner Mar 3, 2025
4163372
test(dev-build): change default deployment prefix
caugner Mar 3, 2025
8a0dc6d
Revert "test(dev-build): change default deployment prefix"
caugner Mar 3, 2025
912e7e0
fix(dev-build): set BUILD_OUT_ROOT
caugner Mar 3, 2025
de12689
fix(dev-build): check out generic-content etc
caugner Mar 3, 2025
15d8c97
chore(dev-build): use upload-cloud-storage action
caugner Mar 3, 2025
c5871ef
chore(dev-build): tweak upload-cloud-storage settings
caugner Mar 3, 2025
52ff497
feat(cloud-function): fallback to prod for review assets
caugner Mar 3, 2025
f16cfc9
fixup! chore(dev-build): tweak upload-cloud-storage settings
caugner Mar 3, 2025
62f7a97
fixup! fix(dev-build): ensure non-empty PREFIX
caugner Mar 3, 2025
606e696
Revert "chore(dev-build): deploy on push to this branch"
caugner Mar 3, 2025
713026b
fix(review-deploy): disallow unauthenticated access
caugner Mar 3, 2025
1b8074e
Apply suggestions from code review
caugner Mar 3, 2025
fe3f27a
refactor(cloud-function): improve proxyContent router
caugner Mar 3, 2025
855493a
chore(cloud-function): pass proxy target via header
caugner Mar 3, 2025
5c6345e
Merge branch 'main' into MP-1889-review-cloud-function
caugner Mar 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 88 additions & 79 deletions .github/workflows/dev-build.yml
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One could argue that this should be called review-build, because it deploys to $prefix.review.mdn.allizom.net, but since we have rarely used this, let's just keep it as is.

Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,35 @@ on:
required: false
default: ""

# This is very useful when combined with the "Use workflow from"
# feature that is built into the "Run workflow" button on
# https://github.com/mdn/yari/actions?query=workflow%3A%22Production+Build%22
# If you override the deployment prefix to something like the name
# of the branch, you can deploy that entire branch to its own prefix
# in S3 which means that it can be fully hosted as its own site.
deployment_prefix:
description: "Deployment prefix"
required: false
default: "main"

log_each_successful_upload:
description: "Deployer logs each success"
required: false
default: "false"
workflow_call:
secrets:
GCP_PROJECT_NAME:
required: true
WIP_PROJECT_ID:
required: true

env:
DEFAULT_DEPLOYMENT_PREFIX: "main"

permissions:
# Authenticate with GCP.
id-token: write

jobs:
build:
environment: review
runs-on: ubuntu-latest

steps:
- name: Merge dispatch inputs with default env vars
run: |
echo "PREFIX=${{ github.event.inputs.deployment_prefix || env.DEFAULT_DEPLOYMENT_PREFIX }}" >> "$GITHUB_ENV"

- uses: actions/checkout@v4

- uses: actions/checkout@v4
Expand All @@ -51,13 +59,47 @@ jobs:
# so we can figure out each document's last-modified date.
fetch-depth: 0

- uses: actions/checkout@v4
with:
repository: mdn/mdn-studio
path: mdn/mdn-studio
lfs: true
token: ${{ secrets.MDN_STUDIO_PAT }}

- uses: actions/checkout@v4
with:
repository: mdn/generic-content
path: mdn/generic-content

- uses: actions/checkout@v4
with:
repository: mdn/curriculum
path: mdn/curriculum

- uses: actions/checkout@v4
with:
repository: mdn/translated-content
path: mdn/translated-content
# See matching warning for mdn/content checkout step
fetch-depth: 0

- name: Checkout (translated-content-de)
uses: actions/checkout@v4
with:
repository: mdn/translated-content-de
path: mdn/translated-content-de

- name: Move de into translated-content
run: |
mv mdn/translated-content-de/files/de mdn/translated-content/files/
rm -rf mdn/translated-content-de

- name: Clean and commit de
working-directory: mdn/translated-content
run: |
git add files/de
git -c user.name='MDN' -c user.email='[email protected]' commit -m 'de'

- uses: actions/checkout@v4
with:
repository: mdn/mdn-contributor-spotlight
Expand Down Expand Up @@ -96,8 +138,7 @@ jobs:
- name: Print information about build
run: |
echo "notes: ${{ github.event.inputs.notes }}"
echo "log_each_successful_upload: ${{ github.event.inputs.log_each_successful_upload }}"
echo "deployment_prefix: ${{ github.event.inputs.deployment_prefix }}"
echo "PREFIX: ${{ env.PREFIX }}"

- name: Print information about CPU
run: cat /proc/cpuinfo
Expand All @@ -109,6 +150,12 @@ jobs:
CONTENT_ROOT: ${{ github.workspace }}/mdn/content/files
CONTENT_TRANSLATED_ROOT: ${{ github.workspace }}/mdn/translated-content/files
CONTRIBUTOR_SPOTLIGHT_ROOT: ${{ github.workspace }}/mdn/mdn-contributor-spotlight/contributors
BLOG_ROOT: ${{ github.workspace }}/mdn/mdn-studio/content/posts
CURRICULUM_ROOT: ${{ github.workspace }}/mdn/curriculum
GENERIC_CONTENT_ROOT: ${{ github.workspace }}/mdn/generic-content/files

# rari
BUILD_OUT_ROOT: "client/build"

# This basically means that all live-sample iframes run on the same
# host as the page that includes the iframe. Not great security but the
Expand Down Expand Up @@ -136,95 +183,57 @@ jobs:
# Info about which CONTENT_* environment variables were set and to what.
echo "CONTENT_ROOT=$CONTENT_ROOT"
echo "CONTENT_TRANSLATED_ROOT=$CONTENT_TRANSLATED_ROOT"
yarn build:prepare

yarn tool sync-translated-content

# Spread the work across 2 processes. Why 2? Because that's what you
# get in the default GitHub hosting Linux runners.
# See https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources
yarn build:docs --locale en-us --locale ja --locale fr &
build1=$!
yarn build:docs --not-locale en-us --not-locale ja --not-locale fr &
build2=$!
# Build the ServiceWorker first
yarn build:sw
yarn build:client
yarn build:ssr

# You must explicitly specify the job you're waiting-on to ensure
# that the exit status of the wait command reflects the exit status
# of the job it's waiting-on.
wait $build1
wait $build2
cp assets/nonprod/robots.txt client/build/robots.txt

# TODO: When the deployer is available this is where we
# would upload the whole content of client/build
du -sh client/build
yarn rari content sync-translated-content
yarn rari git-history

# Generate sitemap index file
yarn build --sitemap-index
yarn rari build --all --issues client/build/issues.json --templ-stats

# SSR all pages
yarn render:html

# Generate whatsdeployed files.
yarn tool whatsdeployed --output client/build/_whatsdeployed/code.json
yarn tool whatsdeployed $CONTENT_ROOT --output client/build/_whatsdeployed/content.json
yarn tool whatsdeployed $CONTENT_TRANSLATED_ROOT --output client/build/_whatsdeployed/translated-content.json
yarn tool:legacy whatsdeployed --output client/build/_whatsdeployed/code.json
yarn tool:legacy whatsdeployed $CONTENT_ROOT --output client/build/_whatsdeployed/content.json
yarn tool:legacy whatsdeployed $CONTENT_TRANSLATED_ROOT --output client/build/_whatsdeployed/translated-content.json

# Sort DE search index by en-US popularity.
node scripts/reorder-search-index.mjs client/build/en-us/search-index.json client/build/de/search-index.json

- name: Deploy with deployer
- name: Update search index
env:
# Set the CONTENT_ROOT first
CONTENT_ROOT: ${{ github.workspace }}/mdn/content/files
CONTENT_TRANSLATED_ROOT: ${{ github.workspace }}/mdn/translated-content/files

DEPLOYER_BUCKET_NAME: mdn-content-dev
DEPLOYER_BUCKET_PREFIX: ${{ github.event.inputs.deployment_prefix }}
DEPLOYER_LOG_EACH_SUCCESSFUL_UPLOAD: ${{ github.event.inputs.log_each_successful_upload }}

AWS_ACCESS_KEY_ID: ${{ secrets.DEPLOYER_STAGE_AND_DEV_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.DEPLOYER_STAGE_AND_DEV_AWS_SECRET_ACCESS_KEY }}

DEPLOYER_ELASTICSEARCH_URL: ${{ secrets.DEPLOYER_DEV_ELASTICSEARCH_URL }}

run: |
if [ ${{ github.event.inputs.translated_content }} == "true" ]; then
echo "Will build mdn/translated-content too"
export CONTENT_TRANSLATED_ROOT=${{ github.workspace }}/mdn/translated-content/files
else
echo "Will NOT build mdn/translated-content too"
fi

# Info about which CONTENT_* environment variables were set and to what.
echo "CONTENT_ROOT=$CONTENT_ROOT"
echo "CONTENT_TRANSLATED_ROOT=$CONTENT_TRANSLATED_ROOT"

cd deployer

# XXX would be nice to validate here that $DEPLOYER_BUCKET_PREFIX is truthy
echo "DEPLOYER_BUCKET_PREFIX=$DEPLOYER_BUCKET_PREFIX"

poetry run deployer upload --prune ../client/build
poetry run deployer search-index ../client/build

- name: Configure AWS Credentials
uses: aws-actions/[email protected]
- name: Authenticate with GCP
uses: google-github-actions/auth@v2
with:
aws-access-key-id: ${{ secrets.DEPLOYER_STAGE_AND_DEV_AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.DEPLOYER_STAGE_AND_DEV_AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
token_format: access_token
service_account: deploy-mdn-review-content@${{ secrets.GCP_PROJECT_NAME }}.iam.gserviceaccount.com
workload_identity_provider: projects/${{ secrets.WIP_PROJECT_ID }}/locations/global/workloadIdentityPools/github-actions/providers/github-actions

- name: Invalidate CDN
env:
DISTRIBUTION: E9813D0RN1QZI
PATHS: /*
run: aws cloudfront create-invalidation --distribution-id "$DISTRIBUTION" --paths "$PATHS"
- name: Setup gcloud
uses: google-github-actions/setup-gcloud@v2

- name: Sync build with GCS
uses: "google-github-actions/upload-cloud-storage@v2"
with:
path: "client/build"
destination: "${{ vars.GCP_BUCKET_NAME }}/${{ env.PREFIX }}"
resumable: false
concurrency: 500
process_gcloudignore: false

- name: Notify PRs about deployment
run: |
gh pr list -S "$GITHUB_SHA -is:merged" --json number --jq '.[].number' | xargs -i gh pr comment {} --body "Dev build for $GITHUB_SHA was deployed to: $DEPLOYMENT_URL" || true
env:
DEPLOYMENT_URL: https://${{ github.event.inputs.deployment_prefix }}.content.dev.mdn.mozit.cloud/
DEPLOYMENT_URL: https://${{ env.PREFIX }}.review.mdn.allizom.net/
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Slack Notification
Expand Down
124 changes: 124 additions & 0 deletions .github/workflows/review-deploy.yml
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: I called this workflow review-deploy to avoid confusion with {prod,stage,test}-build that actually build and deploy content, and to decouple the Cloud Function deployment from the dev-build.

Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
name: Review Deployment

on:
push:
branches:
- "MP-1889-review-cloud-function"

Comment on lines +4 to +7
Copy link
Contributor Author

@caugner caugner Feb 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove before merging:

Suggested change
push:
branches:
- "MP-1889-review-cloud-function"

schedule:
# * is a special character in YAML so you have to quote this string
- cron: "0 */24 * * *"

workflow_dispatch:
inputs:
notes:
description: "Notes"
required: false
default: ${DEFAULT_NOTES}

invalidate:
description: "Invalidate CDN (use only in exceptional circumstances)"
type: boolean
required: false
default: false

workflow_call:
secrets:
GCP_PROJECT_NAME:
required: true
WIP_PROJECT_ID:
required: true

permissions:
contents: read
id-token: write

jobs:
deploy:
environment: review
runs-on: ubuntu-latest

if: ${{ github.repository == 'mdn/yari' }}

steps:
- name: Print information about CPU
run: cat /proc/cpuinfo

- uses: actions/checkout@v4

- name: Setup Node.js environment
uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
cache: yarn

- name: Prepare Cloud Function
working-directory: cloud-function
run: |
npm ci
echo "{}" > redirects.json
echo "{}" > canonicals.json

- name: Authenticate with GCP
if: ${{ ! vars.SKIP_FUNCTION }}
uses: google-github-actions/auth@v2
with:
token_format: access_token
service_account: deploy-mdn-review-functions@${{ secrets.GCP_PROJECT_NAME }}.iam.gserviceaccount.com
workload_identity_provider: projects/${{ secrets.WIP_PROJECT_ID }}/locations/global/workloadIdentityPools/github-actions/providers/github-actions

- name: Setup gcloud
if: ${{ ! vars.SKIP_FUNCTION }}
uses: google-github-actions/setup-gcloud@v2
with:
install_components: "beta"

- name: Deploy Function
if: ${{ ! vars.SKIP_FUNCTION }}
run: |-
set -eo pipefail

for region in europe-west3; do
gcloud beta functions deploy mdn-review-$region \
--gen2 \
--runtime=nodejs20 \
--region=$region \
--source=cloud-function \
--trigger-http \
--entry-point=mdnHandler \
--concurrency=100 \
--min-instances=0 \
--max-instances=10 \
--memory=2GB \
--timeout=120s \
--run-service-account=run-mdn-review-functions@${{ secrets.GCP_PROJECT_NAME }}.iam.gserviceaccount.com \
--set-env-vars="WILDCARD_ENABLED=true" \
--set-env-vars="SOURCE_CONTENT=https://storage.googleapis.com/${{ vars.GCP_BUCKET_NAME }}/" \
--set-env-vars="BSA_ENABLED=true" \
--set-env-vars="SENTRY_DSN=${{ secrets.SENTRY_DSN_CLOUD_FUNCTION }}" \
--set-env-vars="SENTRY_ENVIRONMENT=review" \
--set-env-vars="SENTRY_TRACES_SAMPLE_RATE=${{ vars.SENTRY_TRACES_SAMPLE_RATE }}" \
--set-env-vars="SENTRY_RELEASE=${{ github.sha }}" \
--set-secrets="KEVEL_SITE_ID=projects/${{ secrets.GCP_PROJECT_NAME }}/secrets/stage-kevel-site-id/versions/latest" \
--set-secrets="KEVEL_NETWORK_ID=projects/${{ secrets.GCP_PROJECT_NAME }}/secrets/stage-kevel-network-id/versions/latest" \
--set-secrets="SIGN_SECRET=projects/${{ secrets.GCP_PROJECT_NAME }}/secrets/stage-sign-secret/versions/latest" \
--set-secrets="BSA_ZONE_KEYS=projects/${{ secrets.GCP_PROJECT_NAME }}/secrets/stage-bsa-zone-keys/versions/latest" \
2>&1 | sed "s/^/[$region] /" &
pids+=($!)
done

for pid in "${pids[@]}"; do
wait $pid
done

- name: Slack Notification
if: failure()
uses: rtCamp/action-slack-notify@v2
env:
SLACK_CHANNEL: mdn-notifications
SLACK_COLOR: ${{ job.status }}
SLACK_ICON: https://avatars.slack-edge.com/2020-11-17/1513880588420_fedd7f0e9456888e69ff_96.png
SLACK_TITLE: "Stage"
SLACK_MESSAGE: "Build failed :collision:"
SLACK_FOOTER: "Powered by stage-build.yml"
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ node_modules/
/client/.env
__pycache__/
.idea/
tsconfig.tsbuildinfo

*.log
npm-debug.log*
Expand Down
5 changes: 3 additions & 2 deletions cloud-function/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,13 @@ The function uses the following environment variables:
requests to the main site.
- `ORIGIN_LIVE_SAMPLES` (default: `"localhost"`) - The expected `Host` header
value for requests to live samples.
- `ORIGIN_REVIEW` (default: `"content.dev.mdn.mozit.cloud"`) - The host of the
content preview pages.
- `SOURCE_CONTENT` (default: `"http://localhost:8100"`) - The URL at which the
client build is served.
- `SOURCE_API` (default: `"https://developer.allizom.org/"`) - The URL at which
the API is served.
- `WILDCARD_ENABLED` (default: `false`) - If enabled, accepts any `Host` header
value, and uses the leftmost subdomain to route into a subdirectory of
`SOURCE_CONTENT`.

The placement handler uses the following environment variables:

Expand Down
Loading
Loading