Building Go projects using modules on Jenkins

ci jenkins golang modules


January 25, 2019

The Go 1.11 release introduced experimental, opt-in support for Go modules. It is now possible to build Go projects outside of the $GOPATH. This profound change has a significant impact on developer workflows. For example, you don’t have to set up the expected directory structure anymore just because you want to fix a simple bug in an open source package. Furthermore, CI processes can clone the code and run the Go command directly from the checkout directory.

In this blog post, you will learn how to set up an end-to-end Jenkins pipeline for a Go project using modules. You will configure the Jenkins Go plugin which automatically downloads and installs the Go runtime for a specific version. The pipeline will consist of typical stages like compilation, running tests, performing code analysis and releasing the cross-compiled binaries.


Auto-installation of Go runtime

Using Go in Jenkins should be as easy as possible. Jenkins provides a central place for configuring tools. The Go plugin ties into that mechanism. First, install the plugin via Manage Jenkins > Manage Plugins > Available > Search for "Go Plugin", then navigate to Manage Jenkins > Global Tool Configuration > Go to configure the Go runtime.

In the screenshot below, we are configuring Go 1.11.4. Jenkins will automatically download the distribution and install it on the CI agent when running a Go build. Make sure to give the Go installation a meaningful name so it can be clearly identified in the pipeline definition.

Go tool installation

Figure 1. Configuring Go runtimes

Next, we’ll use the Go runtime in a Jenkinsfile, the "configuration as code" source of truth for Jenkins pipelines.


Setting up a Jenkinsfile

Jenkins derives the definition of a declarative pipeline from a Jenkinsfile. A Jenkinsfile uses a Domain Specific Language (DSL) to specify where the job can be run, what stages it consists of and which steps to execute in each of the stages.

Let’s start by building the foundation for a Go project. Every declarative pipeline needs to define the root element pipeline. The Jenkinsfile shown in listing 1 configures the Go runtime and enables Go modules by setting the environment variable GO111MODULE to on.

Jenkinsfile

pipeline {
    agent any
    tools {
        go 'go-1.11'
    }
    environment {
        GO111MODULE = 'on'
    }
    stages {
        ...
    }
}

Listing 1. Define a pipeline for Go projects

In the next section, we’ll declare the Go commands and tool invocations that should be executed when the pipeline is run. For that purpose, we’ll expand on the DSL element stages.


Defining pipeline stages

The actual work of a pipeline is defined in so-called stages. Stages represent a grouping of individual steps that can execute one or more steps. We’ll define the following stages to demonstrate a typical pipeline for Go projects:

  • Compile: Compiles packages and dependencies.

  • Tests: Runs the unit tests and publishes the coverage metrics to Codecov.

  • Code Quality: Performs code quality analysis on the source code with golangci-lint.

  • Release: Builds and publishes the binaries using GoReleaser.

Jenkins' pipeline visualization renders each stage of a pipeline as shown in figure 2. You may have noticed that the last stages hasn’t been executed. We’ll configure the "release" stage to only run if the commit has been tagged.

Pipeline visualization

Figure 2. The standard pipeline visualization in Jenkins

Let’s complete the pipeline definition by adding the stages described above. In listing 2, you can see the relevant shell commands for each stage. For some of the commands, you will have to set up access tokens by defining environment variables or credentials.

Jenkinsfile

pipeline {
    ...

    stages {
        stage('Compile') {
            steps {
                sh 'go build'
            }
        }
        stage('Test') {
            environment {
                CODECOV_TOKEN = credentials('codecov_token')
            }
            steps {
                sh 'go test ./... -coverprofile=coverage.txt'
                sh "curl -s https://codecov.io/bash | bash -s -"
            }
        }
        stage('Code Analysis') {
            steps {
                sh 'curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin v1.12.5'
                sh 'golangci-lint run'
            }
        }
        stage('Release') {
            when {
                buildingTag()
            }
            environment {
                GITHUB_TOKEN = credentials('github_token')
            }
            steps {
                sh 'curl -sL https://git.io/goreleaser | bash'
            }
        }
    }
}

Listing 2. Typical CI stages for a Go project

Once you are happy with the pipeline definition, make sure to commit and push the Jenkinsfile to the remote repository. Now, we are ready to stand up the pipeline job in Jenkins.


Creating the pipeline job

Creating the pipeline job in Jenkins require a manual step. In the UI, select New Item, enter a name and select "Pipeline" or "Multibranch Pipeline" depending on whether you want to build a pipeline for a single branch or for multiple branches.

Pipeline job

Figure 3. Selecting the location of the Jenkinsfile

In the section "Pipeline" of the new job select "Pipeline script from SCM", pick the origin of the Jenkinsfile and enter the repository URL. Save the job and you are done. Click the option "Build Now" to ensure that everything works as expected.



comments powered by Disqus