Basics ๐
When developing new automations it’s best to make each component as modular and reusable as possible. When using GitHub Actions, Composite Actions may be your answer for quick development and reusability.
Composite Actions allow you to execute multiple shell steps by calling the Action. These shell steps can be bash, python, nodejs, or powershell. This functionality can be very powerful when you have logic you often want to reuse.
Take for example, running a small Python script that requires a dependency in an Actions Workflow. You could create a Composite Action that:
- Using the bash shell, executes
pip3 install <package>
- Using the python shell, include a Python script in-line
- OR, using the bash shell, execute a Python script in a file
That might look something like:
name: 'run-python-script'
description: 'Installs a package and runs a python script'
runs:
using: "composite"
steps:
- name: Install boto3
shell: bash
run: pip3 install boto3
- name: Run Python Script
shell: python
run: |
print("hello world")
It looks much like a Workflow YAML file but is a little different. The important part to note is using: composite
. Additionally, only the run
command can be used to execute code in a specified shell
.
There is one huge caveat for using Composite Actions - you can’t call another Action.
How and Where ๐
With that concept in mind, you may want to create many Composite Actions. But, how and where do you put them?
Each GitHub Action must reside in its own action.yml
file in its own directory. I’d suggest creating a repository just for reusable Actions. In that repo, or your primary repo, create an /actions/
directory to store the Actions. Put each Action in a separate directory.
It should look like this:
actions
โโโ another-action
โย ย โโโ action.yml
โโโ first-action
โโโ action.yml
Inputs and Outputs ๐
Just like a function or method in programming languages, Composite Actions provide for inputs (parameters) or outputs (return values).
In this example, we have an input parameter named name
and an output called greeting
. Using the bash
shell, we return Hello <name>
as the greeting output.
name: 'say-hello'
description: 'Returns a greeting output with a greeting to the input name'
inputs:
name:
required: true
outputs:
greeting:
required: true
runs:
using: "composite"
steps:
- name: say-hello
shell: bash
env:
INPUT_NAME: ${{ inputs.name }}
run: |
GREETING="Hello $INPUT_NAME"
echo "::set-output name=greeting::$GREETING"
We need to do a few things to use inputs and outputs.
First, the inputs
and outputs
stanzas must be included in the YAML.
Then, to get the input values as environment variables, we must specify under the env
key. This is because the inputs are not automatically provided as environment variables when using Composite Actions, unlike Workflows.
Finally, to use the output, we must print a special string to stdout. The string must be in the format of ::set-output name=<NAME>::<VALUE>
. This can be accomplished using print()
in Python, console.log()
in JavaScript, or echo
in bash.
Calling Composite Actions ๐
You can call a Composite Action from a Workflow just like any other Action. If you’re storing them in a private repository, you need to check the repository out first.
After the Composite Action has run, the outputs of the Action are available as steps.<step-name>.outputs.<output-name>
and can be used in any future steps.
In this example we’re always using Doug
as the value of name
, but it could just as well be an input to the workflow_dispatch
event.
name: Greetings
on: [workflow_dispatch]
jobs:
greeting:
runs-on: ubuntu-latest
steps:
- name: Checkout this repo
uses: actions/checkout@v2
- name: Call Composite Action say-hello
uses: ./actions/say-hello
id: say-hello
with:
name: 'Doug'
- name: echo output from say-hello
run: |
echo ${{ steps.say-hello.outputs.greeting }}