Preview URLs with Azure Pipeline + Firebase Hosting

Photo by Lopez Robin on Unsplash

Preview URLs with Azure Pipeline + Firebase Hosting

Introduction

I recently had to implement an Azure pipeline for a Next.js app. Giving a Pull Request on our remote repository, the requirements were:

  1. On a Pull Request update, deploy the web app of this version on a preview URL. For this, we have to use Firebase Hosting Channels.

  2. When a Pull Request is merged into the main branch, deploy the new version on an official URL and delete the channel created for this Pull Request.

Here go the key points to meet these requirements.

Preview and official versions

Let's start with the easy part. To deploy our "not completed" version on a Firebase channel I assume you have set up your Firebase account and have the Hosting service ready to use (docs).

First, we need to configure the pipeline to trigger whenever a PR gets updated. (Azure docs on pipeline triggers). Here we match the Pull Requests whose target branch is main.

pr:
  branches:
    include:
      - main

The whole control will live here. We conditionally set a global variable using the Azure predefined variable Build.Reason.

variables:
  ${{ if eq(variables['Build.Reason'], 'PullRequest') }}:
    DEPLOY_PREVIEW: true
  ${{ if ne(variables['Build.Reason'], 'PullRequest') }}:
    DEPLOY_PREVIEW: false

To allow your pipeline to do anything in your firebase project, you need to use tokens. Store your firebase token on a variable group. On the dev.azure.com of your project, go to Pipelines and then Library. You will see a button "+ Variable group". Name your variable group, name your variable key and store your firebase token as the value (docs). I named mine as FIREBASE_TOKEN .

To get the variable group from the pipeline do the following:

stages:
  - stage: buildAndDeploy
    displayName: Build and deploy
    variables:
      - group: MY_VARIABLE_GROUP

OBS: This variable group can go at the global level (like DEPLOY_PREVIEW we just set). The code above is putting the variables available only for the stage buildAndDeploy. I had problems trying to set the DEPLOY_PREVIEW conditionally together with a variable group. For our simple case, this is enough.

After running the normal pipeline steps (lint, test and build) it is time to deploy. First, we need to set up firebase CLI.

 - task: CmdLine@2
   displayName: 'Setup firebase CLI'
   inputs:
     script: |
       npm install -g firebase-tools
       firebase use <firebase-proj-name> --token=$(FIREBASE_TOKEN)

Now, be sure that your firebase.json is available (through files tracked by git or Azure Library Secure files) with configuration for the Hosting service. A simple configuration looks like this:

{  
  "hosting": {
    "public": "dist",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ]
}

See firebase docs for more options on Hosting service configuration. After this CLI setup task, we gonna write two tasks: one to deploy for production (or whatever environment you have) and the other to deploy for testing (Preview). To run the correct task, we set a task condition (docs).

- task: CmdLine@2
  displayName: 'Deploy NON PREVIEW'
  condition: eq(variables.DEPLOY_PREVIEW, false)
  inputs:
    script: firebase deploy --token=$(FIREBASE_TOKEN)

- task: CmdLine@2
  displayName: 'Deploy PREVIEW'
  condition: eq(variables.DEPLOY_PREVIEW, true)
  inputs:
    script: firebase hosting:channel:deploy --token=$(FIREBASE_TOKEN) preview-$(System.PullRequest.PullRequestId) --json |\ jq -r '.result."<firebase-proj-name>".url'

The helper function eq() is provided by Azure (docs on Functions). It tests the equality of two parameters. If DEPLOY_PREVIEW is false, we are running a merge, the Pull Request was completed and we want to deploy to the "official" URL. If DEPLOY_PREVIEW is true, it's a Pull Request creation or update, then we want to deploy to a preview URL.

We can define preview-<PR number> as the CHANNEL_ID , so we can (1) update this preview live version easily when this PR receives new commits, and (2) delete this channel easily when the PR is completed. For the PR 11, the channel id would be preview-11 (docs on firebase channels).

On the script of the deploy PREVIEW task, we are using jq to print out the url from the output of the firebase CLI command. When the deployment is done, firebase gives you back the URL to check your app running.

Delete channel

To delete the channel, we need to know the CHANNEL-ID . The way we defined the rule for CHANNEL-ID, we only need to know the PR number. However, when the PR is completed and the pipeline is triggered we do not have this simple data (PR number) on Azure pre-defined variables. So, one option is to get the PR number from the merge commit message ("Merged PR 123: new feature") (ref).

The following bash script will do the work. We split by spaces, get the item in index 2 and then split by the ":" character, leaving only the PR number. Then, we use this PR number to build our CHANNEL-ID on the CLI command.

  - task: bash@3
    displayName: 'Delete PR channel'
    condition: eq(variables.DEPLOY_PREVIEW, false)
    inputs:
      targetType: inline
      script: |
        echo Getting PR number from commit message - $(Build.SourceVersionMessage)
        echo "Expected format: Merged PR <PR number>: <PR name>"
        SPLITTED=( $(Build.SourceVersionMessage) )
        arrIN=(${SPLITTED[2]//:/ })
        PR_NUMBER=${arrIN[0]} 
        echo Got the following PR number - $PR_NUMBER
        firebase hosting:channel:delete -f preview-$PR_NUMBER --token=$(FIREBASE_TOKEN)

Again, we are using a condition to run this task or not. Also, note the targetType: inline necessary to run our bash script.

The -f on firebase CLI command is to avoid a prompt for confirmation.

And we are done.

Let me know what you think.

See you!