DEV Community

Kathryn DiPippo
Kathryn DiPippo

Posted on

4 beginner-friendly Github Actions snippets

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 logo nektos / act

Run your GitHub Actions locally 🚀

act-logo

Overview push Join the chat at https://gitter.im/nektos/act Go Report Card awesome-runners

"Think globally, act locally"

Run your GitHub Actions locally! Why would you want to do this? Two reasons:

  • Fast Feedback - Rather than having to commit/push every time you want to test out the changes you are making to your .github/workflows/ files (or for any changes to embedded GitHub actions), you can use act to run the actions locally. The environment variables and filesystem are all configured to match what GitHub provides.
  • Local Task Runner - I love make. However, I also hate repeating myself. With act, you can use the GitHub Actions defined in your .github/workflows/ to replace your Makefile!

How Does It Work?

When you run act it reads in your GitHub Actions from .github/workflows/ and determines the set of actions that need to be run. It uses the Docker API to either pull or build the necessary images, as defined in your workflow…

List of Snippets


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 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.



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 }}


Enter fullscreen mode Exit fullscreen mode


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.



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


Enter fullscreen mode Exit fullscreen mode

Add the below file to your repo as scripts/test.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";


Enter fullscreen mode Exit fullscreen mode


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 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.



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


Enter fullscreen mode Exit fullscreen mode

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.



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)


Enter fullscreen mode Exit fullscreen mode


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.

Add the below file to your repo as .github/workflows/barney.yml, or whatever other workflow name you find appropriate.



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


Enter fullscreen mode Exit fullscreen mode

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.



name: 'Barney'
description: 'Barney'
runs:
  using: 'docker'
  image: 'Dockerfile'
branding:
  icon: heart
  color: red


Enter fullscreen mode Exit fullscreen mode

action.yml then points to a Dockerfile, also located to the root of your repo. Add the below file to your repo as Dockerfile.



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"]


Enter fullscreen mode Exit fullscreen mode

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.



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']
})


Enter fullscreen mode Exit fullscreen mode

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.



/**
 * 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,
  })
}


Enter fullscreen mode Exit fullscreen mode

Add the below file to your repo as update-pr.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 + ')'
  })
}


Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)