Most of the time, Github Workflow are about chaining actions executed on some event:

on: (1)
  push:
  branches:
    - main

jobs:
  build: (2)
  runs-on: ubuntu-latest

  steps: (3)
    - name: Step1
      ...
    - name: Step2
      ...
1 trigger/event which will launch the job(s)
2 one "job" = chain of steps
3 the steps definition

An action often looks like the followin one:

- name: Do Something (1)
  id: my_id (2)
  uses: actions/some-action@v1 (3)
  with: (4)
    some_key: ${{ some_value }} (5)
1 A name used in the Github Actions UI
2 An identifier used if the action has some output (you can then use it in another action with steps.<id>.outputs.<output name>)
3 The action reference with its version (github repository with tag)
4 The action configuration
5 Configuration can use interpolated values, including the previous steps outputs.

So everything is well chained and works well…​until you need some particular value.

An example is to parse a tag name in a repository having multiple products. Concretely, you create tags with this pattern: <product>/<version>. If you have a workflow which builds automatically the product when it is tagged you likely want something like - just a pseudo-workflow:

on: (1)
  push:
    tags:
      - '*/*.*.*'
build:
  runs-on: ubuntu-latest
  steps:
    - name: Checkout (2)
      uses: actions/checkout@v2

    - name: Login to DockerHub (3)
      uses: docker/login-action@v1
      if: startsWith( github.ref, 'refs/tags/')
      with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

    - name: Build and push (4)
      id: docker_build
      uses: docker/build-push-action@v2
      if: startsWith( github.ref, 'refs/tags/')
      with: (5)
          push: true
          context: ./${{ steps.parsed_tag.outputs.image }}
          file: ./${{ steps.parsed_tag.outputs.image }}/Dockerfile
          tags: myregistry.com/myorg/${{ steps.parsed_tag.outputs.image }}:${{ steps.parsed_tag.outputs.version }}
1 When a tag is pushed - so a released is created
2 Clone the repository
3 Ensure your docker daemon is authenticated to be able to push images to a docker registry
4 Build the image and push it to your organisation
5 You can see that the configuration requires to get the image and version from the tag.

Last step will, indeed, not run in current version because steps.parsed_tag.outputs does not exist since we don’t have a step parsed_tag. The trick is to parse the tag (refs/tags/<product>/<version>) to extract the exact version.

You have the choice to have a look to an action doing almost what you want - and potentially chain some actions to do exactly the needed parsing, or to do the parsing yourself - since you can always pass a bash command and/or a command in a custom docker image in a step.

Indeed we will use last option here.

The build will get github.ref placeholder which looks like refs/tags/<product>/<version>. Parsing that in bash looks like:

ref=${{ github.ref }}
image=$(echo "$ref" |  cut -d '/' -f 3)
version=$(echo "$ref" |  cut -d '/' -f 4)

Then all the trick is to export image and version in a step output. The nice thing with Github Action is that there is a protocol to write log lines, errors, …​ and outputs through the output of the program (stdout). The syntax is ::set-output name=<output name>::<output value>. Concretely, for our bash script we just need to do:

echo "::set-output name=image::$image"
echo "::set-output name=version::$version"

Now we need to insert out bash script execution before the last step of our previous job (build) and force its id to parsed_tag to match the placeholders used in the last step:

- name: Parsed tag
  id: parsed_tag
  run: |
    ref=${{ github.ref }}
    version=$(echo "$ref" |  cut -d '/' -f 4)
    image=$(echo "$ref" |  cut -d '/' -f 3)
    echo "::set-output name=image::$image"
    echo "::set-output name=version::$version"

Indeed, the goal of this post is not to dicuss about bash or the best way to parse a string but to show that you can always, in a Github Workflow, define a custom step which can communicate with other steps through its outputs. Used with the interpolation mecanism of the steps and your preferred language for the custom steps it is really powerful and can make the workflows more readable since they can likely need less steps quite quickly.

From the same author:

In the same category: