Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
82 changes: 5 additions & 77 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -703,74 +703,6 @@ jobs:
release-tag: ${{ needs.prep.outputs.TAG }}
secrets: inherit

webDeploy:
name: Deploy Web to S3
needs: [prep, webBuild, buildStorybook]
if: ${{ always() && needs.webBuild.result == 'success' }}
# Preserve the default token scopes while adding OIDC access
permissions: write-all
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- name: Checkout
uses: useblacksmith/checkout@0647fdbab2614a5eb86d2971070e325060d91056 # v1.7.0
with:
ref: ${{ needs.prep.outputs.DEPLOY_SHA }}

- 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 -xzvf "${{ needs.webBuild.outputs.TAR_FILENAME }}"

- name: Download storybook docs artifact
continue-on-error: true
# v7
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
with:
name: storybook-docs-artifact
path: ./dist/docs

- name: Setup Cloudflare CLI
run: pip3 install cloudflare==2.19.0

- name: Configure AWS Credentials
# v6
uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7
with:
role-to-assume: arn:aws:iam::949552126497:role/app-${{ env.DEPLOY_ENV }}-web-deploy-gh-actions
role-session-name: gh-${{ github.run_id }}-${{ github.run_attempt }}
aws-region: us-east-1

- name: Deploy to S3
run: |
aws s3 cp --recursive --acl public-read --exclude "*.br" "$GITHUB_WORKSPACE"/dist ${{ env.S3_BUCKET }}/

for PAIR in js:text/javascript css:text/css html:text/html svg:image/svg+xml wasm:application/wasm ttf:font/ttf; do
EXT="${PAIR%%:*}"
CONTENT_TYPE="${PAIR#*:}"
aws s3 cp --recursive --acl public-read --exclude "*" --include "*.${EXT}.br" \
--content-encoding br --content-type "$CONTENT_TYPE" \
"$GITHUB_WORKSPACE"/dist ${{ env.S3_BUCKET }}/
done

aws s3 cp --acl public-read --content-type 'application/json' --metadata-directive REPLACE ${{ env.S3_BUCKET }}/.well-known/apple-app-site-association ${{ env.S3_BUCKET }}/.well-known/apple-app-site-association
aws s3 cp --acl public-read --content-type 'application/json' --metadata-directive REPLACE ${{ env.S3_BUCKET }}/.well-known/apple-app-site-association ${{ env.S3_BUCKET }}/apple-app-site-association
env:
S3_BUCKET: s3://${{ env.DEPLOY_ENV == 'staging' && 'staging-' || '' }}${{ vars.PRODUCTION_S3_BUCKET }}

- name: Purge Cloudflare cache
run: |
/home/runner/.local/bin/cli4 --verbose --delete hosts=["$HOST"] /zones/:9ee042e6cfc7fd45e74aa7d2f78d617b/purge_cache
env:
CF_API_KEY: ${{ secrets.CLOUDFLARE_TOKEN }}
HOST: ${{ env.DEPLOY_ENV == 'production' && vars.WEB_PRODUCTION_HOST || vars.WEB_STAGING_HOST }}

# The host is verified by webDeployWorker, which deploys after this job.

webDeployTestStaging:
name: Deploy Web to test-staging.new.expensify.com
needs: [prep, webBuild, buildStorybook]
Expand Down Expand Up @@ -829,10 +761,9 @@ jobs:
env:
HOST: https://test-staging.new.expensify.com

webDeployWorker:
webDeploy:
name: Deploy Web to the 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]
needs: [prep, webBuild, buildStorybook]
if: ${{ always() && needs.webBuild.result == 'success' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
Expand Down Expand Up @@ -953,7 +884,6 @@ jobs:
iosSubmit,
webBuild,
webDeploy,
webDeployWorker,
victoryChartRendererBuild,
]
steps:
Expand All @@ -969,7 +899,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, webDeployWorker]
needs: [androidBuild, androidUploadGooglePlay, androidSubmit, iosBuild, iosUploadTestflight, iosSubmit, webBuild, webDeploy]
steps:
- name: Check whether a newer deploy run exists
id: check
Expand Down Expand Up @@ -1008,7 +938,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, webDeployWorker, victoryChartRendererBuild]
needs: [androidBuild, androidUploadGooglePlay, androidSubmit, iosBuild, iosUploadTestflight, iosSubmit, webBuild, webDeploy, victoryChartRendererBuild]
if: ${{ always() }}
steps:
# Determine effective result for each platform
Expand Down Expand Up @@ -1043,11 +973,9 @@ jobs:
echo "IOS_RESULT=${{ needs.iosUploadTestflight.result }}" >> "$GITHUB_OUTPUT"
fi

# Web: propagate build failure even when deploy is skipped. Web is only deployed once its Worker is too.
# Web: propagate build failure even when deploy is skipped.
if [ "${{ needs.webBuild.result }}" == "failure" ]; then
echo "WEB_RESULT=failure" >> "$GITHUB_OUTPUT"
elif [ "${{ needs.webDeployWorker.result }}" != "success" ]; then
echo "WEB_RESULT=${{ needs.webDeployWorker.result }}" >> "$GITHUB_OUTPUT"
else
echo "WEB_RESULT=${{ needs.webDeploy.result }}" >> "$GITHUB_OUTPUT"
fi
Expand Down
4 changes: 2 additions & 2 deletions tests/tooling/verifyStagingBuildsSucceeded.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ function stagingDeployJobs(overrides: Array<[string, JobConclusion | null]> = []
[IOS_BUILD, 'success'],
[IOS_UPLOAD, 'success'],
['Build Web / Build Web', 'success'],
['Deploy Web to S3', 'success'],
['Deploy Web to the Cloudflare Worker', 'success'],

// Production-only, skipped on every staging deploy.
['Submit Android for production rollout', 'skipped'],
Expand Down Expand Up @@ -188,7 +188,7 @@ describe('verifyStagingBuildsSucceeded', () => {
test('ignores a failed web deploy and the continue-on-error uploads', async () => {
mockListJobs.mockResolvedValue(
stagingDeployJobs([
['Deploy Web to S3', 'failure'],
['Deploy Web to the Cloudflare Worker', 'failure'],
['Upload iOS to BrowserStack', 'failure'],
['Upload Android to Applause', 'failure'],
]),
Expand Down
4 changes: 2 additions & 2 deletions web/cloudflare-worker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,9 @@ The script diffs status, caching and security headers for a fixed set of paths.
| `staging` | `staging-new-expensify-assets` | `staging.new.expensify.com/*` (managed in Cloudflare, not in `wrangler.jsonc`) | Staging |
| `production` | `new-expensify-assets` | None until switchover, then `new.expensify.com/*` (managed in Cloudflare, not in `wrangler.jsonc`) | Production |

Every staging and production deploy also deploys the same build to that environment's Worker (the `webDeployWorker` job in `.github/workflows/deploy.yml`). Staging deploys also deploy it to the `test-staging` Worker (`webDeployTestStaging`). They use `CLOUDFLARE_WORKERS_DEPLOY_TOKEN`, which can only edit these three Workers. That token can't read zones, so routes aren't in `wrangler.jsonc`: wrangler would try to sync them and fail.
Every staging and production deploy also deploys the same build to that environment's Worker (the `webDeploy` job in `.github/workflows/deploy.yml`). Staging deploys also deploy it to the `test-staging` Worker (`webDeployTestStaging`). They use `CLOUDFLARE_WORKERS_DEPLOY_TOKEN`, which can only edit these three Workers. That token can't read zones, so routes aren't in `wrangler.jsonc`: wrangler would try to sync them and fail.

The Worker deploy runs after `webDeploy` and is required: if it fails, or `WEB_STAGING_HOST` or `WEB_PRODUCTION_HOST` doesn't report the new version in `/version.json`, the deploy fails. The version is checked there because both jobs have deployed by then, whichever one serves the host. The `test-staging` deploy is a trial, so its failures don't affect the staging deploy.
The Worker deploy is required: if it fails, or `WEB_STAGING_HOST` or `WEB_PRODUCTION_HOST` doesn't report the new version in `/version.json`, the deploy fails. A new Worker version takes effect immediately, so no cache needs purging. 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.

Expand Down
Loading