Github Actions
Tags: github, actions
Published: 2023-01-13 11:40:17

---
title: Github Actions
published: true
description: 
tags: github, actions
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2023-01-13 09:55 +0000
---
I've been using different build systems for years, but after playing with github actions quite a bit there was still some things that I was only managing based on trial and error. I've written this guide to try and fill in those gaps. If you've been through the [Quickstart for GitHub Actions](https://docs.github.com/en/actions/quickstart) and [Understanding GitHub Actions](https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions) documentation but still have some questions I'm hoping this post will answer some of them.

First some definitions:
* Workflows are combinations of jobs
* Jobs are combinations of steps that are run on a virtual machine, they can even be a workflow of their own
* Steps are either actions or shell commands

## Sharing data in your workflow
This was one of the bits that I felt the documentation didn't explain clearly enough. All the information is there, but spread across multiple pages and can take quite a while to put it all together.

### Environment variable types

It's worth baring in mind that because most of what follows uses environment variables under the hood it is a `string`, so even though the value could be `'true'` or `'false'` you would need an expression that explicitly compares it to those values. e.g. `if: env.isTag == 'true'` would work, but `if: env.isTag` alone would not.

[Inputs](https://docs.github.com/en/actions/learn-github-actions/contexts#inputs-context) to workflows however can be `string`, `number` or `boolean` types.

### Sharing data between steps in a job
If one of your steps has an output that you need in a later step within the same job you can set it as an environment variable. This is done by sending a statement setting it to the `$GITHUB_ENV` file. A minimal demonstration of this is:

```yaml
steps:
  - name: Set myValue
    run: echo "myValue=wibble" >> $GITHUB_ENV
  - name: Echo myValue
    run: echo "$myValue"
```

Ofcourse the above isn't that useful, the `wibble` part would usually be a calculated value. You would usually use some of the calculate the value from some other environment variables. You can either calculate your value in the bash script, e.g.

```yaml
env:
  wibble: wibble
steps:
  - name: Set myValue
    run: echo "myValue=$GITHUB_REF_NAME-$wibble" >> $GITHUB_ENV
```

or you can use the javascript context `${{ }}` and any of the [expressions](https://docs.github.com/en/actions/learn-github-actions/expressions) or [contexts](https://docs.github.com/en/actions/learn-github-actions/contexts) to calculate your value, e.g.

```yaml
env:
  wibble: wibble
steps:
  - name: Set myValue
    run: echo "myValue=${{ github.ref_name + "-" + env.wibble }} >> $GITHUB_ENV
```
These two blocks should be equivalent, but the [expressions](https://docs.github.com/en/actions/learn-github-actions/expressions) mean I generally use the JS version.

The key bit is that it is sent to the `$GITHUB_ENV` file. This is what will make it available as an environment variable to the steps that follow it. But only within that job. What if you want to share that value with different jobs?

### Sharing data between jobs within a workflow

In the above you used the `$GITHUB_ENV` file to make a value available to later steps, but if you want to share a value to a different job you need to use the `$GITHUB_OUTPUTS` file instead. But that would be too easy. In addition you also need to declare it as an output.

Within your job you will need to add an `outputs` section that uses the JS context to access the value you will output. You do this by accessing the [`steps` context object](https://docs.github.com/en/actions/learn-github-actions/contexts#steps-context). The step that you use will now need an `id` so that you can access it through `steps.<stepId>`.
For example, if you want a job that outputs a value that says if this build was triggered by a tag:

```yaml
jobs:
  isTagJob:
    runs-on: ubuntu-latest
  outputs:
    isTag: ${{ steps.set_istag.outputs.isTag }}
  steps:
    - id: set_istag   # step id used to find the output above
      name: Set isTag # name that will appear in the logging for this section
      run: echo "isTag=${{ startsWith(github.ref, 'refs/tags/') }}" >> $GITHUB_OUTPUT
```

This will make the `isTag` value available to any jobs dependant on this one through the [needs context](https://docs.github.com/en/actions/learn-github-actions/contexts#needs-context). The format is `needs.<jobId>.outputs.<outputName>` so the above would be `needs.isTagJob.outputs.isTag`. For example the following will only run the `announce_tag` step if `isTag` is `'true'`.

```yaml
...
  announceTag:
    runs-on: ubuntu-latest
    needs: [isTagJob] # This makes it dependent on the `isTagJob` job above
    steps:
      - id: announce_tag
        name: Announce tag
        if: needs.isTag.outputs.isTag == 'true'
        run: echo ::notice::This build is for a tag
```

### Sharing data to reusable workflows

Workflows that are called by other workflows use `workflow_call` as their `on` trigger. You can pass values to these workflows by defining `inputs` in that workflow and passing the values in using `with` in the calling workflow.

For example the file `.github/workflows/build-dotnet.yml` shown below takes two inputs `projectDir` and `isTag` and then uses them in it's jobs.

```yaml
name: Build Dotnet

on:
  workflow_call:
    inputs:
      projectDir:
        required: true
        type: string
      isTag:
        required: true
        type: boolean

jobs:
  build-code:
    runs-on: ubuntu-latest
    steps:
      - id: build
        name: "Build ${{ inputs.projectDir}}"
        run: dotnet build ${{ inputs.projectDir}}
      - id: push_artifact
        name: "Push ${{ inputs.projectDir}} artifact"
        if: inputs.isTag # the input is a boolean so don't need to compare to `'true'` like with env vars.
        uses: actions/upload-artifact@v3
          with:
            name: ${{ inputs.projectDir }}
            path: ./temp/${{ inputs.projectDir}}/**/*
```

The calling workflow will need to ensure it provides the values correctly so that this workflow can use them. The calling workflow code could look like:

```yaml
name: Build
on:
    push:
jobs:
  isTagJob:
    runs-on: ubuntu-latest
    outputs:
      isTag: ${{ steps.set_istag.outputs.isTag }}
    steps:
      - id: set_istag   # step id used to find the output above
        name: Set isTag # name that will appear in the logging for this section
        run: echo "isTag=${{ startsWith(github.ref, 'refs/tags/') }}" >> $GITHUB_OUTPUT
  build-dotnet-proj-a:
    needs: [isTagJob]
    uses: ./.github/workflows/build-dotnet.yml
    with:
      projectDir: projects/projectA
      isTag: ${{needs.isTagJob.outputs.isTag == 'true'}}
  build-dotnet-proj-b:
    needs: [isTagJob]
    uses: ./.github/workflows/build-dotnet.yml
    with:
      projectDir: projects/projectB
      isTag: ${{needs.isTagJob.outputs.isTag == 'true'}}
```

The same syntax for inputs in reusable workflows is also used actions.