diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 5dbc319b501f..d2cebadd8ba8 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -769,24 +769,21 @@ jobs: CF_API_KEY: ${{ secrets.CLOUDFLARE_TOKEN }} HOST: ${{ env.DEPLOY_ENV == 'production' && vars.WEB_PRODUCTION_HOST || vars.WEB_STAGING_HOST }} + # The staging host is verified by webDeployStagingWorker, which deploys after this job. - name: Verify deploy + if: ${{ env.DEPLOY_ENV == 'production' }} run: | APP_VERSION=$(jq -r .version < package.json) ./.github/scripts/verifyDeploy.sh "$HOST" "$APP_VERSION" env: - HOST: ${{ env.DEPLOY_ENV == 'production' && vars.WEB_PRODUCTION_HOST || vars.WEB_STAGING_HOST }} + HOST: ${{ vars.WEB_PRODUCTION_HOST }} - webDeployCloudflareWorkers: - name: Deploy Web to Cloudflare Workers (${{ matrix.environment }}) + webDeployTestStaging: + name: Deploy Web to test-staging.new.expensify.com needs: [prep, webBuild, buildStorybook] if: ${{ always() && needs.webBuild.result == 'success' && needs.prep.outputs.DEPLOY_ENV == 'staging' }} # Trial hosting for the web build, so a failure here must not fail the staging deploy. continue-on-error: true - strategy: - fail-fast: false - matrix: - # `staging` serves no traffic until staging.new.expensify.com is routed to its Worker in Cloudflare. - environment: [test-staging, staging] runs-on: blacksmith-2vcpu-ubuntu-2404 steps: - name: Checkout @@ -812,10 +809,65 @@ jobs: - name: Extract web build run: tar -xzf "${{ needs.webBuild.outputs.TAR_FILENAME }}" - # The Workers serve these to tabs still running this build after later deploys. Both Workers read the same bucket, - # so only one matrix leg uploads. + - name: Download storybook docs artifact + continue-on-error: true + # v7 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 + with: + name: storybook-docs-artifact + path: ./dist/docs + + - name: Install Cloudflare Worker dependencies + working-directory: web/cloudflare-worker + run: npm ci + + - name: Deploy to Cloudflare Workers + working-directory: web/cloudflare-worker + run: npx wrangler deploy --env test-staging + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_WORKERS_DEPLOY_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + WRANGLER_SEND_METRICS: false + + - name: Verify deploy + run: | + APP_VERSION=$(jq -r .version < package.json) + ./.github/scripts/verifyDeploy.sh "$HOST" "$APP_VERSION" + env: + HOST: https://test-staging.new.expensify.com + + webDeployStagingWorker: + name: Deploy Web to the staging Cloudflare Worker + # After webDeploy, so the S3 copy of this build is in place before the Worker's version check. + needs: [prep, webBuild, buildStorybook, webDeploy] + if: ${{ always() && needs.webBuild.result == 'success' && needs.prep.outputs.DEPLOY_ENV == 'staging' }} + runs-on: blacksmith-2vcpu-ubuntu-2404 + steps: + - name: Checkout + uses: useblacksmith/checkout@0647fdbab2614a5eb86d2971070e325060d91056 # v1.7.0 + with: + ref: ${{ needs.prep.outputs.DEPLOY_SHA }} + + - name: Setup Node + # v6.1.0 + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + with: + node-version-file: '.nvmrc' + cache: npm + cache-dependency-path: web/cloudflare-worker/package-lock.json + + - name: Download web build artifact + # v7 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 + with: + name: web-build-tar-gz-artifact + path: ./ + + - name: Extract web build + run: tar -xzf "${{ needs.webBuild.outputs.TAR_FILENAME }}" + + # The Workers serve these to tabs still running this build after later deploys. - name: Upload web build to the previous builds bucket - if: ${{ matrix.environment == 'staging' }} continue-on-error: true run: | if [[ -z "$AWS_ACCESS_KEY_ID" ]]; then @@ -848,17 +900,18 @@ jobs: - name: Deploy to Cloudflare Workers working-directory: web/cloudflare-worker - run: npx wrangler deploy --env "${{ matrix.environment }}" + run: npx wrangler deploy --env staging env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_WORKERS_DEPLOY_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} WRANGLER_SEND_METRICS: false - name: Verify deploy - if: ${{ matrix.environment == 'test-staging' }} run: | APP_VERSION=$(jq -r .version < package.json) - ./.github/scripts/verifyDeploy.sh https://test-staging.new.expensify.com "$APP_VERSION" + ./.github/scripts/verifyDeploy.sh "$HOST" "$APP_VERSION" + env: + HOST: ${{ vars.WEB_STAGING_HOST }} buildStorybook: name: Build storybook docs @@ -907,6 +960,7 @@ jobs: iosSubmit, webBuild, webDeploy, + webDeployStagingWorker, victoryChartRendererBuild, ] steps: @@ -922,7 +976,7 @@ jobs: name: Post a Slack message when the deploy is cancelled manually runs-on: blacksmith-2vcpu-ubuntu-2404 if: ${{ cancelled() }} - needs: [androidBuild, androidUploadGooglePlay, androidSubmit, iosBuild, iosUploadTestflight, iosSubmit, webBuild, webDeploy] + needs: [androidBuild, androidUploadGooglePlay, androidSubmit, iosBuild, iosUploadTestflight, iosSubmit, webBuild, webDeploy, webDeployStagingWorker] steps: - name: Check whether a newer deploy run exists id: check @@ -961,7 +1015,7 @@ jobs: ANDROID_RESULT: ${{ steps.platformResults.outputs.ANDROID_RESULT }} IOS_RESULT: ${{ steps.platformResults.outputs.IOS_RESULT }} WEB_RESULT: ${{ steps.platformResults.outputs.WEB_RESULT }} - needs: [androidBuild, androidUploadGooglePlay, androidSubmit, iosBuild, iosUploadTestflight, iosSubmit, webBuild, webDeploy, victoryChartRendererBuild] + needs: [androidBuild, androidUploadGooglePlay, androidSubmit, iosBuild, iosUploadTestflight, iosSubmit, webBuild, webDeploy, webDeployStagingWorker, victoryChartRendererBuild] if: ${{ always() }} steps: # Determine effective result for each platform @@ -996,9 +1050,11 @@ jobs: echo "IOS_RESULT=${{ needs.iosUploadTestflight.result }}" >> "$GITHUB_OUTPUT" fi - # Web: propagate build failure even when deploy is skipped + # Web: propagate build failure even when deploy is skipped. Staging is only deployed once its Worker is too. if [ "${{ needs.webBuild.result }}" == "failure" ]; then echo "WEB_RESULT=failure" >> "$GITHUB_OUTPUT" + elif [ "${{ env.DEPLOY_ENV }}" == "staging" ] && [ "${{ needs.webDeployStagingWorker.result }}" != "success" ]; then + echo "WEB_RESULT=${{ needs.webDeployStagingWorker.result }}" >> "$GITHUB_OUTPUT" else echo "WEB_RESULT=${{ needs.webDeploy.result }}" >> "$GITHUB_OUTPUT" fi diff --git a/web/cloudflare-worker/README.md b/web/cloudflare-worker/README.md index e57f421bdae3..bcdb5556a404 100644 --- a/web/cloudflare-worker/README.md +++ b/web/cloudflare-worker/README.md @@ -111,7 +111,9 @@ The script diffs status, caching and security headers for a fixed set of paths. | `staging` | `staging-new-expensify-assets` | None until switchover, then `staging.new.expensify.com/*` (managed in Cloudflare, not in `wrangler.jsonc`) | Staging | | `production` | `new-expensify-assets` | None until switchover, then `new.expensify.com/*` | Production | -Every staging deploy also deploys the same build to the `test-staging` and `staging` Workers (the `webDeployCloudflareWorkers` job in `.github/workflows/deploy.yml`). It uses `CLOUDFLARE_WORKERS_DEPLOY_TOKEN`, which can only edit those two Workers. That token can't read zones, so routes aren't in `wrangler.jsonc`: wrangler would try to sync them and fail. A failure there does not fail the staging deploy. +Every staging deploy also deploys the same build to the `staging` Worker (the `webDeployStagingWorker` job in `.github/workflows/deploy.yml`) and the `test-staging` Worker (`webDeployTestStaging`). Both use `CLOUDFLARE_WORKERS_DEPLOY_TOKEN`, which can only edit those two Workers. That token can't read zones, so routes aren't in `wrangler.jsonc`: wrangler would try to sync them and fail. + +The `staging` deploy runs after the S3 upload and is required: if it fails, or `staging.new.expensify.com/version.json` doesn't report the new version, the staging deploy fails. The `test-staging` deploy is a trial, so its failures don't affect the staging deploy. `test-staging` serves a staging build on a hostname nobody else uses. `staging` serves nothing until `staging.new.expensify.com/*` is pointed at its Worker in Cloudflare.