You must use the default actions and the community-created actions when developing various workflow needs. However, sometimes the requirements that you need to implement in a workflow are not supported by available actions. You may want to create actions to define workflows as you desire in such scenarios.
This chapter explores creating custom actions and utilizing them in GitHub Actions workflows.
Types of Actions
Actions perform specific tasks in a GitHub Actions workflow. With custom actions, you can interact with a GitHub repo using the GitHub API or interact with external APIs to perform activities.
There are three types of actions: Docker container actions, JavaScript actions, and composite run steps actions. Let’s look at each of these types.
Docker container actions: The Docker container action’s dependencies are packaged as a Docker container to utilize the action reliably and consistently. Since they need to build and retrieve the container before executing the actions, Docker container actions are slower than JavaScript actions. Docker container actions can only be run on Linux runners. If you want to use a Linux-based self-hosted runner to run Docker container actions, you must first install Docker.
JavaScript actions: JavaScript actions run faster and run directly on the runner machine. If you intend to run JavaScript actions on GitHub-hosted runners, the actions should be written in pure JavaScript without any dependencies on any other binaries. JavaScript actions can run on Windows, macOS, or Linux runners.
Composite run steps actions: You can combine multiple run steps into a single action and enable a workflow to execute all the run steps defined in the action as a single action. Composite run step actions can run on Windows, macOS, or Linux runners.
This section looked at types of actions and their differences.
Creating Custom Actions
Custom actions perform desired steps and are reusable in multiple workflows. This section looks at creating custom actions.
JavaScript Custom Action
Let’s begin with creating a public GitHub repo. Once the repo is created, it can be cloned to your machine using VS Code. You need to have Node.js 12.x or higher and npm installed on your machine to perform the steps described here. You can verify the node and npm versions with the following commands in a VS Code terminal (also see Figure 9-1).
node --version
npm --version
Figure 9-1
Check node and npm versions
You need to execute npm init -y to initialize the folder with a package.json file (see Figure 9-2).
Figure 9-2
Folder for first custom action initialized
Next, you need to create an action metadata file in the folder. The metadata file defines the action's main entry point, input, and output. The name of the file must be action.yml or action.yaml. The following YAML file includes using: 'node12', which says this is a JavaScript action, and main: 'index.js', which defines the entry point. The sample action metadata file is shown next.
name: 'DemoJSAction'
description: 'Display massage'
inputs:
name-of-you: # id of input
description: 'Your name'
required: true
default: 'Chaminda'
outputs:
time: # id of output
description: 'The time of the message'
runs:
using: 'node12'
main: 'index.js'
This metadata file defines one input parameter that asks to provide a name and one output parameter that is the time of the message.
Next, you must set up the actions toolkit packages’ actions/core and actions/github in the custom actions folder. To do this, you need to execute the following commands (also see Figure 9-3).
npm install @actions/core
npm install @actions/github
Figure 9-3
Install actions toolkit components
The code needs to execute the action to index.js because it is the file specified in the metadata to run (see Figure 9-4).
const core = require('@actions/core');
const github = require('@actions/github');
try {
// `name-of-you` input defined in action metadata file
const yourName = core.getInput('name-of-you');
console.log(`Hello ${yourName}!`);
const time = (new Date()).toTimeString();
core.setOutput("time", time);
// Get the JSON webhook payload for the event that triggered the workflow
Optionally, you can add a readMe.md file to the repo so that users know how to use it.
# Demo javascript action
This action prints "Hello Chaminda" or "Hello" + the name of a person
## Inputs
### `name-of-you`
**Required** The name of the You. Default `"Chaminda"`.
## Outputs
### `time`
The time of the message.
## Example usage
uses: chamindac/demojsaction@v1.1
with:
name-of-you: 'Pushpa'
To compile the code and the modules for distribution, you can use @vercel/ncc, which you must first install. Execute npm i -g @vercel/ncc to install @vercel/ncc/ in the terminal (see Figure 9-5).
Figure 9-5
Installing @vercel/ncc
Now you can build the distribution package for the action by using the following command (see Figure 9-6).
ncc build index.js --license licenses.txt
Figure 9-6
Build action for distribution
The dist/index.json is added with node module content, and dist/licenses.txt is added with all the license information for the node modules used (see Figure 9-7).
Figure 9-7
Distribution files for action
The action.yml metadata file should be updated to use the new entry point, dist/index.js (see Figure 9-8).
Figure 9-8
Change entry point of action
The next step is to commit the code and compiled action.js files to the repo. Use the following command to add the files for commit (also see Figure 9-9).
The following commands commit and push the action files to the repo (see Figure 9-10).
git commit -m "First js action is ready"
git tag -a -m "First js action release" v1
git push --follow-tags
Figure 9-10
Commit and push custom action
The action files are available in the public repo, as shown in Figure 9-11.
Figure 9-11
Custom action files in public GitHub repo
You can use a custom action within a new GitHub repo workflow, as shown next. Public repo actions can be used in any repo.
on: [workflow_dispatch]
jobs:
custom_js_action_job:
runs-on: ubuntu-latest
name: Custom js Action Demo
steps:
- name: First js action step
id: myjsaction
uses: chamindac/demojsaction@v1
with:
name-of-you: 'Pushpa'
# Use the output from the `myjsaction` step
- name: Get the output message time
run: echo "The time was ${{ steps.myjsaction.outputs.time }}"
The action step prints the message with the input name (see Figure 9-12).
Figure 9-12
Print message in custom action
Next, the message time is printed as output obtained from the custom action step (see Figure 9-13).
Figure 9-13
Print message time
You have created an action in a public repo and used it in another GitHub repo workflow. However, if you create a custom action in a private GitHub repo, it is only usable in the same repo. You need to check out the repo and state to use its root if the action is in the root of the repo, as shown next.
on: [workflow_dispatch]
jobs:
custom_js_action_job:
runs-on: ubuntu-latest
name: Custom js Action Demo
steps:
# To use this repository's private action,
# you must check out the repository
- name: Checkout
uses: actions/checkout@v2
- name: Custom js Action Step
uses: ./ # Uses an action in the root directory
id: myjsaction
with:
name-of-you: 'Pushpa'
# Use the output from the `myjsaction` step
- name: Get the output time
run: echo "The time was ${{ steps.myjsaction.outputs.time }}"
This section discussed developing a custom JavaScript action to enhance GitHub workflows.
Composite Run Steps Action
Composite actions let you combine multiple run steps in a single action. Let’s create a simple composite action to understand how it works. As a prerequisite, let’s create a public repo and clone it to a local machine. Next, open it in Visual Studio Code. Create a folder named mycompositeaction in the repo. Add a file named helloworld.sh and enter the echo "Hello World! This is my compositeaction" (see Figure 9-14).
Figure 9-14
helloworld.sh
You must make the helloworld.sh executable. For this, you can use chmod +x hellowold.sh on a Linux machine. However, if you are using a Windows machine, you need to use the following commands to make the helloworld.sh executable and let Git notify with it (also see Figure 9-15).
git add helloworld.sh
git update-index --chmod=+x helloworld.sh
Figure 9-15
Make helloworld.sh executable
Next let’s add an action.yml with the custom action’s metadata. It takes two inputs (your name and country), greets you, and prints.
name: 'Hello World'
description: 'saying hello world to composite action'
inputs:
your-name: # id of input
description: 'Your Name'
required: true
default: 'Chaminda'
runs:
using: "composite"
steps:
- run: echo Hello ${{ inputs.your-name }}.
shell: bash
- run: ${{ github.action_path }}/helloworld.sh
shell: bash
Next, add action.yml, git, commit, and push (see Figure 9-16).
git add action.yml
git commit -m "my composite action added"
git tag -a -m "my composite action release" v1
git push --follow-tags
Figure 9-16
Commit and push
You can test the composite action using the following workflow. Notice that we are referring to an action in a repo folder. This way, you can keep multiple actions in the same repo.
The composite action executed in the workflow prints the input name and the message from helloworld.sh (see Figure 9-17).
Figure 9-17
Composite action in a workflow
Docker Container Action
Docker container actions let you develop your actions using any language because it runs on an image selected by you. Let’s use the composite run steps action repo for the container action.
First, create a folder named mycontaineraction in the repo folder's root (see Figure 9-18).
Figure 9-18
Folder for container action
Next, add a Docker file and define the image and the code file to copy to the container root for execution (see Figure 9-19).
You must enable the execution for mydockeractionsample.sh file. In Linux, you can use chmod +x mydockeractionsample.sh. However, in Windows, use the following command.
# Use the output from the `mycontaineraction` step
- name: Get the output time
run: echo "The time was ${{ steps.mycontaineraction.outputs.timeofmessage }}"
The executed workflow successfully uses the container action (see Figure 9-22).
Figure 9-22
Container action used in workflow
Publishing Custom Actions
You can publish the custom actions you created in the GitHub Marketplace for others to use. However, you need to satisfy the following requirements in your action to allow it to be published in the GitHub Marketplace.
The repo must be public.
The repo can only contain a single action. In the previous section, you created a JavaScript action as a single action in the repo. Therefore, you can publish it to the marketplace. However, the container and composite step run actions were created in the same repo, which prevents you from publishing them to the marketplace.
An action.yml metadata file must be in the root of the repo.
The name of the action cannot have a name already used in the marketplace.
Let’s try to publish the JavaScript action in the Marketplace. When you open the repo, you see that you can draft a release to make your action discoverable in the GitHub Marketplace (see Figure 9-23).
Figure 9-23
Draft a release
You can tag a release by accepting the Marketplace agreement before publishing (see Figure 9-24).
Figure 9-24
Agreement
You must complete two-factor authentication before publishing an action to the marketplace.
Summary
This chapter explored developing custom actions for GitHub Actions workflows using JavaScript, containers, or composite step-run actions. Custom actions interact with GitHub or external APIs, further enhancing your workflows’ capabilities.
The next chapter looks at a few quick-start examples of GitHub Actions.