GitHub Actions Day 29: Creating an Action

December 29, 2019

This is day 29 of my GitHub Actions Advent Calendar. If you want to see the whole list of tips as they're published, see the index.

Advent calendars usually run through Christmas, but I'm going to keep posting about GitHub Actions through the end of December. Consider it bonus content!

So far this month we've looked at a lot of workflows for GitHub Actions. Almost all of them will run commands as part of the workflow. Some of them will also make use of actions to help set up the environment or expand the workflow.

An action is a piece of code -- either a runnably docker container or a JavaScript application -- that you can re-use within several workflows.

There are several actions that GitHub provides to use within your workflows. For example, the checkout action will clone your repository so that you can build and test it. The setup-node action will set up the requested version of Node.js in the PATH. And the clever github-script action lets you run JavaScript within the GitHub context.

But it's straightforward to create your own action that you can use in your workflows or share with others. GitHub provides a toolkit to help you get starting building a workflow in JavaScript or TypeScript.

I'm going to take the TypeScript sample as a start and create an action that listens for comments on an issue. And when it sees a particular comment -- /octocat -- then it will reply by posting a random Octocat from the Octodex.

First, we'll get some data out of the github.context, which contains information out of the workflow execution. From there, we can get the repository that we're running in, the issue that was commented on, and the body of the comment that was posted.

const context = (github as any).context
const repository = context.payload.repository
const issue = context.payload.issue
const comment = context.payload.comment.body

We also want to establish an outbound connection to GitHub -- we can create an instance of OctoKit (the GitHub API), connecting back to our repository with the GITHUB_TOKEN. (Users will need to pass their GITHUB_TOKEN when they invoke the action.)

const token = process.env.GITHUB_TOKEN || ''
const octokit = new github.GitHub(token)

Now that we've got all this plumbing in place, we can add our business logic. We want to do some work when we see a comment that is /octocat (or /mona).

if (command == '/octocat' || command == '/mona') {
  // display an octocat
}

When we see that comment, we'll grab a random octocat image from a list of octocats.

const rand = Math.floor(Math.random() * octocats.length)
const octocat = octocats[rand]

Now that we have the Octocat that we want to post, we can use the Octokit API to post it, by calling issues.createComment(...). We'll post Markdown with an inline image to the Octocat that we chose at random.

octokit.issues.createComment({
  owner: repository.owner.login,
  repo: repository.name,
  issue_number: issue.number,
  body: '![' + octocat + '](' + octodex_url + '/' + octocat + ')'
})

I've put this all together and packaged it up as mona-action. When you add it to your workflow:

on: issue_comment

jobs:
  octocat:
    runs-on: ubuntu-latest
    steps:
    - uses: ethomson/mona-action@master
      env:
        GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"

And you add a comment on an issue that is /octocat, you'll get a nice image as a result:

Octocat

Certainly this is a simplistic example, but this gives you an idea of how you can take advantage of the GitHub Actions API to retrieve data about the action execution within your workflow, and how to use the Octokit API to communicate back with GitHub. From there you can create an action that suits your workflow.

Want to see this action in full? It's at https://github.com/ethomson/mona-action