Deploying Open Source Pull Requests to Firebase Hosting
The Problem
I volunteer at Code for Boston. I help out with web app development, contributing code and reviewing contributions. All of our projects are open source, run on Github, and follow the fork-and-pull development model.
We built my last two projects using Next.js on Firebase Hosting, and our CI runs on Github Actions. Firebase provides some great tooling: firebase init
can set up workflows for CD off of a specific branch and preview deployments for pull requests.
Unfortunately, the preview deployment workflow does not work across forks. Workflows that run on pull requests from forks do not have access to Github secrets. Otherwise, a malicious user could craft a pull request to leak secrets by printing them out. Since we can’t secrets, we can’t provide Firebase credentials to the PR workflow.
Without automated deployments for pull requests, reviewers have to check out the pull request branch and run the site themselves. This can be a significant barrier to participation, especially for non-developers looking at UI.
The Solution
We automate PR deploys by splitting the workflow into two. We build the application in the PR workflow, store the static files as an artifact, and deploy it using a second workflow triggered using the workflow_run
action trigger. This trigger allows running a workflow after the completion of another specified workflow. The triggered workflow always runs at the tip of the default branch. This way, it runs with trusted code, and so has access to secrets.
Building The Site
This workflow runs on each pull request, building the site and uploading to an artifact:
name: Build Pull Request
on: [pull_request]
jobs:
build:
# Don't build forks, even if actions are enabled
if: github.repository_owner == 'codeforboston'
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v2
# Set up the build environment using a local composite action
- name: Setup Build Environment
uses: ./.github/actions/setup-repo
# Build the app, putting the static files in ./out/
- name: Build App
run: yarn export:nolint
# Upload the static files to an artifact
- name: Export App
uses: actions/upload-artifact@v2
if: github.event_name == 'pull_request'
with:
name: digital-testimony-app
path: out
retention-days: 1
Deploying The Site
This workflow deploys the artifact uploaded by the previous workflow to a preview URL and posts it to the PR conversation for review:
name: Deploy Pull Request
on:
workflow_run:
workflows: ["Build Pull Request"]
types:
- completed
jobs:
deploy:
name: Deploy Pull Request
runs-on: ubuntu-latest
# Don't build forks, even if actions are enabled
if: github.event.workflow_run.conclusion == 'success' && github.repository_owner == 'codeforboston'
environment: dev
steps:
- name: Checkout code
uses: actions/checkout@v2
# Download the site's static files and deploy them to Firebase Hosting
- name: Deploy App to Firebase Hosting
uses: alexjball/action-hosting-deploy@v1
with:
repoToken: "$"
firebaseServiceAccount: "$"
projectId: digital-testimony-dev
artifactName: digital-testimony-app
Note that this workflow runs on the default branch, and so uses the Firebase configuration from that branch. Only the content of the site is affected by the PR.
We reused the Firebase deployment action, patching it to accept the artifactName
of the site artifact uploaded in the triggering workflow. The action can resolve the artifact using the Github API.
You can generate a service account for firebaseServiceAccount
using firebase init
. Just ignore the workflow files it generates.
Is This Safe?
This process deploys external contributor code. Can this be abused? Not really, at least not any more than one could with a local development frontend, which is publicly accessible. Both use development client keys, and both are allowed domains on our Firebase projects. And the development environment contains no private data, so phishing-styles attacks are not an issue.
Conclusion
Splitting the build and deploy steps between workflows using the workflow_run
trigger allowed us to easily deploy pull requests to preview URL’s. This enables more people to participate in reviews and makes it easy to review frontend changes without checking out the PR branch.