Repo: bicep-environment-templates – Composite Actions Library

Automation is the true superpower of any development team. I say that not be overly dramatic, but the truth is that it can have a huge multipler affect on your team to leverage tools like github workflows to do more than just build / deploy applications. Github workflows can be used to perform a variety of actions including things like:

  • Installing Tools on your github runner
  • Performing common operations
  • Performing house-keeping operations like tags, packages, and releases

What are composite actions?

Github Composite Actions are a method of building reusable blocks and actions that can be pulled into any workflow that’s created. There’s a great ebook, that goes into the benefits and approach to using actions.

But for the basics, Github Actions, as a tool involves you using workflows and setting up triggers to automate events and actions around your repo. Github has a lot of great documentation here.

At it’s most basic a workflow looks like this:

name: deploy-app

on:
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v2

    - name: Build and push Docker image
      run: |
        docker build -t ghcr.io/mack-bytes.government/test-app:latest .
        echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
        docker push ghcr.io/mack-bytes.government/test-app:latest

The above workflow is a sample for building a docker container, and honestly it doesn’t get more simple than that.

What did you build?

As part of the mack-bytes-government organization on github, I’ve created a repo called bicep-environment-templates. The goal of this is to be a one-stop-shop, for developer helper scripts, bicep modules designed for government, and these github workflow actions.

The organization can be found here: https://github.com/mack-bytes-government

The repo can be found here: https://github.com/mack-bytes-government/bicep-environment-templates

Why did you build this?

As great as that utility is, it won’t take long of building these workflows for you to get to an application and say “I need this thing I built elsewhere,” or “Someone has to have solved this?” And I’ve felt that pain a lot lately. So I decided to solve that pain by building a set of public composite actions for a lot of work I’ve done overall.

So Composite actions are a method of building reusable “lego bricks” that I can add to my workflows to perform atomic operations and reduce code duplication.

The benefits of this really are:

  • Less Technical Debt: Less duplicated code means easier to perform updates.
  • Better Stability: By having a single “known good” implementation, I can prevent errors from creeping into my workflows.
  • Easier Debugging: If I have it implemented in multiple places, it becomes easier to identify where the errors are occurring.
  • Testable Units: I will get into this more in this post, but I’ve implemented a testing framework for these actions to ensure quality.

So why do this? What do we gain from composite actions? Honestly, I am building these for me. I am making reusable actions that I can leverage as part of the projects I work on both personally and professionaly. But beyond that, if I’m going to benefit, why shouldn’t you benefit too.

How do I implement these actions?

The truth is that’s the easiest part. Below is an example of implementing my “get-agent-details” action, which gets you details on the github runner:

name: get-agent-details
on: 
    workflow-dispatch:

jobs:
  test-get-agent-details:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2

    - uses: mack-bytes-government/bicep-environment-templates/composite-actions/get-agent-details@get-agent-details-latest

Now, one key note here is that I’ve leveraged git tags to facilitate support for multiple levels of version pinning for these actions. I’ve given a full write-up in the readme explaining how it works and that can be found here.

What did you mean by “testable?”

So one of the biggest headaches when you build workflows in github or pipelines in ADO, is that you have to test them. The only real way to do that is to run them. And this leads to a process where with composite actions of doing the following:

  1. Build the composite action.
  2. Go build the calling workflow.
  3. Run the calling workflow
  4. Bang your head, stub your toe until it works.

But in application development we build unit tests for that. So the question is, why can’t we do that here. So I built a framework into this repo where I can build and validate my composite actions, and do this as part of PR validation.

Let’s take an example, below is the current version of the “install-regctl” composite action:

name: 'install-regctl'
description:  'Installs regctl'
inputs:
    reg_ctl_version:
        description: 'Version of regctl to install'
        required: true
        default: 'v0.8.2'
runs:
    using: 'composite'
    steps:
        - name: Install RegCtl
          shell: bash
          run: |
            echo "Detecting architecture..."
            TARGET_ARCHITECTURE="amd64"
            ARCH=$(uname -m)

            if [[ "$ARCH" == "x86_64" ]]; then
                echo "amd64 architecture detected"
                TARGET_ARCHITECTURE="amd64"
            elif [[ "$ARCH" == "aarch64" ]]; then
                echo "arm64 architecture detected"
                TARGET_ARCHITECTURE="arm64"
            else
                echo "Unknown architecture: $ARCH"
                exit 1
            fi

            echo "Installing regctl - version ${VER_REGCTL}..."
            curl -L https://github.com/regclient/regclient/releases/download/${{ inputs.reg_ctl_version }}/regctl-linux-$TARGET_ARCHITECTURE >regctl
            sudo mv regctl /usr/local/bin/
            sudo chmod 755 /usr/local/bin/regctl
            rm -rf regctl
            echo "Getting Version Information"
            regctl version

Now if I want to run and test this, I built the following in the “.github/workflows” directory of the repo as a “test workflow”. This “test-workflow” is running the latest version of the install-regctl composite action, and confirming that it installed properly and that the version is showing correctly.

name: Test Installing regctl
on: 
    workflow_call:
      inputs:
        agent:
          description: 'The agent to run the job on'
          required: true
          type: string

jobs:
  test-install-regctl:
    runs-on: ${{ inputs.agent }}

    steps:
    - uses: actions/checkout@v2

    - uses: mack-bytes-government/bicep-environment-templates/composite-actions/install-regctl@install-regctl-latest
      with:
        reg_ctl_version: "v0.8.2"

    - name: Validate regctl
      shell: bash
      run: |
        VERSION_COMMAND_OUTPUT=$(regctl version)
        INSTALLED_VER=$(echo $VERSION_COMMAND_OUTPUT | awk '{print $2}')

        # Compare the installed version with the required version
        VER_REGCTL="v0.8.2"
        if [ "$INSTALLED_VER" == "$VER_REGCTL" ]; then
            echo "Version matches"
        else
            echo "Version does not match. Installed version is $INSTALLED_VER but required version is $VER_REGCTL"
            exit 1
        fi

I then have a single workflow in the repo (.github/workflows) called “run-tests”, and the contents is as follows:

name: Run Action tests

on:
  workflow_dispatch:
  push:
        branches:
        - main
  pull_request:
    branches:
      - '**'

jobs:
  test-get-agent-details-amd64:
    permissions:
      contents: read
      packages: read

    uses: ./.github/workflows/test-get-agent-details.yaml
    with: 
      agent: ubuntu-latest

  test-install-devcontainer-cli-amd64:
    uses: ./.github/workflows/test-install-devcontainer-cli.yaml

  test-install-regctl-amd64:
    permissions:
      contents: read
      packages: read

    uses: ./.github/workflows/test-install-regctl.yaml
    with: 
      agent: ubuntu-latest

  test-install-docker-amd64:
    permissions:
      contents: read
      packages: read

    uses: ./.github/workflows/test-install-docker.yaml
    with: 
      VER_DOCKER_MAX: 26.1.3
      agent: ubuntu-latest

Now notice, this repo calls all tests and passes the github runner to use. So if you enable arm64 agents, or have other agents to test on, they can be added here. I then went into my branch policy and set that all PRs must successfully have completed running the “run-tests.yaml” workflow.

So now, I have unit tests running on my composite actions to ensure they work before I ever build a consuming workflow downstream.

What’s next?

So for next steps with this, I intend to continue to grow this composite actions library with new items as needed. But I intend also to grow this repo with common scripts and bicep modules designed to support government requirements and deployments.

Final Thoughts

I’ve worked on projects many times that have implemented this pattern, and it always pays significant dividends. So I hope you find this useful too and it helps to accelerate your team’s efforts.

Leave a Reply

Your email address will not be published. Required fields are marked *