git工作流下golang项目的version信息该如何处理
之前自己写的几个简单的小工具都是没有把version信息注入到编译出来的文件里,发布版本多了后也无法溯源了.想着有必要看下这方面的知识了.
观察现有的一些
之前我博客docker panic那次事故就是根据docker info里的containerd的commit id找到了相关的代码来回溯.我们先看看各个项目的version信息
-
docker:
$ docker version Client: Docker Engine - Community Version: 19.03.2 API version: 1.40 Go version: go1.12.8 Git commit: 6a30dfc Built: Thu Aug 29 05:28:55 2019 OS/Arch: linux/amd64 Experimental: false Server: Docker Engine - Community Engine: Version: 19.03.2 API version: 1.40 (minimum version 1.12) Go version: go1.12.8 Git commit: 6a30dfc Built: Thu Aug 29 05:27:34 2019 OS/Arch: linux/amd64 Experimental: false containerd: Version: 1.2.10 GitCommit: b34a5c8af56e510852c35414db4c1f4fa6172339 runc: Version: 1.0.0-rc8+dev GitCommit: 3e425f80a8c931f88e6d94a8c831b9d5aa481657 docker-init: Version: 0.18.0 GitCommit: fec3683
-
etcd:
$ etcd --version etcd Version: 3.3.13 Git SHA: 98d3084 Go Version: go1.10.8 Go OS/Arch: linux/amd64
-
k8s:
$ kubectl version -o json { "clientVersion": { "major": "1", "minor": "13", "gitVersion": "v1.13.12", "gitCommit": "a8b52209ee172232b6db7a6e0ce2adc77458829f", "gitTreeState": "clean", "buildDate": "2019-10-15T12:12:15Z", "goVersion": "go1.11.13", "compiler": "gc", "platform": "linux/amd64" }, "serverVersion": { "major": "1", "minor": "13", "gitVersion": "v1.13.12", "gitCommit": "a8b52209ee172232b6db7a6e0ce2adc77458829f", "gitTreeState": "clean", "buildDate": "2019-10-15T12:04:30Z", "goVersion": "go1.11.13", "compiler": "gc", "platform": "linux/amd64" } }
如何注入
对比下都有如下信息:
- version
- go version
- arch info and os
- git commit id
- build date
github上一些star比较多的项目的 makefile
都是类似的如下内容
BUILD_VERSION := $(shell cat version) BUILD_TIME := $(shell date "+%F %T") COMMIT_SHA1 := $(shell git rev-parse HEAD) all: gox -osarch="darwin/amd64 linux/386 linux/amd64" \ -output="dist/{{.Dir}}_{{.OS}}_{{.Arch}}" \ -tags="containers_image_openpgp" \ -ldflags "-X 'github.com/xxxx/xxxxx/cmd.version=${BUILD_VERSION}' \ -X 'github.com/xxxx/xxxx/cmd.buildTime=${BUILD_TIME}' \ -X 'github.com/xxxx/xxxx/cmd.commit=${COMMIT_SHA1}'"
搜索了下资料是通过 go build -ldflags
注入变量的,例如下面:
cat>test.go<<EOF package main var a string func main(){ print(a) } EOF go build -ldflags '-X main.a=tttt' test.go ./test # 下面是输出 tttt
包名.变量名
形式注入到编译过程里,可以覆盖掉原有变量的值,变量首字母可以不用大写也会注入.知道了实现方法,来思考下如何优雅.
市面上很多都是单独整了个version文件,直接从里面cat的.实际都是master代码测试完美了后发布tag,tag触发release.可以根据tag号做版本号来外部注入.总的来说就是k8s这方面最规范.去看它的源码.
从k8s源码学习
查看了下源码结构找到了核心部分 staging/src/k8s.io/component-base/version/version.go
func Get() apimachineryversion.Info { // These variables typically come from -ldflags settings and in // their absence fallback to the settings in ./base.go return apimachineryversion.Info{ Major: gitMajor, Minor: gitMinor, GitVersion: gitVersion, GitCommit: gitCommit, GitTreeState: gitTreeState, BuildDate: buildDate, GoVersion: runtime.Version(), Compiler: runtime.Compiler, Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), } }
以及文件 staging/src/k8s.io/component-base/version/base.go
里等待被注入的变量.查看了下makefile的逻辑,复杂的逻辑和变量处理是仍shell脚本里处理的,因为makefile并不是一个功能强的脚本语言.
脚本 hack/lib/init.sh
比较先执行,里面有执行
# The root of the build/dist directory KUBE_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd -P)" ... source "${KUBE_ROOT}/hack/lib/version.sh"
version脚本里逻辑可能对于非运维看比较麻烦,简单说下上面version信息:
-
Major
就是大版本号,例如1.18.2
就是1
-
Minor
就是小版本号,例如1.18.2
就是18
-
GitVersion
就是release的版本,如果你是master代码下载编译则是最新的$release-dirty
字样 -
GitCommit
是取编译时候的git commit id -
GitTreeState
是你代码有没有commit,修改了代码没有commit则是dirty,你提issue社区人员看到后不会过多理你来浪费时间 - 其余几个没啥可说的
个人实现
我简单说下现在我推荐的逻辑判断:
-
获取项目的目录,也就是上面的
KUBE_ROOT
设置下命令别名git,让git只对项目目录生效:git=(git --work-tree "${KUBE_ROOT}") #后面使用git命令用:"${git[@]}"
-
获取构建时间
BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
-
获取当前HEAD的commit id
HEAD=$("${git[@]}" rev-parse "HEAD^{commit}")
-
tag名不为空的时候(指定编译之前的tag版本传入tag变量),或者ci是tag触发的.判断下该tag名存在否
if [ -n "$TAG" ]; then TAG_COMMITID=$("${git[@]}" rev-parse $TAG 2>/dev/null) if [ "$?" -ne 0 ];then echo no such tag: $TAG exit 1 fi
-
否则tag为空下也就是master的代码,获取下离master最近的也就是最新的的tag号:
else #默认取最新的tag TAG_COMMITID=$("${git[@]}" rev-list --tags --max-count=1) TAG=$("${git[@]}" describe --tags ${TAG_COMMITID}) fi
-
取tag号
"${git[@]}" checkout $TAG 2>/dev/null BUILD_VERSION=${TAG}
-
设置git tree state,没提交代码则是dirty
if [ -z "$("${git[@]}" status --porcelain 2>/dev/null)" ];then GIT_TREE_STATE='clean' else GIT_TREE_STATE='dirty' fi
-
对比tag的commit id是否和head一致,这步是针对master的ci触发的version设置为上一个
$tag-dirty
的值if [ "${HEAD}" != "${TAG_COMMITID}" ];then #tag的基础上改动,所以tag版本号-dirty BUILD_VERSION+="-dirty" COMMIT_ID=${HEAD} else COMMIT_ID=${TAG_COMMITID} fi
-
最后git切回去,因为前面都是取变量的值,不会动代码
"${git[@]}" checkout $HEAD 2>/dev/null
整个细节性都在我github上
https://github.com/zhangguanzhang/gonelist
入口脚本是 https://github.com/zhangguanzhang/gonelist/blob/master/build/build.sh
# 使用master的代码构建 bash build/build.sh build # 自己编译指定tag版本的话 export TAG_NUM=xxx # 如果是tag推送,上面的逻辑处理就是tag的值不用声明,自己编译最新的tag release也不用声明变量 bash build/build.sh release