GitHub recently launched its own automated build tool, GitHub Actions, but there is no open registration yet. I submitted my application as soon as possible, and now it has been approved, so I have tried GitHub Actions for the first time. The general feeling is that tools like Travis CI should be shivering in the corner 😄?

GitHub Actions allows you to build a complete CI/CD Pipeline that is deeply integrated with the GitHub ecosystem without using third party services such as Travis CI or Circle CI, and is freely available for open source projects. If you want to use it as soon as possible, you can use the link github.com/features/ac… Go apply for permission.

github actions

Golang project

To demonstrate GitHub Actions, let’s build a simple “Hello World” Golang application that contains a basic Pipeline, Each Pull Request or push of code to the Master branch triggers an automatic build of the Pipeline, lint the code, run unit tests, and generate code coverage reports using Codecov.

When a new tag is created on the repository, Pipeline releases a new GitHub version using the GoReleaser tool.

GoReleaser is an automated release tool for Golang projects that simplifies the build and release process and provides some custom options for all processes.

Create a repository on GitHub named Go-Github-Actions and create a main.go file in the project root directory as follows:

package main

import (
	"fmt"

	"github.com/cnych/go-github-actions/hello"
)

func main() {
	fmt.Println(hello.Greet())
}Copy the code

Hello is the Greet function (hello package), so we need to create a new package named Hello under the root directory, and create a new file named hellog.go under the package as follows:

package hello

// Greet... Greet GitHub Actions
func Greet() string {
	return "Hello GitHub Actions"
}Copy the code

Initialize go Modules in the project root directory:

$ go mod init github.com/cnych/go-github-actions
go: creating new go.mod: module github.com/cnych/go-github-actionsCopy the code

Then create a unit test file (hello_test.go) under the hello package as follows:

package hello import "testing" func TestGreet(t *testing.T) { result := Greet() if result ! = "Hello GitHub Actions" { t.Errorf("Greet() = %s; Expected Hello GitHub actions", result) } }Copy the code

Execute unit tests under the root directory:

$go test./hello OK github.com/cnych/go-github-actions/hello 0.007s $go run main.go hello GitHub ActionsCopy the code

The final code structure looks like this:

$tree.. ├── Go ├── go, go, go, go, go, go, go, go, go, go, go, go, go, go, go, go, go, go, goCopy the code

Finally, don’t forget to push the code to GitHub.

GitHub Actions Pipeline

After we push the code to GitHub, we can see the Actions entry on the page (if it is already open) :

github actions config

You can see that Actions provides many built-in workflows, such as Golang, Rust, Python, Node, etc. To write your own workflows, click on Set Up a Workflow Yourself in the upper right corner. Jump to the writing page of Pipeline:

github actions pipeline custom

The on property controls the conditions under which a build is triggered by Workflow, such as when code is pushed to the Master and release branches:

on:
  push:
    branches:
    - master
    - release/*Copy the code

When only the pull_request is merged into the master branch:

on:
  pull_request:
    branches:
    - masterCopy the code

In addition, it can be triggered by timed tasks, such as the daily build task Monday through Friday at 2pm:

on:
  schedule:
  - cron: 0 2 * * 1-5Copy the code

Making the Actions of the Workflow can complete syntax in the document help.github.com/articles/wo… .

Another important thing is that actions are reusable units of work that can be created and distributed by anyone on GitHub. We can find all kinds of actions on GitHub Marketplace. Do this by specifying that the action is included and the ref you want to use:

- name: < display name for action >
  uses: {owner}/{repo}@ref
  with:
      <map of inputs>Copy the code

By looking at the default Workflow script generated for GitHub Actions, we can see that workflow is a set of jobs and steps that meet certain conditions or events:

name: CI

on: [push]

jobs:
  build:

    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v1
    - name: Run a one-line script
      run: echo Hello, world!
    - name: Run a multi-line script
      run: |
        echo Add other actions to build,
        echo test, and deploy your project.Copy the code

You can also have multiple workflows within a project, each responding to a different set of events.

main workflow

In our example here, we will define two workflows. The Build workflow will be triggered when pushing code to the Master branch or creating a PR, and the Release workflow will be triggered when creating a new tag. The workflow releases a new version of the application.

Each workflow consists of one or more jobs. Our Build Workflow contains three jobs (Lint, Build, and Test), while Release Workflow contains only one Release Job.

Each Job consists of multiple steps. For example, a “unit test” Job contains steps to get code, run tests, and produce code coverage reports.

Workflow is defined in a YAML file under the.github/workflows directory at the root of the repository, where each file represents a different Workflow.

Here is the Build Workflow we defined :(main.yaml)

name: Build and Test on: push: branches: - master pull_request: jobs: lint: name: Lint runs-on: ubuntu-latest steps: -name: Set up Go uses: actions/setup-go@v1 with: go-version: 1.12 -name: Check out code uses: actions/checkout@v1 - name: Lint Go Code run: | export PATH=$PATH:$(go env GOPATH)/bin # temporary fix. See https://github.com/actions/setup-go/issues/14 go get -u golang.org/x/lint/golint make lint test: name: Test runs-on: ubuntu-latest steps: - name: Set up Go uses: Actions /setup-go@v1 with: go-version: 1.12-name: Check out code uses: actions/checkout@v1 - name: Run Unit tests. run: make test-coverage - name: Upload Coverage report to CodeCov uses: Codecov /[email protected] with: token: ${{secrets.codecov_token}} file:./coverage. TXT build: name: build runs-on: Ubuntu - Latest needs: [Lint, test] Steps: -name: Set up Go uses: actions/setup-go@v1 with: go-version: 1.12-name: Check out code uses: actions/checkout@v1 - name: Build run: make buildCopy the code

We first define the name of the workflow and trigger rules. We want the trigger rules to be triggered when the code is pushed to the Master branch or when a PR is executed.

on:
  push:
    branches:
      - master
  pull_request:Copy the code

The workflow contains three jobs: Lint, Test, and Build. Lint defines jobs as follows:

lint: name: Lint runs-on: ubuntu-latest steps: - name: Set up Go uses: actions/setup-go@v1 with: go-version: 1.12-name: Check out code uses: actions/checkout@v1 -name: Lint Go code run: | export PATH=$PATH:$(go env GOPATH)/bin # temporary fix. See https://github.com/actions/setup-go/issues/14 go get -u golang.org/x/lint/golint make lintCopy the code

Here we specify that we want the Job task to run on ubuntu (the runs-on keyword). Actions now supports Linux, Mac, Windows, and Docker environments, and will be able to run its own machines as runners in the future, similar to GitLab CI Runner. Then the Job execution steps are defined:

GitHub already provides such an action, so we can use it directly:

- name: Set up Go
  uses: actions/setup-go@v1
  with:
    go-version: 1.12Copy the code

You can find out how to use action on GitHub Marketplace:
Github.com/marketplace….

The with keyword allows us to specify parameters for the action. The setup-go action allows us to specify the version of Go to use. Since we used go modules in the example above, So we specify version 1.12 (greater than 1.11), and the next step is to get the source code, again using a built-in action:

- name: Check out code
  uses: actions/checkout@v1Copy the code

Then we install and run the Golint tool:

- name: Lint Go Code
  run: |
    export PATH=$PATH:$(go env GOPATH)/bin
    go get -u golang.org/x/lint/golint 
    make lintCopy the code

This completes the Job definition for Lint, and the rest of the jobs are similarly defined. For example, let’s look at the Test Job and see the following definition:

test: name: Test runs-on: ubuntu-latest steps: - name: Set up Go uses: actions/setup-go@v1 with: go-version: 1.12-name: Check out code uses: actions/checkout@v1 - name: Run Unit tests. Run: make test-coverage - name: Upload Coverage report to CodeCov uses: CodeCov /[email protected] with: token: ${{secrets.codecov_token}} file: ./coverage.txtCopy the code

The only difference in this definition is that the action used for upload code test coverage is a third-party action, which can also be found in the Marketplace: github.com/marketplace… , we will upload the code coverage for the test to CodeCov. Here we need to use GitHub secrets to store the CodeCov Token needed for the operation of CodeCov. Log in via GitHub user authorization on CodeCov website, and then enable the above [go-Github – Actions] project. GitHub project Settings -> Secrets: Name = CODECOV_TOKEN, Value = Codecov Token This completes the action declaration for the Test Job.

We can create our own actions in any language (including a Dockerfile), or use the official Action development kit if you prefer Typescript:
Github.com/actions/too….

This completes our first workflow😄, but note that everything we do here is done with the make command, so we also need to add a Makefile in the project root directory that looks like this:

PROJECT_NAME := "github.com/cnych/go-github-actions" PKG := "$(PROJECT_NAME)" PKG_LIST := $(shell go list ${PKG}/... | grep -v /vendor/) GO_FILES := $(shell find . -name '*.go' | grep -v /vendor/ | grep -v _test.go) .PHONY: all dep lint vet test test-coverage build clean all: build dep: ## Get the dependencies @go mod download lint: ## Lint Golang files @golint -set_exit_status ${PKG_LIST} vet: ## Run go vet @go vet ${PKG_LIST} test: ## Run unittests @go test -short ${PKG_LIST} test-coverage: ## Run tests with coverage @go test -short -coverprofile cover.out -covermode=atomic ${PKG_LIST} @cat cover.out >> coverage.txt build: dep ## Build the binary file @go build -i -o build/main $(PKG) clean: ## Remove previous build @rm -f ./build help: ## Display this help screen @grep -h -E '^[a-zA-Z_-]+:.*? # #. *? ' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*? # # "}; {printf "\033[36m%-30s\033[0m %s\n", ?1, ?2}'Copy the code

Next we’ll create a new branch and change the code a little bit and look at the PR workflow in Actions:

$ git checkout -b actions-demo
Switched to a new branch 'actions-demo'Copy the code

Then modify the Greet function in hello package:

// Greet ... Greet GitHub Actions
func Greet() string {
  return "Hello GitHub Actions. qikqiak.com is awesome"
}Copy the code

Also modify the test code in hello_test.go:

func TestGreet(t *testing.T) { result := Greet() if result ! = "Hello GitHub Actions. qikqiak.com is awesome" { t.Errorf("Greet() = %s; Expected Hello GitHub Actions. qikqiak.com is awesome", result) } }Copy the code

Keep in mind that the workflow file defined above is also added to the project root directory.github/workflows/main.ymlIn the file, now push the branch and create a Pull Request to the master branch. The workflow defined above will be immediately triggered to build. The merge operation cannot be performed when workflow is not completed.

After the task is executed, Merge can be performed:

We also integrated with CodeCov in Workflow above, so we can see PR status checks and Coverage code Coverage reports:

release workflow

Above we defined the workflow for building the test. Next we will define our release workflow in a similar way. As mentioned above, each workflow is a separate file. We create a file here. Making/workflows/the yml, content as follows:

name: Release
on:
  create:
    tags:
    - v*

jobs:
  release:
    name: Release on GitHub
    runs-on: ubuntu-latest
    steps:
    - name: Check out code
      uses: actions/checkout@v1

    - name: Validates GO releaser config
      uses: docker://goreleaser/goreleaser:latest
      with:
        args: check

    - name: Create release on GitHub
      uses: docker://goreleaser/goreleaser:latest
      with:
        args: release
      env:
        GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}Copy the code

In this Workflow we define the task to be fired only when a new tag is created, and then only a release Job.

In the Job task we defined above, we first get the project code, and then use GoReleaser official Docker image to build the task. When using a Docker container, you can define args and entryPoint of the container. Here we use args to define check and release respectively.

GITHUB_TOKEN specifies the GITHUB_TOKEN environment variable required by GoReleaser to publish our app on GitHub, but note that the value of the variable is automatically injected by the Actions platform. So we don’t need to add it manually, so it’s much more convenient.

Then we create a new tag and push it to the repository:

$git tag v0.1.0 $git push -- Tags Counting Objects: 5, done. 100% (5/5), 799 bytes | 0 bytes/s, done. Total 5 (delta 0), In flue 0 (delta 0) To [email protected]:cnych/go-github-actions. Git * [new tag] v0.1.0 -> v0.1.0Copy the code

If all goes well, Job construction is triggered immediately, and a new version of the Job is created on GitHub after the Job is built, which contains the application package and Changelog automatically generated by the GoReleaser tool.

github actions release

Here we have completed the first GitHub Actions Pipeline😄, this is a very basic example, but a good example for us to understand how GitHub Actions work.

conclusion

GitHub Actions should be adequate for most open source projects hosted on GitHub. It is not currently supported for more advanced uses, such as manual approval and parameterized builds, which are often used by enterprise projects, but GitHub Actions is currently in beta. It may come in the near future, not so much for Jenkins, GitLab CI and other popular tools in the enterprise, but for some third-party CI/CD tools that rely on GitHub, For example, Travis CI or Circle CI, because GitHub Actions are good enough for projects hosted on GitHub, and more importantly, built-in, So it is not unreasonable to say that Travis CI is now shivering in some corner 🤣.

Reference documentation

  • The Features, making the Actions
  • Workflow syntax for GitHub Actions
  • Building a basic CI/CD pipeline for a Golang application using GitHub Actions

Wechat official account

Scan the qr code below to follow our wechat account and reply ◉ and join our Kubernetes discussion group to learn together.

“Sincere appreciation, lingering fragrance in hand”