Lob's website experience is not optimized for Internet Explorer.
Please choose another browser.

Arrow Up to go to top of page
Hero Image for Lob Deep Dives Blog PostNick Fury Comes to the Console via Github Actions
Engineering
January 27, 2022

Nick Fury Comes to the Console via Github Actions

Share this post

I think that Samuel L. Jackson is the greatest actor of all time. A bold statement, but I’m willing to die on this hill. In tribute, I have made this npm library that appends Sam Jackson quotes to your console messages in your NodeJS application.

In this post, you'll learn how to use GitHub actions to run tests, build your library, and publish it to NPM. We use the Samuel Log Jackson repository to illustrate. As a bonus, you'll learn how GitHub actions can be used to respond to new issues in GitHub with a Sam Jackson image and quote by leveraging the marketplace action Sam Jackson Greeter.

What are GitHub Actions

GitHub Actions is an event-driven CI/CD (continuous integration/continuous delivery) platform. The events – which can be anything from a new commit to the creation of an issue – trigger code that runs tests, make API requests to other services, build your project, close issues, create tickets in JIRA, etc. The possibilities are only constrained by your imagination.

GitHub Actions are outlined in YAML files and stored in the .github/workflows folder of the repo. This folder should be in the root directory. Let’s take a look at an example of a typical workflow file. This is the workflow file that triggers when a pull request is made on Samuel Log Jackson.

name: CI
on: [pull_request]
jobs:
 build:
   name: Build, lint, and test on Node ${{ matrix.node }} and ${{ matrix.os }}

   runs-on: ${{ matrix.os }}
   strategy:
     matrix:
       node: ['10.x', '12.x', '14.x']
       os: [ubuntu-latest]

   steps:
     - name: Checkout repo
       uses: actions/checkout@v2

     - name: Use Node ${{ matrix.node }}
       uses: actions/setup-node@v1
       with:
         node-version: ${{ matrix.node }}

     - name: Install deps and build (with cache)
       uses: bahmutov/npm-install@v1

     - name: Test
       run: yarn test --ci --coverage --maxWorkers=2

     - name: Build
       run: yarn build

The name attribute is optional. This shows up in the Actions panel of the repo whenever the workfile is triggered.

The on attribute specifies which event(s) trigger this file to run. For this file, it says pull_request, which means that ANY pull request from ANY branch will trigger this file to run. The full list of events that trigger workflows can be found here.

The jobs attribute is required and represents a task that runs. For this example, there is only one task called build.

The runs-on attribute is where you state what type of VM the task runs inside. Right now there are three different types available – Ubuntu, MacOS, and Windows.

The strategy.matrix allows you to split the task into different permutations. In this example, we set a variable – node – and put three different versions that we want to test. We also set an os variable that allows us to expand the number of VMs that run this particular task.

The steps attribute is where we start outlining the commands that the VM will run.

The uses attribute points to another GitHub Action that can be found in the GitHub Marketplace. These custom actions have their own documentation and may need input parameters to work. For example, the actions/setup-node@v1 takes an input parameter called node-version. The value is set by the matrix variable that we declared earlier in strategy.matrix. We reference the variable by using the ${{ variable_name }} syntax.

The other attribute you can use is called run, and this where you can specify commands to run. These commands can be system commands, like cp or touch, or custom command line utilities. In this example, the task is calling two custom commands I have listed in the package.json file for the repo.

Releasing a new version on NPM with GitHub Actions

Let’s walk through releasing a new version of your repo on one of the many package managers out there. This is a core use case for GitHub Actions and is a good way to get started using them.

name: Current Release
on:
 release:
   types: [published]
jobs:
 release:
   name: Build and release new version to NPM

   runs-on: ubuntu-latest
   steps:
     - name: Checkout Repo
       uses: actions/checkout@v2
     - name: Use Node
       uses: actions/setup-node@v1
       with:
         node-version: 10.x
     - name: Install deps and build (with cache)
       uses: bahmutov/npm-install@v1
     - name: Build
       run: yarn build
     - name: Publish
       uses: JS-DevTools/npm-publish@v1
       with:
         token: ${{ secrets.SHARIQ_NPM_TOKEN }}

This YAML is pretty straightforward. We are only going to run this on the Ubuntu VM, and only for version NodeJS 10.x. Therefore, we do not need the matrix variables that we declared in the previous YAML file. We make use of two actions provided by GitHub, setup-node and checkout. In the third step, we run yarn build – a custom command that we registered in our package.json file. The last step, we use the npm-publish action provided by JS-DevTools. This has an input parameter called token. The token is the value that NPM gives you as an authorization key. Notice that this has the variable syntax that we encountered earlier.

Now we need to add this token variable to our repo secrets. This is where you store sensitive information related to your workflows. You can store API keys, usernames, passwords, etc. in this location.

Secrets are not shown – either in the UI or in the console. Collaborators on the repo have full access to the secrets. GitHub recommends that you don’t put any structured data as your secret, so no JSON or Python pickles. There are some security risks that you should be aware of when using secrets and GitHub has an excellent article that outlines best practices.

Once in place, you can start doing automatic releases via GitHub. To trigger the release event, use GitHub's UI by going to the tags link on the repo homepage.

Secrets are not shown – either in the UI or in the console. Collaborators on the repo have full access to the secrets. GitHub recommends that you don’t put any structured data as your secret, so no JSON or Python pickles. There are some security risks that you should be aware of when using secrets and GitHub has an excellent article that outlines best practices.

Once in place, you can start doing automatic releases via GitHub. To trigger the release event, use GitHub's UI by going to the tags link on the repo homepage.

Building your own GitHub Action

My love for Sam Jackson runs deep, and when I look through the GitHub Marketplace, there is a noticeable lack of Sam Jackson-themed actions. Let’s fix that by making a GitHub Action that supplies a gif of Sam Jackson and a welcome message for anyone that creates an issue in the repo. We can reference this GitHub Action in our Samuel Log Jackson repo to respond with a special message to anyone who opens an issue.

To start, you will need to create a new repo in GitHub and place an action.yml file at the root. This file tells GitHub everything it needs to know about how to run the action.

name: 'Sam Jackson Greeter'
description: 'An action to greet people who interact with the repo with a gif of Samuel L. Jackson'
inputs:
 message:
   description: 'Message displayed alongside the Samuel L Jackson gif'
   required: true
   default: 'You need some Sam Jackson in your life'
 token:
   description: 'Github secret token'
   required: true
branding:
 icon: mic
 color: blue
author: lob
runs:
 using: 'node12'
 main: 'index.js'

The keys to this file are the input and the runs sections. The input section lets you set input parameters for your action. We saw earlier how these are used  by consumers of your action via the with attribute. Here we have set up two required parameters – the message that the consumer wants to send and the GitHub token for the consumer’s repo. The GitHub token is a value that is automatically inserted into the secrets vault (the same vault where we stored the token value from NPM). A consumer could reference it by typing ${{secrets.GITHUB_TOKEN}}. We are going to use this token as a PAT (Personal Access Token) to allow us to make other API calls to GitHub later on. The runs section tells GitHub what sort of environment you will be using for this action and what the entry point into the action will be. Javascript is the easiest language to get started with, but you can use Python, Golang, or C# as well.  

If you choose to use Javascript, I highly recommend that you use the GitHub Action Toolkit. In addition, if you plan to interact with the Github API during the course of your action, then I suggest using the octokit/actions library which is a GitHub API client especially for the GitHub Action environment. Create an index.js file in the root directory and put in the following content.

const github = require("@actions/github");
const core = require("@actions/core");
const { Octokit } = require("@octokit/action");

let gifURLs = [
   'https://c.tenor.com/p29xMArwXB8AAAAd/samuel-l-jackson-silly.gif', // silly face sam
   'https://c.tenor.com/8aKkFuCN7TsAAAAC/samuel-l-jackson-snakes-on-a-plane.gif', // snakes on a plane
   'https://c.tenor.com/PUw8yTi8V5AAAAAC/samuel-l-jackson-shocked.gif', // oh-really-sam?!
   'https://c.tenor.com/hIhNfnidIlkAAAAC/butts-release.gif', // jurassic park
   'https://c.tenor.com/m36sCPqucWMAAAAC/samuel-jackson-pink.gif' // pink wig sam
];

async function runMain() {
   try {
       const randInt = function (min, max) {
           return Math.floor(Math.random() * (max - min + 1)) + min;
       };
       let message = `<img src="${gifURLs[randInt(0, gifURLs.length - 1)]}" width="200px" /> \n`;
       const octokit = new Octokit({ auth: core.getInput('token') });
       let res = await octokit.rest.issues.createComment({
           issue_number: github.context.payload.issue.number,
           owner: github.context.payload.repository.owner.login,
           repo: github.context.payload.repository.name,
           body: message + core.getInput('message')
       });
       console.log(res);

   } catch (err) {
       console.log("There was an error executing the action: " + err);
       core.setFailed(err.message);
   }
}

runMain();

We set up runMain as an async function because we use the await keyword to make calls to the GitHub API. We get the input parameters for our action by calling core.getInput with the variable name we used in the action.yml file. Authenticate by passing in the token set by the consumer to the Octokit client. Now we can make calls to GitHub API on behalf of the repo. Access the event payload data by inspecting the value of the github.context.payload variable. This gives us the same event data that comes through the GitHub API. Now parse out the parameters needed to make a comment on the issue.

Tips for developing your Action

I’ll be honest, the workflow for developing your own action is a bit much. You need another repo to act as the consumer so that you can test your action out. There is a tool called act that allows you to simulate actions on your consumer repo, but there is no substitute for manually triggering the events on your consumer repo to make sure everything works.

Another caveat is that the workfile YAML in the consuming repo expects to target a specific version or commit hash. You may notice all of the actions referenced have an “@v1” or “@latest” target. When developing your own action, make sure to update your tags after each commit.

If you plan on making calls to the Github API in your action, then make sure you are using the Octokit flavor that works in the actions environment. This caused a lot of headache for me as I was using the regular Octokit.js library and could not get any calls made. ‍

Consuming our custom GitHub Action

Now that we have built the custom GitHub Action (Sam Jackson Greeter), let’s set up another repo to consume the action and respond to issues with Sam Jackson gifs. We create a new file at .github/workflow/answer_issue.yml in the target repo and add the following:

name: Always Sam
on:
 issues:
   types: [opened]
jobs:
 respond_with_sam:
   runs-on: ubuntu-latest
   steps:
     - uses: lob/action_sam_jackson@0.0.2
       with:
         message: "Welcome to Samuel Log Jackson. I hear you are having a problem? Don't worry, we are assembling a special team to tackle your issue. We're calling it the Avenger Initiative."
         token: ${{secrets.GITHUB_TOKEN}}

In this file we hit on several concepts that we talked about before: we are referencing our newly created action lob/action_sam_jackson and passing in the two required input parameters.

Since we are running on a single VM, we dropped the matrix array we used earlier in the runs-on attribute.

Nick Fury Comes to the Console via Github Actions image 2

And now, our Samuel L. Jackson-theme repo has automated responses to issues using his visage. Leave an issue and see the action in the wild.

Continue Reading