IssueOps Series: Build Reusable Automation with Custom GitHub Actions
Custom Github Actions a building block to IssueOps using Github Workflows
Overview
Writing a custom GitHub Action allows you to automate repetitive tasks and integrate them directly into your GitHub workflow. You can create a reusable unit of automation that can be shared across multiple repositories. GitHub Actions allows users to create different types of custom actions, as mentioned below.
Types of Custom Actions
There are three primary types of custom GitHub Actions:
JavaScript Actions: These actions are written in JavaScript or another language that compiles to JavaScript (like TypeScript). They run directly on the runner machine.
Docker Container Actions: These actions package the environment and code into a Docker container. This is useful for actions that require a specific operating system, dependencies, or toolchain that isn't readily available on the standard GitHub-hosted runners. It ensures a consistent and isolated execution environment.
Composite Actions: This type allows you to combine multiple workflow steps, including
runscripts and other actions, into a single, reusable action. It's a great way to simplify complex or frequently used sequences of steps in your workflows.
Requirements - Custom GitHub Action
To build a custom action, the required file is the action.yml. There are certain important properties/fields for the action.yml regardless of what type of custom action the user wants to create.
Here's a breakdown of the key fields.
name: Name for the custom action.
description: Short description, describing the purpose and intent of the custom action.
inputs: Define the data that the action accepts as input variables.
outputs: Defines the data the action would generate as output variables.
runs: This is a crucial section that specifies how the action is executed. The variation depends on the type of action.
For JavaScript actions: Use runs. using: 'nodeX' and runs. main to point to the main JavaScript file.
For Docker container actions, use 'runs'.using: 'docker' and runs.image pointing to the Dockerfile.
For Composite actions: Use runs. using: 'composite' and a list of steps.
Create Custom GitHub Action
Here, we would create a Custom JavaScript GitHub Action. The goal of the action here would be to create a Jira Ticket - Issue Type - Bug on the Jira Board.
This custom action would be in a separate GitHub repository, a single action.yml, as this repo would have a single custom action. One could organise the repository to store source code for multiple GitHub actions, each action maintained under a separate directory. Below is the project structure for the JavaScript custom GitHub Action.
project-root-dir/
├── action.yml
├── index.js
├── jira-issue.js
├── README.md
├── package.json
├── package-lock.json
└── ...Create action.yml
name: IssueOps Jira
description: IssueOps related Jira Integration
inputs:
JIRA_API_TOKEN:
description: Jira API Token
required: true
JIRA_API_HOST:
description: Jira API Host
required: true
JIRA_USER_EMAIL:
description: Jira User Email
required: true
JIRA_ISSUE_OPERATION:
description: Jira Issue Operation
required: true
JIRA_PROJECT:
description: Jira Project
required: true
JIRA_JSON_INPUT:
description: Jira JSON Input
required: false
runs:
using: 'node20'
main: index.jsAs in the example, the action.yml accepts a few inputs, values for which would be used in the index.js as the action is executed as part of a job in the GitHub workflow.
To better understand how these values are used in the index.js file, let’s create one below.
Create an Index.js
index.js
const core = require('@actions/core');
const github = require('@actions/github');
const {Version3Client} = require('jira.js')
const {createIssue} = require('jira-issue')
const jiraClient = new Version3Client({
host: core.getInput('JIRA_HOST'),
authentication: {
basic: {
email: core.getInput('JIRA_USER_EMAIL'),
apiToken: core.getInput('JIRA_API_TOKEN')
}
}
})
switch (core.getInput('JIRA_ISSUE_OPERATION')) {
case 'CREATE_ISSUE':
const issue = JSON.parse(core.getInput('JIRA_JSON_INPUT'));
await createIssue(issue, jiraClient);
break;
default:
console.log('No operation specified or unknown operation')
break;
}jira-issue.js
const core = require('@actions/core');
exports.createIssue = async (issue,jiraClient) {
await jiraClient.issues.createIssue({
fields:{
issuetype: {
name: issue.issue_type
},
summary: issue.summary,
description: issue.description,
project: {
key: issue.project
}
}
}).then(res => {
console.log("Issues created successfully, project: " + issue.project + ", issue type: " + issue.issue_type)
return {
message: "Issues created successfully",
project: issue.project,
issueType: issue.issue_type
}
}).catch(error => {
console.log("Error creating the issue, project: " + issue.project + ", issue type: " + issue.issue_type)
core.setFailed(JSON.stringify({
message: "Error creating the issue",
project: issue.project,
issueType: issue.issue_type
}))
})
}Publishing Custom GitHub Action
The GitHub Action could be published to the GitHub Marketplace with a separate public repository and a release Tag added before publishing, as release tags are the recommended way to use the actions with a specific version in the actions workflow. It’s also required to add a README.md to the repository, describing the GitHub Action's purpose and usage.
Publishing a GitHub Action to GitHub Marketplace
To publish the GitHub Actions to the GitHub Marketplace. Follow below instructions
On the Repository, there would be a suggestion display to publish the action marketplace.
Click on the Draft a release button(See the screenshot below).
Create the release tag or choose from the drop-down on the draft page.
Accept the GitHub Marketplace developer Agreement.
Select the checkbox “Publish this release to the GitHub marketplace”
Finally, publish the action.
Using Custom GitHub Action
As mentioned above, we published the Custom JavaScript GitHub Action. In the example below, the GitHub Actions Workflow Job uses this custom action to create a Jira issue, with issue type as bug, on a certain workflow trigger event.
name: Create Jira Issues Workflow
on:
pull_request:
branches: [ "master" ]
jobs:
create_jira_issue:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [ 20.x ]
steps:
- name: Create Jira Issue
if: github.actor == 'dependabot[bot]'
uses: actions/issues-ops-jira@v1
with:
JIRA_API_TOKEN: ${{ secrets.JIRA_TOKEN }}
JIRA_API_HOST: ${{ secrets.JIRA_BASE_URL }}
JIRA_PROJECT: ${{ secrets.JIRA_PROJECT }}
JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}
JIRA_ISSUE_OPERATION: 'CREATE_ISSUE'
JIRA_JSON_INPUT: <<JSON-String-Create-Issue>>
The workflow utilizes a few secrets configured at the repository level, passing them as input parameters required for the Custom GitHub Action to create a Jira issue in the Workflow Job 'create_jira_issue'. The workflow job would create a Jira BUG when there is a PR raised by the GitHub Dependabot to fix a version for a vulnerable project dependency. The bug ticket is to track the vulnerable dependency and the fix version for the same.
Note: This blog post is a building block as part of the IssueOps Series. The purpose here is to demonstrate how GitHub actions could be a useful tool to automate such use cases. With this series, the effort would be to demonstrate the different use cases that could be automated as part of IssueOps using GitHub Actions.
Conclusion
Custom GitHub Actions are a powerful tool for streamlining and automating your software development workflow. By creating and using your own actions, you can tailor your CI/CD pipelines to your project's specific needs, leading to increased efficiency, consistency, and reusability across your repositories. This flexibility allows teams to encapsulate complex, multi-step processes into a single, manageable component, making it easier to maintain and share best practices.
Ultimately, mastering custom actions empowers developers to go beyond the pre-built marketplace and build a truly bespoke automation strategy. They represent a significant leap toward creating a highly efficient and personalized development environment, enabling teams to focus more on building great software and less on repetitive, manual tasks.


