Github: automated Github release of GoLang binary using Github Actions

Github Actions provide the ability to define a build workflow directly in Github.  The workflow steps are defined as yaml and can be triggered by various events, including a code push, branch, or tagging in the repository.

In this article I will detail the steps of creating a statically-linked GoLang binary that is automatically built when tagged with a semantic value (e.g. ‘r1.0.1’).  It will package the git commit messages as Release Notes and the binary as a release in the repository.

Prerequisites

We need to start with a Github repository that has a Go Language program proven out locally.  For this example, you can use my ‘golang-github-action-example’ repository.

sudo apt install make git -y

# get example project
git clone https://github.com/fabianlee/golang-github-action-example.git
cd golang-github-action-example

Local compile and test (optional)

If you have a Go compiler locally installed, you can build the binary we will be automatically generating with a Github workflow in a later step.  This is an optional step.

# compile with local Go compiler
make golang-build-local

# run web server on localhost 8080
build/main

Build and Release with Github Action workflow

We will be adding a Github Action workflow by adding a yaml manifest file into the “.github/workflows” directory of our repository.

Our workflow will kick off when any new semantic tag starting with “r” (e.g. r1.0.1) is pushed to the repository.  I will go over key areas below, but download the full version of github-actions-release.yml from my github.

Trigger

on:
  push:
    tags: ['r*']

Checkout Code

Checkout and fetch all the history from the repo, because we will need this later when determining the release notes.

    - uses: actions/checkout@v3
      with:
        fetch-depth: 0 # get all tags, needed to get git log
        ref: main

Go Language setup

Install Go with setup-go on the Gitlab runner.

      - name: setup Java
        uses: actions/setup-go@v3
        with:
          go-version: '^1.19.2'

Build Go Language binary

Then we use the Go compiler to build the statically linked binary.

    - run: |
        go version
        cd src
        go mod init ${GITHUB_REPOSITORY}
        go mod tidy
        go build -ldflags "-X main.Version=${GITHUB_REF_NAME} -X main.BuiltBy=github-actions" main.go

The ‘ldflags‘ provides linker level metadata that can be compiled into the binary, and is exposed as a variable that can be used at runtime.

Build Release Notes from commit log

Because this is a small independent project we simply want to use the commit messages made from the last semantic tag up to the current tag as the Release Notes for this release.

We take advantage of the idea that you can query for a range of commits using ellipsis.

git log fromTagName...toTagName

The script below writes the commits as a list into the file “body.log”.

    - name: get semantic tag version and release notes from commit messages
      id: tag 
      run: |
        curtag=${GITHUB_REF_NAME}
        major_minor=$(echo "$curtag" | cut -d'.' -f1-2)
        patch=$(echo "$curtag" | cut -d'.' -f3)
        # avoid empty patch number
        [ -n "$patch" ] && ((patch--)) || patch=".x"
        prevtag="${major_minor}.${patch}"
        echo "" > body.log
        if git tag | grep $prevtag ; then
          git log -q ${curtag}...${prevtag} --pretty="- %s" -q --no-color >> body.log
        else
          git log --pretty="- %s" -q --no-color >> body.log
        fi
        line_count=$(cat body.log | wc -l)
        echo "curtag=$curtag" >> $GITHUB_OUTPUT
        echo "prevtag=$prevtag" >> $GITHUB_OUTPUT
        echo "line_count=$line_count" >> $GITHUB_OUTPUT

Create Github release

We finish off by using the custom release-action to push the ‘build/main’ binary and Release Notes generated at “body.log” from the previous step.

    - uses: ncipollo/release-action@v1
      with:
        name: ${{ env.GITHUB_REF_NAME }}
        tag: ${{ env.GITHUB_REF_NAME }}
        artifacts: "build/libs/springBoot.jar"
        bodyFile: "body.log"
        token: ${{ secrets.GITHUB_TOKEN }}

The “secrets.GITHUB_TOKEN” used as authentication to the Github API is a special variable pre-populated by Github.

Trigger Github Workflow

As mentioned earlier, this workflow is invoked by pushing a tag that looks like a semantic version.  Here are the git commands for creating a tag and pushing it remotely.

# check for any changes that might need to be checked in first
git status

# create new tag that triggers workflow, push tag
newtag=r1.0.0
git tag $newtag && git push origin $newtag

This will almost immediately create a workflow which you can view in real-time by going to your Github repository > Actions tab.

You can view the real-time progress by clicking into the task.  The icon will indicate when the workflow is complete.

Validate Release

The Github Release can be reached from the Github web UI.  Click on “Releases” > golang-github-action-example, and they will each be listed with Release Notes and binary download available.

 

REFERENCES

actions/checkout

actions/setup-java

ncipollo/release-action

stackoverflow, github action invoking python

github source, slackapi/slack-github-action

github cli, create repo

github cli, create secret