4 beginner-friendly Github Actions snippets
Published: 2021-06-09 14:34:15

GitHub Actions are extremely useful for those of you interested in setting up a CICD pipeline. Workflows are not only easy to implement but even easier to hook into your existing projects. These are a list of snippets that I found useful while trying to understand how to add these to my repos and how to develop my own.

I validated these snippets by setting up a separate repo to test opening PRs. I also recommend using nektos/act as a CLI to validate your workflows if you want a more seamless dev experience.

{% github nektos/act %}

# List of Snippets
  * [Post a comment automatically when a PR is opened](#snippet-1)
  * [Run a PHP snippet](#snippet-2)
  * [Open a Jira ticket when Dependabot opens a PR (for Hosted Jira instances)](#snippet-3)
  * [Update empty PR body with a picture of Barney](#snippet-4)

<a name="snippet-1"></a><br>

# Post a comment automatically when a PR is opened

This snippet is more useful for understanding the structure of a GitHub Actions workflow than for actual production use. Please refer to the documentation for [creating a pull request template for your repository](https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository) for a more robust alternative without using up your Actions quota.


**Add the below file to your repo as `.github/workflows/comment.yml`**, or whatever other workflow name you find appropriate.
```yml
name: Comment

on:
  pull_request:
    types: [opened]

jobs:
  version_comment:
    name: Comment on a new PR with reminder
    runs-on: ubuntu-latest
    steps:
    - name: Add PR Comment
      id: add_pr_comment
      uses: octokit/request-action@v2.x
      with:
        route: POST /repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments
        body: "\"Change me to the comment you want to automatically put 🐶\""
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```

<a name="snippet-2"></a><br>

# Run a PHP snippet

For those of you (like myself) who work on teams developing primarily in PHP, it may be useful to also write your adhoc scripts in PHP for less context switching. TEST_ENV is placed here as an example environmental variable should anything about the PR's metadata need to be passed into the script.

**Add the below file to your repo as `.github/workflows/php.yml`**, or whatever other workflow name you find appropriate.
```yml
name: Testing php scripts

on:
  pull_request:
    types: [opened]

jobs:
  version_comment:
    name: Comment on a new PR with reminder
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: Setup PHP
      uses: shivammathur/setup-php@v2
      with:
        php-version: '7.3'
    - name: Check PHP version
      run: php --version
    - name: Run test.php
      env:
        TEST_ENV: "Hello from ENV"
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      run: php scripts/test.php
```

**Add the below file to your repo as `scripts/test.php`.**
```php
<?php

echo "Hello! This is scripts/test.php\n";

$github_token = getenv('GITHUB_TOKEN');
$test_env = getenv('TEST_ENV');

echo "$github_token = " . $github_token . "\n";
echo "$test_env = " . $test_env . "\n";
```

<a name="snippet-3"></a><br>

# Open a Jira ticket when Dependabot opens a PR (for Hosted Jira instances)

The work for these files is that GitHub actions runs a Python script, and this script is the one responsible for firing off the Jira ticket's creation.

This script will work for Jira instances that are both Cloud-based and Hosted. However, for those of you using Cloud-based instances, I encourage you to look into using the [Jira Create issue GitHub Action](https://github.com/marketplace/actions/jira-create-issue) already provided by Atlassian. This will ensure you are using the latest and greatest. This feature is not available to those with Hosted instances.

**Before starting**, you will need to have a Jira account with a username and password that can be used on behalf of API calls. Naviate to https://github.com/YOUR_USERNAME/YOUR_REPO/settings/secrets/actions and select "New repository secret". Save your Jira account's username as `JIRA_USER` and your Jira account's password as `JIRA_PASS`.

**Add the below file to your repo as `.github/workflows/dependabot_to_jira.yml`**, or whatever other workflow name you find appropriate.
```yml
name: Dependabot Jira Sync

on:
  pull_request:
    types: [opened]

jobs:
  run_jira_ticket:
    name: Create Jira Ticket for Dependabot PR
    if: contains(github.event.pull_request.labels.*.name, 'Dependencies')
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: Set up Python
      uses: actions/setup-python@v1
      with:
        python-version: '3.x'
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt
    - name: Create Jira Ticket and Update PR Title
      env:
        GITHUB_JSON: ${{ toJson(github) }}
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        JIRA_USER: ${{ secrets.JIRA_USER }}
        JIRA_PASS: ${{ secrets.JIRA_PASS }}
      run: python3 scripts/jira_ticket.py
```

**Add the below file to your repo as `scripts/jira_ticket.py`.**
Make sure that you change jiraUrl to the url of your Jira hosted instance.
```py
import os
import json
import requests

# Get ENV variables.
def getENV():
  githubJson = os.environ['GITHUB_JSON']
  githubToken = os.environ['GITHUB_TOKEN']
  github = json.loads(githubJson)
  jiraUser = os.environ['JIRA_USER']
  jiraPass = os.environ['JIRA_PASS']
  return github, githubToken, jiraUser, jiraPass

def createJiraTicket(jiraUser, jiraPass, githubPRTitle, githubPRURL):
  jiraUrl = f"https://backlog.YOURINSTANCE.com/rest/api/2/issue"
  jiraHeaders = {"Content-Type": "application/json"}
  jiraBody = {
    "fields": {
      "project": {"key": "EEE"},
      "summary": f"{githubPRTitle}",
      "description": f"{githubPRURL} | {jiraUser}",
      "issuetype": {"name": "Ticket"}
    }
  }
  response = requests.post(jiraUrl, auth=(jiraUser, jiraPass), headers=jiraHeaders, json=jiraBody)
  print(response.status_code)
  if response.status_code == 201:
    jiraResponse = json.loads(response.text)
    return jiraResponse['key']

# Make API call to GitHub to change PR title.
def updateGithubPRTitle(githubToken, jiraKey, githubPRTitle, githubRepo, githubPRNum):
  githubHeaders = {"Authorization": f"token {githubToken}"}
  githubBody = {"title": f"{jiraKey} | {githubPRTitle}"}
  githubUrl = f"https://api.github.com/repos/{githubRepo}/pulls/{githubPRNum}"
  response = requests.patch(githubUrl, json=githubBody, headers=githubHeaders)
  print(response.status_code)

if __name__ == "__main__":
  github, githubToken, jiraUser, jiraPass = getENV()
  
  githubRepo = github['repository']
  githubPRURL = github['event']['pull_request']['_links']['html']['href']
  githubPRTitle = github['event']['pull_request']['title']
  githubPRNum = github['event']['pull_request']['number']

  jiraKey = createJiraTicket(jiraUser, jiraPass, githubPRTitle, githubPRURL)
  print(jiraKey)
  updateGithubPRTitle(githubToken, jiraKey, githubPRTitle, githubRepo, githubPRNum)
```

<a name="snippet-4"></a><br>

# Update empty PR body with a picture of Barney

One of my coworkers used to do this whenever a PR was opened without a body for a significant amount of time. This is a fun GitHub action the team implemented together to learn more about the GitHub action development process.

This snippet is different from the others in that we constructed our own connector and stored it in the repo with the workflow's file. Alternatively, if you wish for the work to be more widely available, you would keep the repo dedicated to this singular action and publish it to the [Marketplace](https://github.com/marketplace?type=actions).

**Add the below file to your repo as `.github/workflows/barney.yml`**, or whatever other workflow name you find appropriate.
```yml
name: Body by Barney

on:
  pull_request:
    types: [opened]

jobs:
  barney_job:
    runs-on: ubuntu-latest
    name: Barney is here
    env:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    steps:
      # To use this repository's private action, you must check out the repository
      - name: Checkout
        uses: actions/checkout@v2
      - name: I love you
        uses: ./ # Uses an action in the root directory
        id: barney
```

`barney.yml` points to the root of the repository for where the action is located, specifically in this case an `action.yml` file. **Add the below file to your repo as `action.yml`.**
```yml
name: 'Barney'
description: 'Barney'
runs:
  using: 'docker'
  image: 'Dockerfile'
branding:
  icon: heart
  color: red
```

`action.yml` then points to a Dockerfile, also located to the root of your repo. **Add the below file to your repo as `Dockerfile`.**
```py
FROM node:slim

# A bunch of `LABEL` fields for GitHub to index
LABEL "com.github.actions.name"="Body by Barney"
LABEL "com.github.actions.description"="Auto-populate empty PR descriptions."
LABEL "com.github.actions.icon"="edit"
LABEL "com.github.actions.color"="purple"
LABEL "repository"="http://github.com/YOUR_USERNAME/YOUR_REPO"
LABEL "homepage"="http://github.com/YOUR_USERNAME/YOUR_REPO"
LABEL "maintainer"="YOUR NAME (https://github.com/YOUR_USERNAME)"

# install
COPY package*.json ./
RUN npm ci --only=production

# start
COPY . .
ENTRYPOINT ["node", "/index.js"]
```

`Dockerfile` then points to `/index.js` for where the remainder of this action fires off, another file in the root. **Add the below file to your repo as `index.js`.** The action here will comment in logs whether Barney is applied or needed on newly opened PRs.

```js
const { Toolkit } = require('actions-toolkit')
const getPR = require('./lib/get-pr')
const updatePR = require('./lib/update-pr')

Toolkit.run(async tools => {
  const pr_result = await getPR(tools)
  tools.log.debug("PR_BODY=" + pr_result.data.body);
  if (!pr_result.data.body) {
    await updatePR(tools);
    tools.log.success('Barney has been applied.')
  } else {
    tools.log.success('No Barney needed.')
  }
}, {
  event: ['pull_request.opened'],
  secrets: ['GITHUB_TOKEN']
})
```

`index.js` now looks to a folder `lib/` where the rest of the files are located. Instead of storing the full implementation in one file, we still make use of separating the components of this action into parts - in the event the code needs to be revisited and refactored.

`lib/` contains two files: `get-pr.js` for getting information about the PR and `update-pr.js` for sending an API call with the image of Barney in the description.

**Add the below file to your repo as `get-pr.js`.**
```js
/**
 * Return the PR.
 * @param {import('actions-toolkit').Toolkit} tools
 */
module.exports = async function getPR (tools) {
  return tools.github.pulls.get({
    ...tools.context.repo,
    pull_number: tools.context.payload.pull_request.number,
  })
}
```

**Add the below file to your repo as `update-pr.js`.**
```js
/**
 * Update the PR body with Barney.
 * @param {import('actions-toolkit').Toolkit} tools
 */
module.exports = async function updatePR (tools) {
  const barney_img = 'https://user-images.githubusercontent.com/1390106/66919899-3a406900-eff0-11e9-83ba-4e6c4c3dbfaf.jpg';
  return tools.github.pulls.update({
    ...tools.context.repo,
    pull_number: tools.context.payload.pull_request.number,
    body: '![image](' + barney_img + ')'
  })
}
```

And there you have it! You can expand `update-pr.js` with whatever content you find appropriate. This snippet will specifically update an empty PR body with the below image:
![Barney the Dinosaur](https://user-images.githubusercontent.com/1390106/66919899-3a406900-eff0-11e9-83ba-4e6c4c3dbfaf.jpg)