We’ve contributed the setup-terminus action to Pantheon as their official method for installing Terminus in GitHub Actions, so you should now switch to using pantheon-systems/terminus-github-actions!
GitHub Actions is a continuous integration and continuous delivery (CI/CD) platform that allows you to automate running tasks using your code, such as compiling binaries, running test suites, and deploying changes.
Sometimes, you might find yourself wanting to pass values between these steps – this can be particularly useful for composite actions, where you need to compute a default value for an optional input.
This was something I recently needed to do, and decided to write up a post showcasing how I accomplished it.
If you just want to know the “how”, the tl;dr is you can pass values between steps by writing them as <key>=<value> pairs to $GITHUB_ENV
The Backstory
I recently created a composite Github Action called ackama/setup-terminus (which lives here) which (as you might have guessed) handles setting up the latest version of the Terminus CLI for interacting with Pantheon.
The action is pretty straight forward, being made up of two steps:
name: Setup Terminus
description: 'Installs and configures the Pantheon CLI tool, Terminus.'
inputs:
pantheon-machine-token:
description: 'Machine token used to authenticate with Pantheon.'
required: true
runs:
using: composite
steps:
- name: Install Terminus
shell: bash
run: |
mkdir ~/terminus && cd ~/terminus
TERMINUS_RELEASE=$(curl --silent "https://api.github.com/repos/pantheon-systems/terminus/releases/latest" | perl -nle'print $& while m#"tag_name": "\K[^"]*#g')
curl -L https://github.com/pantheon-systems/terminus/releases/download/$TERMINUS_RELEASE/terminus.phar --output terminus
chmod +x terminus
sudo ln -s ~/terminus/terminus /usr/local/bin/terminus
- name: Login to Pantheon
shell: bash
run: |
terminus auth:login --machine-token="${{ inputs.pantheon-machine-token }}"
For brevity, I’m going to omit the “Login to Pantheon” step in future snippets, since it doesn’t change
To keep things simple initially, I used the steps provided by Pantheon for installing Terminus as a standalone phar (which means you only require PHP to be able to run it).
While pretty straightforward and robust, it works by grabbing the latest release of the CLI from it’s home on GitHub, and I knew that we were still using the previous major version in some of our workflows so wanted to explore being able to pass the action a specific version as an optional input.
I determined that to support this, the action would have to do the following:
Accept an optional terminus-version input
Install the version of Terminus set by terminus-version, if present
Fallback to installing the latest version of Terminus, if terminus-version isn’t set
Together, these would allow developers to provide the action with a terminus-version to have that version installed:
This was very easy to do, as the action already takes a required input so it was just a matter of copying that and setting required to false:
name: Setup Terminus
description: 'Installs and configures the Pantheon CLI tool, Terminus.'
inputs:
pantheon-machine-token:
description: 'Machine token used to authenticate with Pantheon.'
required: true
terminus-version:
description: |
The full version of Terminus to install. If omitted, the latest version is used.
required: false
runs:
using: composite
steps:
# ...
While inputs also support a default value, it requires a static string meaning we can’t leverage that to fallback to grabbing the latest version of Terminus. We’ll touch on that later.
2. Install the version of Terminus set by terminus-version, if present
Now that I had the terminus-version input, I wanted to use that in the install step which we can do with an expression that accesses the inputscontext to assign the value of the terminus-version input to the TERMINUS_RELEASE variable we’re already using:
name: Setup Terminus
description: 'Installs and configures the Pantheon CLI tool, Terminus.'
inputs:
pantheon-machine-token:
description: 'Machine token used to authenticate with Pantheon.'
required: true
terminus-version:
description: |
The full version of Terminus to install. If omitted, the latest version is used.
required: false
runs:
using: composite
steps:
- name: Install Terminus
shell: bash
run: |
mkdir ~/terminus && cd ~/terminus
TERMINUS_RELEASE="${{ inputs.terminus-version }}"
curl -L https://github.com/pantheon-systems/terminus/releases/download/$TERMINUS_RELEASE/terminus.phar --output terminus
chmod +x terminus
sudo ln -s ~/terminus/terminus /usr/local/bin/terminus
# ...
If this wasn’t a composite action, the input value would also be available as an environment variable called INPUT_TERMINUS_VERSION, which we could use instead of an expression.
This worked, but left me with an issue: terminus-version is an optional input, so it might not have a value!
I could have fixed this by using setting a default value on the input, like so:
name: Setup Terminus
description: 'Installs and configures the Pantheon CLI tool, Terminus.'
inputs:
pantheon-machine-token:
description: 'Machine token used to authenticate with Pantheon.'
required: true
terminus-version:
description: |
The full version of Terminus to install. If omitted, the latest version is used.
required: false
default: 3.0.5
runs:
using: composite
steps:
# ...
The issue with this is that it might not be the latest version, so we’d have to update our action every time a new version of Terminus came out if we wanted to default to installing the latest version.
Unfortunately, default only accepts static strings, so I couldn’t just stick the subshell command we were using earlier to grab the latest version…
3. Fallback to installing the latest version of Terminus, if terminus-version isn’t set
This is where the title of this post comes into play – I could have solved this with more Bash script, but that’d be adding more complexity to the install step which would make it harder to follow and more prone to bugs (especially since tools like ShellCheck don’t support checking bash inlined within YAML).
I could have put the script into its own file, which would allow native tools to be used, but it felt excessive to have a whole extra file given the size of the codebase
Instead, I wanted to see if you could have a step output a value that another step could use and it turns out you can! Pretty easily in fact – GitHub Actions provides an environment variable called GITHUB_ENV which points to a file that is unique for each step in a job, and that sets environment variables within the workflow based on its contents (which should be in <key>=<value> form).
Since it’s a file, we can add to it using the “redirect and append” operator (>>) in Bash:
This creates an environment variable called TERMINUS_RELEASE that is accessible by any step after this one either directly within a script ($TERMINUS_RELEASE) or within an expression via the envcontext (${{ env.TERMINUS_RELEASE }}).
Now that I had the latest version of Terminus set dynamically and available to other steps, it was just a matter of updating the install step to use that version if the terminus-version input isn’t provided:
name: Setup Terminus
description: 'Installs and configures the Pantheon CLI tool, Terminus.'
inputs:
pantheon-machine-token:
description: 'Machine token used to authenticate with Pantheon.'
required: true
terminus-version:
description: |
The full version of Terminus to install. If omitted, the latest version is used.
required: false
runs:
using: composite
steps:
- name: Determine version
shell: bash
run: |
TERMINUS_RELEASE=$(curl --silent "https://api.github.com/repos/pantheon-systems/terminus/releases/latest" | perl -nle'print $& while m#"tag_name": "\K[^"]*#g')
echo "TERMINUS_RELEASE=$TERMINUS_RELEASE" >> $GITHUB_ENV
- name: Install Terminus
shell: bash
run: |
mkdir ~/terminus && cd ~/terminus
curl -L https://github.com/pantheon-systems/terminus/releases/download/$TERMINUS_RELEASE/terminus.phar --output terminus
chmod +x terminus
sudo ln -s ~/terminus/terminus /usr/local/bin/terminus
env:
TERMINUS_RELEASE: ${{ inputs.terminus-version || env.TERMINUS_RELEASE }}
# ...
To help keep things clean, I decided to handle setting the TERMINUS_RELEASE variable using the envmap rather than have the expression embedded with the rest of the script, where it would have been harder to spot.
4. Bonus optimisation: only retrieve the latest release if we expect to use it
While the above works well, we actually don’t always need to know what the latest terminus release is – luckily steps in GitHub Actions support an if property that can be used to have a step run conditionally, based on if the property value is true.
I used this to skip the determine version step if the terminus-value input is provided:
name: Setup Terminus
description: 'Installs and configures the Pantheon CLI tool, Terminus.'
inputs:
pantheon-machine-token:
description: 'Machine token used to authenticate with Pantheon.'
required: true
terminus-version:
description: |
The full version of Terminus to install. If omitted, the latest version is used.
required: false
runs:
using: composite
steps:
- name: Determine version
shell: bash
if: ${{ ! inputs.terminus-version }}
run: |
TERMINUS_RELEASE=$(curl --silent "https://api.github.com/repos/pantheon-systems/terminus/releases/latest" | perl -nle'print $& while m#"tag_name": "\K[^"]*#g')
echo "TERMINUS_RELEASE=$TERMINUS_RELEASE" >> $GITHUB_ENV
# ...
All together now
This is what the action now looks like:
name: Setup Terminus
description: 'Installs and configures the Pantheon CLI tool, Terminus.'
inputs:
pantheon-machine-token:
description: 'Machine token used to authenticate with Pantheon.'
required: true
terminus-version:
description: |
The full version of Terminus to install. If omitted, the latest version is used.
required: false
runs:
using: composite
steps:
- name: Determine version
shell: bash
if: ${{ ! inputs.terminus-version }}
run: |
TERMINUS_RELEASE=$(curl --silent "https://api.github.com/repos/pantheon-systems/terminus/releases/latest" | perl -nle'print $& while m#"tag_name": "\K[^"]*#g')
echo "TERMINUS_RELEASE=$TERMINUS_RELEASE" >> $GITHUB_ENV
- name: Install Terminus
shell: bash
run: |
mkdir ~/terminus && cd ~/terminus
echo "Installing Terminus v$TERMINUS_RELEASE"
curl -L https://github.com/pantheon-systems/terminus/releases/download/$TERMINUS_RELEASE/terminus.phar --output terminus
chmod +x terminus
sudo ln -s ~/terminus/terminus /usr/local/bin/terminus
env:
TERMINUS_RELEASE: ${{ inputs.terminus-version || env.TERMINUS_RELEASE }}
- name: Login to Pantheon
shell: bash
run: |
terminus auth:login --machine-token="${{ inputs.pantheon-machine-token }}"
Ackama has acquired OptimalBI, a New Zealand-based company specialising in data engineering, insights, and operations. This acquisition strengthens Ackama’s ability to support governments, the energy sector and mission driven organisations, where high quality data and data capabilities are…
This year at Ackama has been one of resilience and progress in a challenging environment. Following our acquisition of Common Code, we’ve continued to strengthen our capabilities and expand our impact. We’ve worked with…
We're excited to be working with our Kiwi colleagues to deliver ambitious,
purposeful digital products on both sides of the Tasman.
You've come to the right place!
Common Code is now part of Ackama.
We’re now part of Ackama, delivering purposeful technology across the Asia-Pacific.
Together, we’re creating impact across energy, government, international development, and beyond. Delivering pragmatic, innovative solutions where they matter most.