发布于 

利用Github Actions为Go程序添加git与编译信息

实现效果

可以通过/version获取到当前服务的git版本号以及相关信息,也可以看到编译时间与编译环境等信息。

实现原理

在进行Go程序的编译时,可以通过传入额外的参数来修改编译过程,比较常见的有:

  1. o <output>: 指定输出文件的名称。例如,go build -o myprogram将生成名为 myprogram 的可执行文件。
  2. ldflags "<flags>": 用于传递链接器(ld)的标志。您可以使用此选项将特定标志传递给链接器,例如设置版本信息或自定义链接行为。例如,go build -ldflags "-X main.version=1.0.0"将在构建过程中设置名为 version 的变量的值为 1.0.0
  3. gcflags "<flags>": 用于传递Go编译器(gc)的标志。您可以使用此选项传递特定标志给编译器,例如优化级别或其他调试信息。例如,go build -gcflags "-N -l" 将禁用优化并生成带有调试信息的可执行文件。

本次的实现则是利用ldflags "<flags>"指令进行信息的注入。

核心构建指令

go build -o ./temp/release/linux_amd64/service -ldflags "     -X 'home-network-watcher/utility/bin_utils.GitTag=v1.11.12'     -X 'home-network-watcher/utility/bin_utils.GitCommitLog=8405a9a7a15c33399d8cc400f08836e14b9dc085'     -X 'home-network-watcher/utility/bin_utils.BuildTime=2023.05.21.03:56:22'     -X 'home-network-watcher/utility/bin_utils.BuildGoVersion=github@action golang:1.20-buster' "  main.go

接下来进行Go代码与GitHub Actions、Dockerfile的编写。

Go美化注入代码

新建一个.Go文件,以变量的形式用以储存在程序中需要的git与版本信息,并创建一个函数用以美化显示信息。

创建需要注入信息的变量,设置默认值为unknown,等待编译注入再进行重新赋值。

package binInfo

import (
"fmt"
"runtime"
"strings"
)

// 初始化为 unknown,如果编译时没有传入这些值,则为 unknown
var (
GitTag = "unknown"
GitCommitLog = "unknown"
GitStatus = "unknown"
BuildTime = "unknown"
BuildGoVersion = "unknown"
)

var (
VersionString = "GitTag:" + GitTag + "\n" +
"GitCommitLog:" + GitCommitLog + "\n" +
"GitStatus:" + GitStatus + "\n" +
"BuildTime:" + BuildTime + "\n" +
"BuildGoVersion:" + BuildGoVersion + "\n"
)

/*// StringifySingleLine 返回单行格式
func StringifySingleLine() string {
return fmt.Sprintf("GitTag=%s. GitCommitLog=%s. GitStatus=%s. BuildTime=%s. GoVersion=%s. runtime=%s/%s.",
GitTag, GitCommitLog, GitStatus, BuildTime, BuildGoVersion, runtime.GOOS, runtime.GOARCH)
}*/

// StringifyMultiLine 返回多行格式
func StringifyMultiLine() string {
return fmt.Sprintf("GitTag=%s\nGitCommitLog=%s\nGitStatus=%s\nBuildTime=%s\nGoVersion=%s\nruntime=%s/%s\n",
GitTag, GitCommitLog, GitStatus, BuildTime, BuildGoVersion, runtime.GOOS, runtime.GOARCH)
}

func init() {
beauty()
}

// 对一些值做美化处理
func beauty() {
if GitStatus == "" {
// GitStatus 为空时,说明本地源码与最近的 commit 记录一致,无修改
// 为它赋一个特殊值
GitStatus = "cleanly"
} else {
// 将多行结果合并为一行
GitStatus = strings.Replace(strings.Replace(GitStatus, "\r\n", " |", -1), "\n", " |", -1)
}
}

在Go项目中显示git与编译信息

可在main.go中设置命令行参数进行信息显示。

如下代码,在运行编译后的二进制文件时,通过-v进行信息显示。

package main

import (
"flag"
"fmt"
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
"github.com/gogf/gf/v2/os/gctx"
"home-network-watcher/internal/cmd"
_ "home-network-watcher/internal/logic"
_ "home-network-watcher/internal/packed"
binInfo "home-network-watcher/utility/bin_utils"
"os"
)

func main() {
v := flag.Bool("v", false, "Show bin info.")
flag.Parse()
if *v {
_, _ = fmt.Fprint(os.Stderr, binInfo.StringifyMultiLine())
os.Exit(1)
}
cmd.Main.Run(gctx.New())
}

或者可以通过绑定接口的形式,在访问服务/version 时进行信息的显示。

group.ALL("/version", func(r *ghttp.Request) {
r.Response.Write(binInfo.VersionString)
})

Dockerfile编写

首先定义Go构建器

FROM golang:1.20-buster AS builder

设置需要注入的构建参数的默认值

ARG GIT_TAG="unknown"
ARG GIT_COMMIT_LOG="unknown"
ARG BUILD_TIME="unknown"
ARG BUILD_GO_VERSION="github@action golang:1.20-buster"

关键的注入则是通过在docker进行构建时也可以进行参数的绑定这个特性来完成的。

GitHub Action将ARG的参数传入docker build后,接下来则将信息传入到gf build或者go build中,首先构建传入的信息。

# 设置 LDFlags 变量
ENV LDFLAGS=" \
-X 'home-network-watcher/utility/bin_utils.GitTag=${GIT_TAG}' \
-X 'home-network-watcher/utility/bin_utils.GitCommitLog=${GIT_COMMIT_LOG}' \
-X 'home-network-watcher/utility/bin_utils.BuildTime=${BUILD_TIME}' \
-X 'home-network-watcher/utility/bin_utils.BuildGoVersion=${BUILD_GO_VERSION}' \
"

'home-network-watcher/utility/bin_utils.GitTag=${GIT_TAG}' 为例

go通过追溯包名进行参数数据的传递,在上述定义存储变量的Go文件中,模块为"home-network-watcher/utility/bin_utils” ,则在构建时也是传入包名加内部的变量名进行数据传递,在这里我们将上面获取到的GIT_TAG传递给GitTag变量。

最后则是通过go build传递进去

RUN go build -ldflags "${LDFLAGS}" -o temp/release/linux_amd64/service main.go

以下为完整Dockerfile

FROM golang:1.20-buster AS builder

# 设置构建参数的默认值
ARG GIT_TAG="unknown"
ARG GIT_COMMIT_LOG="unknown"
ARG BUILD_TIME="unknown"
ARG BUILD_GO_VERSION="github@action golang:1.20-buster"

WORKDIR /go/src/app
COPY . .

# 打印构建参数
RUN echo "GIT_TAG=${GIT_TAG}"
RUN echo "GIT_COMMIT_LOG=${GIT_COMMIT_LOG}"
RUN echo "BUILD_TIME=${BUILD_TIME}"
RUN echo "BUILD_GO_VERSION=${BUILD_GO_VERSION}"

# 设置 LDFlags 变量
ENV LDFLAGS=" \
-X 'home-network-watcher/utility/bin_utils.GitTag=${GIT_TAG}' \
-X 'home-network-watcher/utility/bin_utils.GitCommitLog=${GIT_COMMIT_LOG}' \
-X 'home-network-watcher/utility/bin_utils.BuildTime=${BUILD_TIME}' \
-X 'home-network-watcher/utility/bin_utils.BuildGoVersion=${BUILD_GO_VERSION}' \
"

RUN go get github.com/gogf/gf/cmd/gf/v2
RUN go install github.com/gogf/gf/cmd/gf/v2
RUN gf build -e "-ldflags \"${LDFLAGS}\" "
# RUN go build -ldflags "${LDFLAGS}" -o temp/release/linux_amd64/service main.go

FROM loads/alpine:3.8

LABEL maintainer="Hamster <liaolaixin@gmail.com>"

###############################################################################
# INSTALLATION
###############################################################################

# 设置固定的项目路径
ENV WORKDIR /app/main
COPY --from=builder /go/src/app/temp/release/linux_amd64/service $WORKDIR/service
# 添加应用可执行文件,并设置执行权限
RUN chmod +x $WORKDIR/service
# 增加端口绑定
EXPOSE 10401

###############################################################################
# START
###############################################################################
WORKDIR $WORKDIR
CMD ["./service"]

Github Actions编写

十分简单,只需要利用Actions里面可以方便获取的git信息即可,通过—build-arg传递入docker build中

name: Build and push Docker image

on:
push:
tags:
- 'v*'

env:
IMAGE_NAME: ${{ secrets.DOCKER_IMAGE_NAME }}

jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Get version
id: get_version
run: echo "CURRENT_VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV

- name: Get Git Commit Log
id: git-commit-log
run: echo "GIT_COMMIT_LOG=${{ github.sha }}" >> $GITHUB_ENV

- name: Get Build Time
id: build-time
run: echo "BUILD_TIME=$(date +'%Y.%m.%d.%H:%M:%S')" >> $GITHUB_ENV

- name: Checkout code
uses: actions/checkout@v3

- name: Login to Dockerhub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}

- name: Build Docker image
run: docker build -t $IMAGE_NAME:${{ github.sha }} . --build-arg GIT_TAG=${{env.CURRENT_VERSION}} --build-arg GIT_COMMIT_LOG="${{env.GIT_COMMIT_LOG}}" --build-arg BUILD_TIME=${{env.BUILD_TIME}}

- name: Print environment variables
run: |
echo "${{env.GIT_COMMIT_LOG}}"
echo "${{env.BUILD_TIME}}"
echo "${{env.CURRENT_VERSION}}"

- name: Tag Docker image
run: docker tag $IMAGE_NAME:${{ github.sha }} $IMAGE_NAME:${{ env.CURRENT_VERSION }}

- name: Tag Docker image as latest
run: docker tag $IMAGE_NAME:${{ github.sha }} $IMAGE_NAME:latest

- name: Push Docker image
run: |
docker push $IMAGE_NAME:${{ env.CURRENT_VERSION }}
docker push $IMAGE_NAME:latest

将获取到的信息首先储存在$GITHUB_ENV中,在构建docker build指令时进行传递

docker build -t $IMAGE_NAME:${{ github.sha }} . --build-arg GIT_TAG=${{env.CURRENT_VERSION}} --build-arg GIT_COMMIT_LOG="${{env.GIT_COMMIT_LOG}}" --build-arg BUILD_TIME=${{env.BUILD_TIME}}

这样子便完成了git与版本信息的注入,十分的自动便捷。

结语

主要的难点还是如何将信息在整个构建的过程中传递下去,只要可以传递好所需的信息在构建的时候即可以方便地通过go build的-ldflags将信息传递入二进制文件中。


本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

@Hamster