青梅梦呓

和世界交手的这许多年,你是否光彩依旧,兴致盎然

0%

GitHub Actions 自动化构建Go应用

欧老板昨天问我怎么让自己的go程序使用github-actions,给欧老板做了一个简单的demo, 一个beego的程序,随便写了点单测的代码。一个基本的 Pipeline,推送代码到 master 分支的时候就会触发该 Pipeline 的自动构建,进行代码的静态化检查操作、运行单元测试并使用 Codecov 生成代码覆盖率报告,部署到Netlify,并且根据Dockerfile生成Docker镜像。

Golang 项目

主程序

主要是为了演示GitHub Actions的功能,欧老板说他用的beego,我就构建一个最简单的”Hello world”的 beego 程序,其中就包含一个基本的 Pipeline,推送代码到 master 分支的时候就会触发该 Pipeline 的自动构建,进行代码的静态化检查操作、运行单元测试并使用 Codecov 生成代码覆盖率报告,部署到Netlify,并且根据Dockerfile生成Docker镜像。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

// main.go的代码
package main

import "github.com/astaxie/beego"

type MainController struct {
beego.Controller
}

func (this *MainController) Get() {
controller.Ctx.WriteString("感谢欧阳老板给我机会,尽管老板你姓欧")
}

func main() {
beego.Router("/", &MainController{})
beego.Run()
}

单元测试

单测不知道该写什么,创建了hello的文件夹,在里面写了个最长前缀匹配的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//hello.go的代码
package hello

import "strings"

// longestCommonPrefix LeetCode最长公共前缀
func LongestCommonPrefix(strs []string) string {
if len(strs) < 1 {
return ""
}
prefix := strs[0]
for _,k := range strs {
for strings.Index(k,prefix) != 0 {
if len(prefix) == 0 {
return ""
}
prefix = prefix[:len(prefix) - 1]
}
}
return prefix
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// hello_test.go的代码
package hello

import (
"testing"

. "github.com/smartystreets/goconvey/convey"
)

//TestLongestCommonPrefix goconvey单测
func TestLongestCommonPrefix(t *testing.T) {
Convey("TestLongestCommonPrefix should return fl ", t, func() {
a := []string{"flower", "flow", "flight"}
So(LongestCommonPrefix(a), ShouldEqual, "fl")
})
Convey("TestLongestCommonPrefix should return empty string ", t, func() {
b := []string{"dog", "racecar", "car"}
So(LongestCommonPrefix(b), ShouldEqual, "")
})
}

我一直使用go modules,生成依赖,最终的目录结构是下面这样的;我不太会写Makefile,所以直接就把Dockerfile放在了根目录下面。

.
└── actions-go-demo
├── Dockerfile
├── LICENSE
├── README.md
├── go.mod
├── go.sum
├── hello
│ ├── hello.go
│ └── hello_test.go
└── main.go

Github Action配置

简单的构建

手写或者自动生成actions的配置文件.github/workflows/go.yml都可以。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
name: Beego

on:
push:
branches: [ master ]

env:
GOPROXY: "https://goproxy.cn"
GO111MODULE: "on"

jobs:

build:
name: Build
runs-on: ubuntu-latest
steps:

- name: Set up Go 1.14
uses: actions/setup-go@v2
with:
go-version: ^1.14
id: go

- name: Check out code into the Go module directory
uses: actions/checkout@v2

- name: Cache Primes
uses: actions/cache@v1
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-

- name: Go Get dependencies and go module
run: |
go mod tidy
go get -v -t -d ./...

- name: Build
# 这里build 时交叉编译并设置使用所有内置库静态编译程序
# 方便最后一步做出的Docker镜像小一些
run: CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-extldflags "-static"' -o main .

- name: Staticcheck
run: |
# https://github.com/actions/setup-go/issues/14
# 已经修复这个问题,直接go get 就行
# export PATH=${PATH}:`go env GOPATH`/bin
go get -u honnef.co/go/tools/cmd/staticcheck
staticcheck ./...

- name: Test
run: |
go get -u github.com/smartystreets/goconvey
go test -v ./...

- name: Netlify deploy
env:
NETLIFY_TOKEN: ${{ secrets.NETLIFY_TOKEN }}
run: |
./netlifyctl -A ${NETLIFY_TOKEN} deploy || true
cat netlifyctl-debug.log || true

- name: Build the Docker image
run: |
docker login --username=${{ secrets.DOCKER_USN }} registry.ap-northeast-1.aliyuncs.com --password=${{ secrets.DOCKER_PWD }}
docker build -t beego:latest . #执行构建
docker tag beego registry.ap-northeast-1.aliyuncs.com/example/beego
docker push registry.ap-northeast-1.aliyuncs.com/example/beego:latest # 推送

Actions解析

设置环境变量

现在使用的Go版本基本都在1.13以上,完全可以使用GoProxy,当然相对Github来说并不需要,完全可以不用设置这个环境变量。使用Go Module 来管理依赖,设置GO111MODULE为开启状态。

设置缓存

即使使用Go Module,每次重新下载每个构建的依赖关系是真的浪费。Actions官方提供了一个缓存方式,也比较完备的说明了怎么使用,具体移步这里.

Go Build

使用交叉编译,并设置使用所有内置库静态编译程序,这样主要是为了下一步生成Docker镜像比较小。

静态检查

个人比较倾向于使用Staticcheck进行静态检查,如果同用使用这个就会出现构建失败的情况,国内有很多使用this 或者 self之类的语法糖,这种编译检查时候不会通过。需要去改一下名字。

推送到阿里云

由于阿里云开始商业化,dockerhub与github又是无缝对接,直接吧编译好的文件推送到github上即可。我的仓库中删除了这部分。

代码

上述代码与配置均推送至我的github仓库,可以在这里查看