TIL; There is a cool FOSS tool called GoReleaser. This will make releasing Golang project a lot easier. You can see the source code of this example in https://github.com/franzramadhan/homebrew-go-example
Background
We want to automate binary release process of our Golang code. We also want the binary to be installable using Homebrew without needing to manually create the Homebrew Formula.
In this example we want to demonstrate how easily we can do a release for our Go binary project.
Solution and Constraints
- We can utilize GoReleaser to automate the release and Homebrew Formula creation.
- GoReleaser also already has Github Action so we can easily integrate it in Github workflow
- We will only build and release commit-hash that has semantic versioning tag. e.g: v1.0.0, v0.0.1-rc, etc
- In order to host Homebrew formula, the git repository name is mandatory to have
homebrew-
prefix
Workflow
Write the code for main.go. Basically it will just do HTTP request to http://numbersapi.com/ to show the fact of the day.
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"time"
"github.com/hashicorp/go-retryablehttp"
)
func main() {
// use retryable http
client := retryablehttp.NewClient()
client.RetryMax = 10
// get current time and set the date and month format
currentTime := time.Now()
month := currentTime.Format("1")
date := currentTime.Format("2")
req, err := retryablehttp.NewRequest(http.MethodGet, "http://numbersapi.com/"+month+"/"+date+"/date?json", nil)
if err != nil {
log.Fatalln(err)
}
res, err := client.Do(req)
if err != nil {
log.Fatalln(err)
}
defer res.Body.Close()
var response map[string]interface{}
fail := json.NewDecoder(res.Body).Decode(&response)
if fail != nil {
log.Fatalln(err)
}
resp, err := json.Marshal(response)
if err != nil {
log.Fatalln(err)
}
fmt.Println(string(resp))
}
Write the unit test
package main
import (
"testing"
)
func TestMain(t *testing.T) {
main()
}
- Create .goreleaser.yml
# Check https://goreleaser.com/customization/env/
env:
- GO111MODULE=on
- GOPROXY=https://goproxy.io
# Check https://goreleaser.com/customization/hooks/
before:
hooks:
- go mod download
# Check https://goreleaser.com/customization/build/
builds:
- env:
- CGO_ENABLED=0
goos:
- darwin
- linux
goarch:
- amd64
ignore:
- goos: darwin
goarch: 386
- goos: linux
goarch: arm
goarm: 7
- goarm: mips64
gomips: hardfloat
# Run upx after build finished
hooks:
post: ./upx.sh
# Check https://goreleaser.com/customization/archive/
archives:
- name_template: "homebrew-go-example_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
replacements:
amd64: x86_64
project_name: homebrew-go-example
# Check https://goreleaser.com/customization/homebrew/
brews:
- homepage: 'https://github.com/franzramadhan/homebrew-go-example'
description: 'Example binary distribution using homebrew.'
folder: Formula
commit_author:
name: franzramadhan
email: franzramadhan@gmail.com
tap:
owner: franzramadhan
name: homebrew-go-example
Create the github workflow for running unit test. This will run the unit test for every pull request and push.
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
# checkout the repository
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
# Install specific version of go
- name: Setup go
uses: actions/setup-go@v2
with:
go-version: 1.15
# Run unit test
- name: Test
run: go test ./...
Create workflow for release. This will only run when there is a semantic version format tag pushed.
name: Release
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+' # Only build tag with semantic versioning format
jobs:
build:
runs-on: ubuntu-latest
steps:
# checkout the repository
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
# install upx to compress the binary
- name: Install upx
run: sudo apt-get install -y upx
# Install specific version of go
- name: Setup go
uses: actions/setup-go@v2
with:
go-version: 1.15
# Run goreleaser with command line flag
- name: Release
uses: goreleaser/goreleaser-action@v2
if: startsWith(github.ref, 'refs/tags/')
with:
version: latest
args: -f .goreleaser.yml release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Trigger release
- Create a git tag with semantic version format. e.g
git tag -s -a v0.0.1
. This will create a signedv0.0.1
tag of latest commit - Push it to the repo
git push origin v0.0.1
- See the release action being run in https://github.com/franzramadhan/homebrew-go-example/actions?query=workflow%3ARelease
- Wait until all process completed and
Formula
directory is created in the root git directory with Homebrew formula written in Ruby.
Install and Test
- Tap the homebrew repo
brew tap franzramadhan/go-example
- Install the binary
brew install homebrew-go-example
- Test to run it
❯ homebrew-go-example | python -m json.tool
2020/09/25 11:15:23 [DEBUG] GET http://numbersapi.com/9/25/date?json
{
"found": true,
"number": 269,
"text": "September 25th is the day in 2003 that a magnitude-8.0 earthquake strikes just offshore Hokkaid\u014d, Japan.",
"type": "date",
"year": 2003
}
Conclusion
- GoReleaser can make life a lot easier for everyone that want to automated release for their golang project
- Aside of able to release and create the homebrew formula automatically, it also has other built in feature to release artifact for another type or platform. Such as Docker, NFPM( Deb or RPM ), Artifactory, etc. Complete features and documentation, are listed in the official site