来探讨下git工作流下golang项目的version信息该如何处理比较符合标准
之前自己写的几个简单的小工具都是没有把version信息注入到编译出来的文件里,发布版本多了后也无法溯源了.想着有必要看下这方面的知识了.
观察现有的一些 之前我博客docker panic那次事故就是根据docker info里的containerd的commit id找到了相关的代码来回溯.我们先看看各个项目的version信息
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 $ 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
1 2 3 4 5 $ etcd --version etcd Version: 3.3.13 Git SHA: 98d3084 Go Version: go1.10.8 Go OS/Arch: linux/amd64
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 $ 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都是类似的如下内容
1 2 3 4 5 6 7 8 9 10 11 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注入变量的,例如下面:
1 2 3 4 5 6 7 8 9 10 11 12 $cat >test.go<<EOF package main var version string func main(){ print(version) } EOF $ go build -ldflags '-X main.version=v0.0.12' test.go $ ./test v0.0.12
包名.变量名形式注入到编译过程里,可以覆盖掉原有变量的值,变量首字母可以不用大写也会注入.知道了实现方法,来思考下如何优雅.
市面上很多都是单独整了个version文件,直接从里面cat的.github上不少人这样version或者go文件里写版本号,xxx-dev,测试通过后再改下commit推上去,这样会多一个无用的commit
实际都是master代码测试完美了后发布tag,tag触发release.可以根据tag号做版本号来外部注入.总的来说就是k8s这方面最规范.去看它的源码.
从k8s源码学习 查看了下源码结构找到了核心部分staging/src/k8s.io/component-base/version/version.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func Get () apimachineryversion.Info { 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比较先执行,里面有执行
1 2 3 4 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社区人员看到这个字段值是dirty后不会过多理你来浪费时间
其余几个没啥可说的
个人实现 我简单说下现在我推荐的逻辑判断:
获取项目的目录,也就是上面的KUBE_ROOT设置下命令别名git,让git只对项目目录生效:
1 git=(git --work-tree "${KUBE_ROOT}") #后面使用git命令用:"${git[@]}"
1 BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
1 HEAD=$("${git[@]} " rev-parse "HEAD^{commit}" )
tag名不为空的时候(指定编译之前的tag版本传入tag变量),或者ci是tag触发的.判断下该tag名存在否
1 2 3 4 5 6 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号:
1 2 3 4 else TAG_COMMITID=$("${git[@]} " rev-list --tags --max-count=1) TAG=$("${git[@]} " describe --tags ${TAG_COMMITID} ) fi
1 2 "${git[@]} " checkout $TAG 2>/dev/nullBUILD_VERSION=${TAG}
设置git tree state,没提交代码则是dirty
1 2 3 4 5 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的值
1 2 3 4 5 6 7 if [ "${HEAD} " != "${TAG_COMMITID} " ];then BUILD_VERSION+="-dirty" COMMIT_ID=${HEAD} else COMMIT_ID=${TAG_COMMITID} fi
最后git切回去,因为前面都是取变量的值,不会动代码
1 "${git[@]} " checkout $HEAD 2>/dev/null
版本号信息可以输出到一个gitignore的文件里,编译的时候读取文件里的值拼接成go build的参数.我个人实现可能不是最优,我个人的整个细节性都在我github上https://github.com/zhangguanzhang/gonelist 入口脚本是 https://github.com/zhangguanzhang/gonelist/blob/master/build/build.sh
1 2 3 4 5 6 # 使用master的代码构建 bash build/build.sh build # 自己编译指定tag版本的话 export TAG_NUM=xxx # 如果是tag推送,上面的逻辑处理就是tag的值不用声明,自己编译最新的 tag release 也不用声明变量 bash build/build.sh release
版本信息的打印我是一行一条,方便脚本来做升级比对的正则提取,花样多的话可以像k8s那样支持 yaml 和 json 格式输出啥的
2021/02/02 版本号的部分可以直接这样获取,下面是切到 1.16.0后修改文件,然后提交
1 2 3 4 5 6 7 8 9 10 11 $ git log commit 25625cffd7066dded1157eebef7032bd43ca025e (HEAD) Author: zhangguanzhang <zhangguanzhang@qq.com> Date: Mon Feb 1 22:06:17 2021 -0500 add-dnsredir commit 4e61de426bcf2300e2dee2388094312996454e28 (tag: 1.16.0) $ git describe --tags --always --dirty 1.16.0-1-g25625cf