Creating another custom GH Action using F#: git-tag-to-workflow-dispatch
Introduction
Last year, I wrote the post Custom GH Action using F# and dynamic matrix configuration explaining how I created a custom GitHub Action using F# for generating a dynamic matrix configuration. By that time, my goal was only to tackle a personal necessity regarding a Terraform automation workflow, so I decided not to publish the action.
Then, this year I discovered the Is there possible to use Git Tags as a choice of workflow dispatch's input discussion on GitHub community repo, which, to this day, is still open. And I thought it was an interesting challenge.
But before we dig into the custom action details, we need to establish a common ground. You need to know what is a workflow, and what are some of the available configuration for the workflows, with focus on the workflow_dispatch
trigger.
And I know that this knowledge is not common for people outside the DevOps/DevSecOps environment, which are more familiar with CI/CD concepts, so let's start by exploring these concepts, and later I present the problem using more clear sentences.
The problem
According to the GitHub documentation, in the context of GitHub Actions:
A workflow is a configurable automated process that will run one or more jobs. Workflows are defined by a YAML file checked in to your repository and will run when triggered by an event in your repository, or they can be triggered manually, or at a defined schedule.
Workflows are defined in the .github/workflows directory in a repository, and a repository can have multiple workflows, each of which can perform a different set of tasks. For example, you can have one workflow to build and test pull requests, another workflow to deploy your application every time a release is created, and still another workflow that adds a label every time someone opens a new issue.
— [1]
When we create our workflows, we could use different triggers. The workflow_dispatch
trigger is used:
To enable a workflow to be triggered manually, you need to configure the workflow_dispatch event. You can manually trigger a workflow run using the GitHub API, GitHub CLI, or GitHub browser interface.
[…]
You can configure custom-defined input properties, default input values, and required inputs for the event directly in your workflow.
— [2]
So, with this short introduction to GitHub Actions concepts, you can finally understand that what this people are asking for is a way to have the repository tags listed automatically as input options when using a workflow with a workflow_dispatch
trigger.
The solution
Since the problem is still open, I decided to create my own solution FsharpGHActions/git-tag-to-workflow-dispatch and share with the community, using F# again because this is my main language for now. So let's start exploring it.
Let's start from the action.yml file, which is the entrypoint for the custom action. At the runs:
key we start defining the steps of it.
The first step is a new checkout (actions/checkout@v4
) of the repository, so this action has access to the repository .github/workflows
folder. At first I thought this was not required, because the client would already run the checkout, but after a test I noticed it was necessary.
- name: Checkout repo
uses: actions/checkout@v4
Then, the next step is just to build the environment variable HELPER_PATH. This variable is going to be used later to specify where to download the custom F# program, and to avoid collisions with the repository folders. I decided to use the commit SHA (GITHUB_SHA
) appended to the string -fsharp-program
. The value of the GITHUB_SHA
is defined as:
The commit SHA that triggered the workflow. […] For example, ffac537e6cbbf934b08745a378932722df287a53.
— [3]
- name: Add HELPER_PATH
shell: bash
run: echo "HELPER_PATH=${GITHUB_SHA}-fsharp-program" >> "${GITHUB_ENV}"
The next three steps are related to the download, extraction and execution of the custom F# program:
- name: Download the self-contained .NET program
shell: bash
env:
PACKAGE_URL: 'https://github.com/FsharpGHActions/git-tag-to-workflow-dispatch/releases/download/v0.0.017/fsharp-program.tar.gz'
run: wget -O "${HELPER_PATH}.tar.gz" "${PACKAGE_URL}"
- name: Uncompress the .NET program
shell: bash
run: |
mkdir "${HELPER_PATH}/"
tar -xf "${HELPER_PATH}.tar.gz" -C "${HELPER_PATH}/"
- name: Run the .NET program
shell: bash
env:
VALUES_TO_TAKE: ${{ inputs.values-to-take }}
WORKFLOW_KEY: ${{ inputs.workflow-yaml-key }}
GIT_TAGS: ${{ inputs.git-tags }}
run: ./${HELPER_PATH}/out/Main "${GIT_TAGS}"
This F# program is responsible for building the new workflow_dispatch
options. It starts from this template:
on:
workflow_dispatch:
inputs:
TEMP_WORKFLOW_KEY%%:
type: choice
options:
- v1.0.0
- v1.0.1
and generates something like this, configured to take as much as VALUES_TO_TAKE tags from GIT_TAGS, and later replacing the placeholder TEMP_WORKFLOW_KEY%% from the template by the WORKFLOW_KEY value.
For example, suppose that you sent this:
- VALUES_TO_TAKE = 5
- WORKFLOW_KEY = version
- GIT_TAGS = [v0.0.010, v0.0.009, v0.0.008, v0.0.007, v0.0.006, v0.0.005, v0.0.004, v0.0.003, v0.0.002, v0.0.001]
It will generate this file:
on:
workflow_dispatch:
inputs:
version:
type: choice
options:
- v0.0.010
- v0.0.009
- v0.0.008
- v0.0.007
- v0.0.006
Next, there's a step that merges this updated workflow_dispatch
configuration with the target workflow, preserving almost perfectly the target YAML configuration. Other than that, you can later review the PR automatically created to before applying this update, just to make sure it's good enough.
There, I decided to use mikefarah/yq because it's easy, has many features and works really well.
- name: Merge the workflows
uses: mikefarah/yq@master
env:
WORKFLOW_FILE_NAME: ${{ inputs.workflow-file-name }}
with:
cmd: yq -i '. * load("./workflow.new.yml")' ".github/workflows/${WORKFLOW_FILE_NAME}"
The next steps are for debugging and cleaning the repository, where the merged workflow is printed to the console, and the F# program files are removed.
- name: Debug -> Print the new workflow file
shell: bash
env:
WORKFLOW_FILE_NAME: ${{ inputs.workflow-file-name }}
run: cat ".github/workflows/${WORKFLOW_FILE_NAME}"
- name: Clean action
shell: bash
run: |
rm "./workflow.new.yml"
rm "./${HELPER_PATH}.tar.gz"
rm -rf "./${HELPER_PATH}/"
And finally, the last step is where we create the pull request that updates the repository configuration using peter-evans/create-pull-request project.
- name: Create pull request
# if: ${{ inputs.pull-request }} # TODO
uses: peter-evans/create-pull-request@v6
with:
token: ${{ inputs.github-token }}
base: ${{ inputs.base }}
branch-suffix: timestamp
branch: git-tag-to-workflow-dispatch
commit-message: |
chore: automated git tags to workflow dispatch
this commit is updating the workflow dispatch (github actions) options autonomously
with the tags from this repository
My idea in the long run is to map more inputs from this action to my own custom action, to let the user configure more properties of the pull request that is going to be used.
How to use it
The usage instructions are already explained at the repository README.org, and if this is not good yet, there's a sample project showing how it can be used here: link.
If you don't want to go for that link, this is the workflow configuration:
name: On tag creation
on:
workflow_dispatch:
push:
tags: # https://github.com/orgs/community/discussions/25302
- '*'
jobs:
test-git-tag-to-workflow-dispatch:
name: Test git-tag-to-workflow-dispatch
runs-on: ubuntu-22.04
strategy:
matrix:
files: [example-001.yml]
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
fetch-depth: 0 # to get the tags
- name: Get the repository tags
run: |
THIS_GIT_TAGS=$(git tag --sort -creatordate | tr '\n' ' ')
echo "Git tags: ${THIS_GIT_TAGS}"
echo "THIS_GIT_TAGS=${THIS_GIT_TAGS}" >> "${GITHUB_ENV}"
- name: Run custom action
uses: FsharpGHActions/git-tag-to-workflow-dispatch@v0.0.018
with:
values-to-take: 5
workflow-file-name: ${{ matrix.files }}
workflow-yaml-key: 'version'
pull-request: true
# needs to be a PAT to update the workflows/ folder
github-token: ${{ secrets.PAT_GITHUB }}
git-tags: ${{ env.THIS_GIT_TAGS }}
base: 'main'
And you can find an example pull request created here: link.
Conclusion
I know that this solution is not really what the community wants, but considering the current limitations, and considering that the real solution is only possible from GitHub dev team itself, I think it's good enough, working as an automation helper.
By using this action you don't really need to remember to update the workflow whenever a new tag is created, you'll already have an open PR with the necessary changes waiting to be reviewed and merged.