How to deploy Firebase Preview Channels on Travis CI
Tags: firebase, travisci, previewchannels, previewchannel
Published: 2021-07-22 14:53:00

Nikita Glukhi, Software Engineer at Valor Software, kindly provided this story and the solution.
Firebase has released the long-awaited Preview channel functionality, which allows for the testing of updates. All Firebase (not Firestore!) users can benefit from this hosting feature. And you may be asking what about Travis CI or other hosting users? Can they use preview channels as well?
At the time of this article’s publication, GitHub Actions does not yet natively provide automated procedures for Travis CI from GitHub. Below is a solution to use preview channels by Firebase with your existing [Travis CI] (https://travis-ci.org/) deployment environment.

### Check updates with no risk for users’ experience
Let’s face the truth, to check the behavior of your polished and tested updates on production, you had to actually push them to production. Then, in case something fails, — get back to a previous version. Application users might have noticed inappropriate system behavior, which is a pity. To get rid of this kind of working situation, we can now use preview channels. Apply this new Firebase feature to automatically deploy updates and see how they behave, with no risk for real users’ experience. Also, you can comfortably leave comments for your updates, and amend them with your team. The best thing is that this temporary storage will remove itself after a preview channel link expires. No storage place taken!

### Prerequisites:
- You need to know Travis CI and have experience with the config file.
- You need to know how to write Bash scripts and know the right stage to run the Bash script for preview channels.

### Steps to reproduce:
1. Create a new local repository.
2. Create a new GitHub repository.
3. Run the ‘git remote add’ command to connect your local repository to the GitHub repository.
4. [Set up Firebase project] (https://firebase.google.com/docs/web/setup).
5. [Set up Travis CI] (https://docs.travis-ci.com/user/tutorial/#to-get-started-with-travis-ci-using-github) for GitHub repository.

Set up configuration variables — add [environment variables in Travis CI] (https://docs.travis-ci.com/user/environment-variables/). The variables that you need to set up are the two access tokens to work with Firebase and GitHub:
• To get FIREBASE_TOKEN, use the command: firebase login:ci
If the command doesn’t work, that’s probably because you don’t have firebase-tools
installed. For installation, run the following command: npm i -g firebase-tools, or curl -sL [https://firebase.tools] (https://valorsoftware.medium.com/how-to-deploy-firebase-preview-channels-on-travis-ci-f97ae6bd8d5b#:~:text=https%3A//firebase.tools) | bash
Then — run a firebase login command, then — firebase login:ci. The latter command gives you a Firebase access token that you want to use as a value for this step (To get FIREBASE_TOKEN…).
• Get GITHUB_TOKEN using this link: [Creating a personal access token] (https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/creating-a-personal-access-token)
Then, apply this value for the GITHUB_TOKEN variable.
1. In your local repository, run the ‘firebase init hosting’ command, choose your Firebase project from the list of already existed projects, and [configure firebase.json file] (https://firebase.google.com/docs/hosting/full-config).
2. Create .travis.yml file in your local repository.
3. Create a Bash script — [https://devdocs.io/bash/] (https://devdocs.io/bash/).
4. Add job to the .travis.yml file that should call the script that we’ve created as step 8.
The job you need to insert into the .travis.yml file:
```
stages:
 - name: "Deploy to Firebase preview channel"
   if: branch = master AND type = pull_request
jobs:
 include:
 - stage: "Deploy to Firebase preview channel"
   skip_cleanup: true
   provider: firebase
   project: fir-project-dc47e
   before_script:
     - sudo apt-get install jq
     - npm install firebase-tools -g
     - npm run build:prod
   script: bash deploy-to-firebase-preview-channels.sh
```
Here fir-project-dc47e — an ID of your Firebase project
1. Push changes to GitHub and create a pull request.
Now, back to solving our Travis CI issue and enabling its usage with the Preview channel Firebase functionality. Find the solution I came up with in the repository: [Travis CI and Firebase preview channels solution] (https://github.com/NikitaGlukhi/travis-ci-and-firebase-preview-channels).
Or a brief version on gist: [.travis.yml: preview channels deployment] (https://gist.github.com/NikitaGlukhi/f094a6a8e6812d104d779e37d6560705).
I’ll separate the script into pieces and describe every step below. To deploy successfully, please, follow my guidelines step by step.
I start with a deployment to a preview channel:
```
#!/usr/bin/env bash
DEPLOY_TO_PREVIEW_CHANNEL_RESULT=$(firebase hosting:channel:deploy pr-$TRAVIS_PULL_REQUEST --expires 30d --token $FIREBASE_TOKEN --json)
```
After running the deploy command, I get a response with an object containing data, and I have the following as a result:
```
{
 "status": "success",
 "result": {
   "fir-project-dc47e": {
     "site": "fir-project-dc47e",
     "url": "https://fir-project-dc47e--pr-1-t36vykr3.web.app",
     "expireTime": "2021-01-08T09:27:24.847798020Z"
   }
 }
}
```
Here fir-project-dc47e — an ID of your Firebase project
I add this object to the DEPLOY_TO_PREVIEW_CHANNEL_RESULT variable. This object has a .result parameter containing all the data needed for future operations.
Next, I select data from the .result parameter and add it to a separate variable that corresponds to the parameter name. This variable will contain an object with a key of ngx-bootstrap-demo.
```
RESULT=`echo ${DEPLOY_TO_PREVIEW_CHANNEL_RESULT} | jq -r '.result'`
```
The following goes to RESULT_DATA:
```
{
     "site": "fir-project-dc47e",
     "url": "https://fir-project-dc47e--pr-1-t36vykr3.web.app",
     "expireTime": "2021-01-08T09:27:24.847798020Z"
   }
```
Now, I extract a website name from the RESULT_DATA variable. The result will go to the SITE variable:
```
SITE=`echo ${RESULT_DATA} | jq -r '."site"'`
```
I extract a preview channel URL from RESULT_DATA variable, and the result goes to the URL variable:
```
URL=`echo ${RESULT_DATA} | jq -r '."url"'`
```
Then — extracting data with the expiration time from the RESULT_DATA variable. And, I write it down to the EXPIRE_TIME_UTC variable. UTC format is a default one, so I bring it to the needed format which is GMT, in my case.
```
EXPIRE_TIME_UTC=`echo ${RESULT_DATA} | jq -r .expireTime`
EXPIRE_TIME=$(TZ='GMT' date -d $EXPIRE_TIME_UTC +%c)
```
The NEW_COMMENT variable creates a text with a project name, link to a preview channel, and its life duration. I’ll add this text of the comment to a pull request later (TRAVIS_PULL_REQUEST/comments).
Then, I extract all the comments from the pull request I want to work on, using the request to GitHub API. The result goes to the COMMENTS variable. The Objects array will have a description for each comment.
```
COMMENTS=$(curl -H "Authorization: token $GITHUB_TOKEN" -X GET "https://api.github.com/repos/$TRAVIS_REPO_SLUG/issues/$TRAVIS_PULL_REQUEST/comments")
```
I declare variables for test cycles. Using the SUBSTRING variable, I search for a comment that might have been added before to replace it with the latest one.
COMMENT_ID equals -1 by default. In the future, I’ll assign a comment ID that I find to it. In case of no overlaps appeared, the value stays as default.
```
SUBSTRING="Project: fir-project-dc47e"
COMMENT_ID=-1
```
In this cycle, I sort out the COMMENTS array, and extract the body of each comment — its text, and search for a substring in this body. If an overlap is detected, I take the comment ID and assign it to the COMMENT_ID variable. If no overlaps are detected, then nothing is assigned, the loop just runs as before.
```
for row in $(echo "${COMMENTS}" | jq -r '.[] | @base64'); do
echo ${row}
  _jq() {
     echo ${row} | base64 --decode | jq -r ${1}
  }
  BODY=$(_jq '.body')  if [[ ${BODY} == *"$SUBSTRING"* ]]; then
    COMMENT_ID=$(_jq '.id')
  fi
done
```
Finally, I run a COMMENT_ID test, if it equals 0 or is more than 0, it means a comment like this exists, and I need to refresh it. Then, I refer to GitHub API (GITHUB_TOKEN). If there’s no comment — the command creates a new comment in a pull request (GitHub API, as well).
```
if [[ ${COMMENT_ID} -ge 0 ]];
 then
   curl -H "Authorization: token $GITHUB_TOKEN" -X PATCH -d "{\"body\": \"$NEW_COMMENT\"}" "https://api.github.com/repos/${TRAVIS_REPO_SLUG}/issues/comments/${COMMENT_ID}"
 else
   curl -H "Authorization: token $GITHUB_TOKEN" -X POST -d "{\"body\": \"$NEW_COMMENT\"}" "https://api.github.com/repos/${TRAVIS_REPO_SLUG}/issues/${TRAVIS_PULL_REQUEST}/comments"
fi
```
As a result, I get the link with the comment to a preview channel. And, the comment we get from the previous operation comes from the person which token we use.

![Preview Channel Link](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/93tv18r8jrm2ghjlbfco.png)

### Any questions?
Feel free to contact me if you have any questions or troubles with deploying the script: nikita.glukhi@valor-software.com.

### Useful links:
- [Issue Comments for a repository] (https://developer.github.com/v3/issues/comments/#list-issue-comments-for-a-repository) — extract all the comments for a repository (as well as for a preview channel, but there’s no example in the doc)
- [Create an issue comment] (https://docs.github.com/en/free-pro-team@latest/rest/reference/issues#create-an-issue-comment) 
- [Update an issue comment] (https://docs.github.com/en/free-pro-team@latest/rest/reference/issues#update-an-issue-comment) — refresh an existing comment