Recently at my day job I have been working on a Golang-based application, for which I wanted to set up an automated CD pipeline for building and releasing. Our application is a command line tool, so the release part is basically copying and uploading the binaries to a specified location.
Since I have been using AppVeyor for my .NET Core projects (Stubbery and RestApiHelpers), and it’s working really nicely, it seemed to be the obvious choice for Golang as well.
I did a quick Google search, and was surprised, because I didn’t find any guides about setting this up. On the other hand, I found the appveyor.yml
file of another open-source Golang-project, which looked rather simple, so I decided to try to set up my pipeline based on that. It turned out to be even simpler than what I expected.
Setting up the build
In this section I’ll describe a sample AppVeyor configuration, which is enough to build a Golang application. (I’ll only focus on the parts relevant to Golang.)
We have two different options to configure AppVeyor projects: one is two use a management website to set the different properties of the project, and the other is to put an appveyor.yml
file at the root of our repository, which describes the same settings, and is automatically processed by AppVeyor.
I chose the latter approach, because it keeps the configuration close to the source code, this way it’s easy to track its history, and it can be part of pull requests. Furthermore, the yml
file contains only the settings which are relevant for our project, so it’s often quite small and easy to understand.
The AppVeyor configuration
I’ll show the relevant parts of the appveyor.yml
file and describe them in detail.
Basic settings
version: 1.0.0.{build}
platform: x64
branches:
only:
- master
The version number can be customized to fit our needs, and it can be manually bumped when we are releasing a new version. The {build}
argument is an internal environment variable of AppVeyor, which will be automatically bumped on every build.
I configured my pipeline to be triggered only by pushes to the master branch, this can be modified in the branches
section.
Cloning and prerequisites
clone_folder: c:\gopath\src\github.com\account\myproject
environment:
GOPATH: c:\gopath
install:
- echo %PATH%
- echo %GOPATH%
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
- go version
- go env
The clone_folder
property specifies the location our repo gets cloned to. This is important in the Golang ecosystem, since we usually want our projects to reside under the folders specified by either the GOROOT
or the GOPATH
environment variables. Hence we are setting that root folder as the value of the GOPATH
variable in the environment
section.
It turns out that the default AppVeyor agent image — among many other technologies — comes with Go preinstalled, so in the install
section we don’t have to do any real installation.
However, it’s still a good idea to print out some dianostic information about our Go environment, which can help troubleshooting if the build goes south. We also add %GOPATH%\bin
and C:\go\bin
to the PATH, so we can use the go toolchain in our build scripts without specifying full paths.
Building
Depending on how complicated our build is, we can either use a go build
command directly in our appveyor.yml
.
build_script:
- go build -o buildOutput\myapp -i .
Or, if we have a more complicated build (for example in my case we are cross-compiling the application for three different platforms), it’s a better idea to put a script doing the build in our repository (I used PowerShell), and execute that in the pipeline.
build_script:
- ps: .\build.ps1
The next thing to specify is the list of files to use during the deployment. These have to be added to the artifacts
section.
artifacts:
- path: buildOutput/myapp
name: binary
We can list multiple files if we are generating more than one build output.
Deployment
The last part is doing the actual deployment of our artifacts.
Now this part will completely depend on the way we want to release are application. I only investigated using GitHub Releases, since that was perfect for my purposes, but AppVeyor has built-in support for numerous other systems (Amazon S3, different Azure services, NuGet, etc.). And I’m sure we can roll our own deployment script as well.
Here is an example for setting up the deploy
section for GitHub Releases.
deploy:
release: myapp-$(appveyor_build_version)
description: 'This is a release of my awesome application.'
provider: GitHub
auth_token:
secure: ZkOJAiZBmapKpbiqovaofs+W0foBWaV9Jom4yBYzcRKlAk4Bee+5b7t+5LrQRVn8
artifact: binary # This is the name we specified in the artifacts section.
draft: false
prerelease: false
on:
branch: master
Where the value of the secure
property is a Personal access token we have to set up on the settings page of GitHub.
One gotcha is that we shouldn’t add the raw value of our token to the appveyor.yml
, since then anyone would be able to read it from our repository (and GitHub is actually smart enough to notice that we erroneously added the plain value of a token to our repo and disables it automatically).
We should rather encrypt it with the Encrypt data option on the AppVeyor management site, and use that value instead. AppVeyor will recognize that its an encrypted token and decrypt it automatically.
So this is the complete appveyor.yml
file we have to put at the root of our repository to get a basic build working. (More details about all the settings can be found in official documentation.)
version: 1.0.0.{build}
platform: x64
branches:
only:
- master
clone_folder: c:\gopath\src\github.com\account\myproject
environment:
GOPATH: c:\gopath
install:
- echo %PATH%
- echo %GOPATH%
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
- go version
- go env
build_script:
- go build -o buildOutput\myapp -i .
build_script:
- ps: .\build.ps1
artifacts:
- path: buildOutput/myapp
name: binary
deploy:
release: myapp-$(appveyor_build_version)
description: 'This is a release of my awesome application.'
provider: GitHub
auth_token:
secure: FW3tJ3fMncxvs58/ifSP7w+kBl9BlxvRMr9liHmnBs14ALRG8Vfyol+sNhj9u2JA
artifact: binary # This is the name we specified in the artifacts section.
draft: false
prerelease: false
on:
branch: master
I think this example shows that setting up a build pipeline for a Golang project in AppVeyor is really simple, and I hope this post will save some time for people getting started with continuous delivery in Golang.