Trigger Github Workflow from Slack using Firebase Functions
Tags: firebase, tutorial, productivity
Published: 2020-01-19 17:01:15

---
title: Trigger Github Workflow from Slack using Firebase Functions
published: true
tags: ['firebase', 'tutorial', 'productivity']
canonical_url: https://whatdafox.com/trigger-github-workflow-from-slack-using-firebase-functions/
cover_image: https://whatdafox.com/assets/static/hero.e116048.0aa210b71ed1a4230852219f7c9924f9.jpg
---

Let's have a look at how to trigger a Github Workflow from Slack, using Firebase Functions.

## Pre-requisites

- A Firebase project
- A Github Repository with Github Actions enabled

## Create a Slack app

First, let's create a Slack application. This application will be used to create the slash command available 
in your workspace.

[Create a new Slack application](https://api.slack.com/start/overview#creating) and create a slash command:

![Slash Commands](https://whatdafox.com/assets/static/enable-slash-commands.2665e34.292c9066a8d3ef629c5dd279e9b624b9.png)

We choose `whatdafox` in this example, but you can name your command however you like.

Don't worry about the **Request URL** for now; we'll come back later to input the right URL.

Slack should suggest to install the application in your Slack workspace, go ahead and do that.

Navigate to your Slack app basic information page, set aside the `Signing Secret`, we will use it later to ensure the 
requests to our Cloud Functions are coming from our Slack application.

## Create a Github Personal Token

Since our slash command will trigger a Github Workflow, we need to generate a Github personal token to make a request to the Github API.

Navigate to [Github Personal Token](https://github.com/settings/tokens) and generate a token with the `repo` scope: 

![Github Personal Token](https://whatdafox.com/assets/static/github-personal-token.30a8f75.46da8b785b9b2d0b93741814c3de024f.png)

## Create your Github Workflow

To trigger a workflow from Slack, of course, we need a workflow! Let's create a basic workflow to deploy our project on Firebase, for example.

> I am going to assume we have a regular JavaScript application to deploy, please adapt this workflow to your use-case.

Create a `.github/workflows` directory in your project, and create a `deploy.yml` file with the following: 

```yaml
name: Deploy

on:
  repository_dispatch:

jobs:
  deploy:
    name: Build and Deploy
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v1

      - name: Setup Node
        uses: actions/setup-node@v1
        with:
          node-version: '12.x'

      - name: Install Firebase
        run: |
          npm i -g firebase-tools

      - name: Build
        run: |
          npm ci
          npm run build

      - name: Deploy
        run: |
          firebase deploy --only hosting --token ${{ secrets.FIREBASE_TOKEN }}
```

The [`repository_dispatch`](https://developer.github.com/v3/repos/#create-a-repository-dispatch-event) event will be triggered by our Cloud Function later.

Push your code to GitHub. We will now setup the trigger.

> If you'd like to test your workflow beforehand, use another trigger instead, like `on: push`, for example.

## Add your Firebase Token to your Github repository

For this workflow to work, we need to generate a Firebase token and set it as a secret on our GitHub repository. 

To generate a Firebase CI token, run the command: 

```bash
$ firebase login:ci
```

Go to your Github repository, navigate to the `Settings > Secrets` page, create a new `FIREBASE_TOKEN` secret and paste 
the token provided by the Firebase CLI.

![Firebase Token](https://whatdafox.com/assets/static/firebase-token.561053d.905abf14c34846f49b125f3bff7fc1b1.png)

## Configure Firebase Functions

### Setup your project

In this example, we'll use TypeScript to write our function to trigger our Github Workflow.

Run the following command to set up Cloud Functions in your Firebase project and follow the instructions: 

```bash
$ firebase init functions
```

This should create a `functions` directory in your project, with some boilerplate files.

### Setup the environment

We know our Cloud Functions will require multiple variables and secrets to work:

- your GitHub username
- your repository name
- your GitHub personal token
- your Slack app signing secret

We can use Firebase Functions config to store this information: 

```bash
$ firebase functions:config:set github.username=your-username github.repository=your-repository github.access_token=your-github-personal-token slack.signing_secret=your-slack-signing-secret
```

Check that the information was saved by running:

```bash
$ firebase functions:config:get
```

You should see the following response: 

```json
{
  "github": {
    "username": "your-username",
    "access_token": "your-github-personal-token",
    "repository": "your-repository"
  },
  "slack": {
    "signing_secret": "your-slack-signing-secret"
  }
}
```

### Writing the function

We will use express in our Firebase Function to respond to requests coming from our slash command. 

First, we need to install the required dependencies. In the `functions` directory, install the necessary dependencies:

```bash
$ npm i --save express cors body-parser axios tsscmp
```

> If you are using TypeScript, you can also install the following:
> `npm i --save @types/express @type/cors @types/tsscmp`

Then create a `functions/utilities/slack.ts` file. We will use this file to store functions related to our slash command: `verify` and `deploy`:

```typescript
import axios from "axios";
import * as crypto from 'crypto';
import timeSafeCompare from 'tsscmp';
import * as functions from 'firebase-functions';

const githubToken = functions.config().github.access_token;
const githubUsername = functions.config().github.username;
const githubRepo = functions.config().github.repository;
const slackSigningSecret = functions.config().slack.signing_secret;

export const verify = (request: any) => {
    //    
};

export const deploy = async (request: any, response: any) => {
    //
};
```

In the verify function, we will parse the request and verify its signature to ensure the request is legit and really coming from our slash command:

```typescript
export const verify = (request: any) => {
    // Grab the signature and timestamp from the headers
    const requestSignature = request.headers['x-slack-signature'] as string;
    const requestTimestamp = request.headers['x-slack-request-timestamp'];

    const body = request.rawBody;
    const data = body.toString();

    // Create the HMAC
    const hmac = crypto.createHmac('sha256', slackSigningSecret);

    // Update it with the Slack Request
    const [version, hash] = requestSignature.split('=');
    const base = `${version}:${requestTimestamp}:${data}`;
    hmac.update(base);

    // Returns true if it matches
    return timeSafeCompare(hash, hmac.digest('hex'));
};
```

In the deploy function, we'll use Axios to make a call to the GitHub API to trigger the [`repository_dispatch`](https://developer.github.com/v3/repos/#create-a-repository-dispatch-event) event:

```typescript
export const deploy = async (request: any, response: any) => {
    const http = axios.create({
        baseURL: 'https://api.github.com',
        auth: {
            username: githubUsername,
            password: githubToken,
        },
        headers: {
            // Required https://developer.github.com/v3/repos/#create-a-repository-dispatch-event
            Accept: 'application/vnd.github.everest-preview+json',
        },
    });

    return http.post(`/repos/${githubUsername}/${githubRepo}/dispatches`, { event_type: 'deployment' })
               .then(() => {
                    return response.send({
                        response_type: 'ephemeral',
                        text: 'Deployment started!'
                    });
               })
               .catch((error) => {
                    return response.send({
                        response_type: 'ephemeral',
                        text: 'Something went wrong :/'
                    });
               });
};
```

Now, we will create the main API. Create a `functions/slack-command.ts` file:

```typescript
import express from 'express';
import cors from 'cors';
import bodyParser from 'body-parser';
import { verify, deploy } from "./utilities/slack";

const urlencodedParser = bodyParser.urlencoded({ extended: false });

const app = express();

// Automatically allow cross-origin requests
app.use(cors({ origin: true }));

app.post('/', urlencodedParser, async (request: any, response: any) => {
    const isLegitRequest = verify(request);

    if(!isLegitRequest) {
        return response.send({
            response_type: 'ephemeral',
            text: 'Nope!'
        });
    }

    const { text } = request.body;

    switch (text) {
        case 'deploy':
            return deploy(request, response);
        default:
            return response.send({
                response_type: 'ephemeral',
                text: 'Nothing happened ¯\\_(ツ)_/¯'
            });
    }
});

export default app;
```

In this file, we create a new Express app and create a `POST` endpoint. When a request is made, it will verify the request legitimacy, parse the command (in our case *deploy*) and trigger the `deploy()` function.

If the request fails the verification, the function will return early, which will limit our costs on Firebase.

> Note that we are using a switch case here, this is because this way it is trivial to add more commands to your slash command in the future 🙂

Open `functions/index.ts` and import our `functions/slack-command.ts` file:

```typescript
import * as functions from 'firebase-functions';

import slackCommand from './slack-command';

export const onSlackCommand = functions.https.onRequest(slackCommand);
```

### Deploy and test

Deploy the function to Firebase by running:

```bash
$ firebase deploy --only functions
```

Now go in your Slack workspace and type:

```
/whatdafox deploy
```

And voilà! Your GitHub Workflow can now be triggered from Slack 🚀