在敏捷开发的时代, 快速的编码, code review, 测试, 部署, 是提升程序员效率的关键。 同时在基础工具完备的如今, 我们甚至无需过多的操作就可以轻松实现上述步骤, 本文就以gitlab为例, 分享一下golang项目结合gitlab如何实现自动化CI。

.gitlab-ci.yml

配置gitlab runner

runner可以为某个项目单独配置, 也可以由gitlab管理员预先定义一些公共使用的runner, 后者无需开发人员过多操作, 主要讲解一下前者, 如何单独配置一个runner。首先安装gitlab runner, 参照官方安装教程, 安装好之后需要注册该runner到项目中, 本文使用的是docker作为executer, 也就是用docker执行pipeline, 在setting->pipeline下获取到项目的token和url, 通过gitlab-runner register来注册该runner,
其中DOCKER_IMAGE 是如果未在.gitlab-ci.yml文件中指定image时默认的image。

sudo gitlab-runner register -n -u $CI_SERVER_URL 
 -r $REGISTRATION_TOKEN 
 --description "test-docker-runner" 
 --executor docker 
 --docker-image $DOCKER_IMAGE 
 --docker-privileged

编写ci文件

上述如此就安装并关联好了gitlab-runner, 接下来就是编写.gitlab-ci.yml文件了:

image: cr.registry.name/groupname/project-name-builder

stages:
  - test
  - build
  - release

cache:
  paths:
  - bin

before_script:
  - echo "Git Branch is ${CI_COMMIT_REF_NAME}"

testcase:
  stage: test
  script:
  - echo "begin run test"
  - make test

build_bin:
  stage: build
  script:
  - make

release_bin:
  stage: release
  script:
  - echo "begin release rpm pkg"
  - make push_rpm

image运行pipeline的镜像, 可以直接使用golang这些基础镜像, 也可以是自己写的一个Builder镜像, 本例中笔者使用的是一个自己写好的builder镜像, 因为在后面的ci操作中, 笔者会打包rpm包并上传到文件服务器上以便后续的发布, 这些client都需要在镜像中准备好, 基础镜像无法满足条件, 所有笔者就自己打包了一个镜像。
接下来是stage的定义, stage就是定义ci任务的各个阶段, 本例中是执行测试, 编译, 发布三个阶段(因为go test的实现并不是分析编译后binary文件, 所以可以看到测试在编译之前), 各个stage串行执行, 只有前一阶段执行完毕才会执行后面的阶段, 如果哪个stage失败则整个pipeline失败。下面的testcase, build_bin, release_bin分别是各个stage的具体定义, 称为job, 各个job的script中可以执行任意的命令, 需要注意script中的命令必须在上面指定的image中存在, 笔者的script只是调用了项目中的makefile, makefile中写好了具体执行的操作, 如果一个stage中定义了多个job的话, 他们会并行执行。before_script定义了在各个job的script执行之前执行的操作, 笔者是打印出执行pipeline的branch, 其中CI_COMMIT_REF_NAME是gitlab内置变量, 表示当前的branch, 如果需要使用自己的变量, 可以通过variables来定义, 但是程序中使用到的敏感的token, key等信息, 不要直接定义在variables中, 可以通过setting->pipeline->Secret variables中设置, 使用的时候会自动作为环境变量注入进去。cache定义了各个stage直接可以保存下来供其他stage使用的数据, 笔者这里是项目下的bin文件, 因为在build阶段生成了binary可执行文件之后, 在release阶段就可以直接使用。需要注意的是, cache并不能保证每次都会存在, 所以在release中需要有其他的逻辑保证没找到cache也可以正常的工作。
笔者上述只是三个stage, 在一个成熟的项目中应该包括很多这样的stage, 依次进行lint, unit tests, data race, memory sanitizer, code coverage, build, release。幸运的是对于这些步骤中的大部分go都直接提供了相应的工具来使用, 例如goline, go test等, 其中代码测试覆盖率这块有个小问题需要单独拿出来讲一下。

golang 项目的 test coverage

go test -cover ${package}go tool cover
go test -coverprofile "profile.cov" ${package_name}
go tool cover -func="profile.cov"

但是上面的测试覆盖率只是针对一个package, 对于一个项目中有多个package, 需要依次调用go test -coverprofile产生每个package的profile, 然后将其合并在一起, 再调用go tool cover产生整个项目的coverage例如:

#!/bin/bash
#
# Code coverage generation
COVERAGE_DIR="${COVERAGE_DIR:-coverage}"
PKG_LIST=$(go list ./... | grep -v /vendor/)
# Create the coverage files directory
mkdir -p "$COVERAGE_DIR";
# Create a coverage file for each package
for package in ${PKG_LIST}; do
    go test -covermode=count -coverprofile "${COVERAGE_DIR}/${package##*/}.cov" "$package" ;
done ;
# Merge the coverage profile files
echo 'mode: count' > "${COVERAGE_DIR}"/coverage.cov ;
tail -q -n +2 "${COVERAGE_DIR}"/*.cov >> "${COVERAGE_DIR}"/coverage.cov ;
# Display the global code coverage
go tool cover -func="${COVERAGE_DIR}"/coverage.cov ;
# Remove the coverage files directory
rm -rf "$COVERAGE_DIR";
go test $(go list ./... | grep -v vendor) -v -coverprofile .testCoverage.txtgo test $(go list ./...) -v -coverprofile .testCoverage.txt &&  go tool cover --func=.testCoverage.txt

徽章(Badges)

有了测试覆盖率的数据就可以在项目的首页展示这些数据了, 如下图所示:
badges

在项目的Setting-> pipeline 最下面可以找到对应的Markdown格式的输出, 在首页的ReadMe中添加上即可, 形如

[![Build Status](https://gitlab.com/pantomath-io/demo-tools/badges/master/build.svg)](https://gitlab.com/pantomath-io/demo-tools/commits/master)   
total:s+(statements)s+(d+.d+\%)

gitlab与golang 的其他要点

cc @somebody#issueIDdocker run -v ``pwd``:/project cr.registry.name/groupname/project-name-builder make

reference