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.
"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 useact
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 yourMakefile
!
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
- Run a PHP snippet
- Open a Jira ticket when Dependabot opens a PR (for Hosted Jira instances)
- Update empty PR body with a picture of Barney
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 }}
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
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";
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
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)
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
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
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"]
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']
})
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,
})
}
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 + ')'
})
}
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:
Top comments (0)