Github Actions - Workflow Patterns

Written by Paul Bradley

Pipelines for Cross Compiling Go programs. Deploying AWS Infrastructure with Terraform and more...

Image of an Anime character using a computer. Generated by AI

Table of Contents
  1. Introduction
  2. Cross Compiling Go to OSX & Windows
  3. Deploying AWS Infrastructure with Terraform
  4. Compiling and Deploying a Go Lambda Function

Introduction

I’m late to using github actions, but I’m a total convert.

During the last seven days, I’ve worked on two projects that have benefited from using them. I am documenting the general patterns here for personal use, but if you find them useful, please use them.

Cross Compiling Go to OSX & Windows

I’m working on a project which adds extra functionality to an SQLite database. Go is good at cross-compiling your code to different computer architectures if you avoid using CGO-enabled packages. My SQLite extensions project uses a CGO-enabled package, and I’ve tried various ways to cross-compile the code without success.

The GitHub actions below use a macos-11 runner to build the OSX binary. I have another action which uses the windows-latest runner, which builds the binary for Windows.

My local development machine is Ubuntu, so I can build the Linux version locally. When I push the code up to GitHub, the two actions are invoked, and within a minute, I have a Windows and OSX version of my project.

 1name: Build OSX Version
 2
 3on:
 4  push:
 5    branches: [ "main" ]
 6  pull_request:
 7    branches: [ "main" ]
 8
 9jobs:
10
11  build:
12    runs-on: macos-11
13    steps:
14    - uses: actions/checkout@v3
15
16    - name: 'Set up Go'
17      uses: actions/setup-go@v3
18      with:
19        go-version: 1.19
20
21    - name: 'Install Go Packages'
22      run: |
23        go get -u go.riyazali.net/sqlite
24        go get -u github.com/leekchan/accounting        
25
26    - name: 'Build'
27      run: go build -v -buildmode=c-shared -o s0-sqlite-extensions-osx-amd64.so ./...
28
29    - name: 'Upload Artifact'
30      uses: actions/upload-artifact@v3
31      with:
32        name: osx-binary
33        path: s0-sqlite-extensions-osx-amd64.so
34        retention-days: 2

Deploying AWS Infrastructure with Terraform

The second project involved me working on some joint Terraform code with a third party. As we were both contributing to the same code repository, we set up Github actions to deploy the infrastructure using the Terraform actions.

Before we started, I migrated the state file from my local machine to an S3 bucket. I introduced the backend block to my main terraform file and then re-initialised it.

 1terraform {
 2    required_providers {
 3        aws = {
 4            source  = "hashicorp/aws"
 5            version = "~> 4.42.0"
 6        }
 7    }
 8    required_version = ">= 1.3.4"
 9
10    backend "s3" {
11        bucket = "projectx-terraform-state-files"
12        key = "prod/dns/terraform.tfstate"
13        region = "eu-west-2"
14    }
15}

I introduced the workflow below with the state file safely stored in an S3 bucket. It runs when the code is pushed to the main or development branch.

The workflow sets up the AWS credentials by pulling them in from Github’s repository secrets store. It then checks the code, initialises Terraform and runs the Plan command to see what needs to change.

The final step of the workflow runs if the checked-out code is from the main branch. The last step is the application of the changes to the AWS infrastructure.

 1name: Terraform Infrastructure Change Management Pipeline
 2
 3on:
 4  push:
 5    branches: [ "main", "development" ]
 6
 7permissions:
 8  id-token: write
 9  contents: read
10
11jobs:
12  build:
13    runs-on: ubuntu-latest
14    defaults:
15      run:
16        shell: bash
17        working-directory: ./production
18
19    steps:
20
21      - name: 'Configure AWS Credentials'
22        uses: aws-actions/configure-aws-credentials@v2
23        with:
24          aws-access-key-id: ${{ secrets.AWS_PRODUCTION_ACCESS_KEY }}
25          aws-secret-access-key: ${{ secrets.AWS_PRODUCTION_ACCESS_SECRET }}
26          aws-region: eu-west-2
27
28      - name: 'Checkout'
29        uses: actions/checkout@v3
30
31      - name: 'Setup Terraform'
32        uses: hashicorp/setup-terraform@v2
33        with:
34          terraform_version: 1.4.6
35
36      - name: 'Terraform Init'
37        run: terraform init
38
39      - name: 'Terraform Plan'
40        run: terraform plan -input=false
41        continue-on-error: true
42
43      - name: 'Terraform Apply'
44        if: github.ref == 'refs/heads/main' && github.event_name == 'push'
45        run: terraform apply -auto-approve -input=false

Compiling and Deploying a Go Lambda Function

Most of my web applications use AWS serverless technology known as Lambda.

The Github workflow below shows the actions I take to compile my Go Lambda function and deploy it to AWS. The workflow also compresses the Go binary using UPX to make the package as small as possible to reduce cold start times. After compressing the binary it then ZIPs the file up before finally pushing it AWS.

 1name: Compile & Deploy Production
 2
 3on:
 4  push:
 5    branches: [ "main" ]
 6  pull_request:
 7    branches: [ "main" ]
 8
 9jobs:
10
11  build:
12    runs-on: ubuntu-latest
13    steps:
14    - uses: actions/checkout@v3
15
16    - name: 'Set up Go'
17      uses: actions/setup-go@v3
18      with:
19        go-version: 1.19
20
21    - name: 'Install Go Packages'
22      run: go mod tidy
23
24    - name: 'Compile Go Binary'
25      run: GOARCH=arm64 GOOS=linux go build -ldflags="-w -s" -o bootstrap main.go state.go templates.go
26
27    - name: 'Compress Go Binary with UPX'
28      uses: svenstaro/upx-action@v2
29      with:
30        files: |
31                    bootstrap
32        args: -9
33        strip: false
34
35    - name: 'ZIP Binary'
36      uses: montudor/action-zip@v0.1.0
37      with:
38        args: zip -qq -r ./main.zip . -i bootstrap
39
40    - name: 'Deploy to AWS Production Account'
41      uses: appleboy/lambda-action@master
42      with:
43        aws_access_key_id: ${{ secrets.AWS_PRODUCTION_ACCESS_KEY }}
44        aws_secret_access_key: ${{ secrets.AWS_PRODUCTION_ACCESS_SECRET }}
45        aws_region: eu-west-2
46        function_name: arn:aws:lambda:eu-west-2:561675481801:function:prod-central-lpres-viewer-default
47        zip_file: main.zip
48        publish: true