はじめに
以下の記事で Go で自作したアクセスログパーサーについて紹介しました。
今回はこのモジュールを題材に Go の CI 手法やリリース手法についてざっくり紹介します。以下のような内容です。
- 静的解析
- 脆弱性チェック
- テスト
- CI
- バージョン管理
- リリース
静的解析
Go の数ある Linter/Formatter をひとつに統合したツールである golangci-lint を使います。ツールを個別にインストールする必要がなく、また複数のツールを非同期で実行するため高速です。
インストール
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
設定
設定は golangci.yaml に書きます。TOML や JSON でも動作するようです。設定ファイルなしでもデフォルト設定で動作しますが、より厳密に解析したいので以下のように設定してみました。重複コードを検出する dupl と長いコードを検出する lll はテストコードでは無効にしています。
run:
timeout: 5m
linters:
enable:
- dogsled
- dupl
- errorlint
- gocritic
- gocyclo
- gofmt
- gofumpt
- gosec
- lll
- makezero
- misspell
- nakedret
- predeclared
- revive
- stylecheck
- tagliatelle
- thelper
- tparallel
- unconvert
- unparam
- wastedassign
- whitespace
issues:
exclude-rules:
- linters:
- dupl
- lll
path: _test\.go
使い方
golangci-lint run ./... -v
脆弱性チェック
脆弱性チェックには Go 公式の govulncheck を使います。コマンドラインで Go のソースコードおよびバイナリの脆弱性をチェックできます。
インストール
go install golang.org/x/vuln/cmd/govulncheck@latest
使い方
govulncheck -test ./...
テスト
Go のテストはこんな感じで書いています。使っているデータは S3 の公式ドキュメントで公開されているサンプルをさらにランダム化したものです。
以下のコマンドでユニットテストを実行します。
go test ./... -v -cover -coverprofile=cover.out
-cover: コードカバレッジの計測-coverprofile=cover.out: コードカバレッジの結果を cover.out ファイルに出力
CI
これらの静的解析、脆弱性チェック、テストを CI で走らせます。GitHub Actions を使うので以下のような YAML を書きます。上で紹介したツールもほぼ公式の Action があるようです。
name: CI
on:
push:
branches:
- main
- master
paths-ignore:
- "README.md"
pull_request:
branches:
- main
- master
paths-ignore:
- "README.md"
permissions:
contents: read
jobs:
test:
strategy:
fail-fast: true
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
go: ["1.20", "1.21"]
runs-on: ${{ matrix.os }}
steps:
- name: Set git to use LF
if: "matrix.os == 'windows-latest'"
run: |
git config --global core.autocrlf false
git config --global core.eol lf
- name: Checkout
uses: actions/checkout@v3
- name: Setup go ${{ matrix.go }}
uses: actions/setup-go@v4
with:
go-version: ${{ matrix.go }}
cache: false
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.54
args: --timeout=5m
- name: Run govulncheck
uses: golang/govulncheck-action@v1
with:
go-version-input: ${{ matrix.go }}
go-package: ./...
cache: false
- name: Run tests
run: |
git diff --cached --exit-code
go test ./... -v -cover -coverprofile=cover.out
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
files: ./cover.out
やっていることは以下の通りです。
- main または master ブランチに対するプッシュ、プルリクエストを検知して起動
- ただし変更が README だけの場合は起動しない
- OS が macos, linux, windows かつ Go のバージョンが 1.20, 1.21 の場合の計 6 パターンをテスト
- Windows の場合は git での改行コードの扱いを LF に変更
- Go セットアップ、静的解析、脆弱性チェック、テストを順次実行
- テストカバレッジを Codecov にアップロード
バージョン管理
gobump はソースに埋め込まれたバージョン情報を検出し、セマンティックバージョニングに従ってインクリメントしてくれます。git と併用することで効果を発揮します。
$ gobump up -w .
? Bump up parser.go:
▸ patch (0.0.6 -> 0.0.7)
minor (0.0.6 -> 0.1.0)
major (0.0.6 -> 1.0.0)
リリース
ローカルでのタスクランナーとして Makefile を使い、ツールのインストールやテストコマンドを簡易に実行できるようにします。重要な点として、リリースも make コマンドで実行します。一方で GitHub Actions では Windows も扱うので、YAML に make コマンドを書くことは避けています。
GOBIN ?= $(shell go env GOPATH)/bin
VERSION := $$(make -s show-version)
HAS_LINT := $(shell command -v $(GOBIN)/golangci-lint 2> /dev/null)
HAS_VULNCHECK := $(shell command -v $(GOBIN)/govulncheck 2> /dev/null)
HAS_GOBUMP := $(shell command -v $(GOBIN)/gobump 2> /dev/null)
BIN_LINT := github.com/golangci/golangci-lint/cmd/golangci-lint@latest
BIN_GOVULNCHECK := golang.org/x/vuln/cmd/govulncheck@latest
BIN_GOBUMP := github.com/x-motemen/gobump/cmd/gobump@latest
export GO111MODULE=on
.PHONY: check
check: test cover golangci-lint govulncheck
.PHONY: deps
deps: deps-lint deps-govulncheck deps-gobump
.PHONY: deps-lint
deps-lint:
ifndef HAS_LINT
go install $(BIN_LINT)
endif
.PHONY: deps-govulncheck
deps-govulncheck:
ifndef HAS_VULNCHECK
go install $(BIN_GOVULNCHECK)
endif
.PHONY: deps-gobump
deps-gobump:
ifndef HAS_GOBUMP
go install $(BIN_GOBUMP)
endif
.PHONY: test
test:
go test ./... -v -cover -coverprofile=cover.out
.PHONY: cover
cover:
go tool cover -html=cover.out -o cover.html
.PHONY: golangci-lint
golangci-lint: deps-lint
golangci-lint run ./... -v --tests
.PHONY: govulncheck
govulncheck: deps-govulncheck
$(GOBIN)/govulncheck -test ./...
.PHONY: show-version
show-version: deps-gobump
$(GOBIN)/gobump show -r .
.PHONY: check-git
ifneq ($(shell git status --porcelain),)
$(error git workspace is dirty)
endif
ifneq ($(shell git rev-parse --abbrev-ref HEAD),main)
$(error current branch is not main)
endif
.PHONY: publish
publish: deps-gobump check-git
$(GOBIN)/gobump up -w .
git commit -am "bump up version to $(VERSION)"
git tag "v$(VERSION)"
git push origin main
git push origin "refs/tags/v$(VERSION)"
.PHONY: clean
clean:
go clean
rm -f cover.out cover.html
リリースは make publish で行います。以下では gobump バイナリの存在を確認し、なければインストールします。
HAS_GOBUMP := $(shell command -v $(GOBIN)/gobump 2> /dev/null)
BIN_GOBUMP := github.com/x-motemen/gobump/cmd/gobump@latest
.PHONY: deps-gobump
deps-gobump:
ifndef HAS_GOBUMP
go install $(BIN_GOBUMP)
endif
gobump で現在のバージョンを取得し、変数に入れます。
VERSION := $$(make -s show-version)
.PHONY: show-version
show-version: deps-gobump
$(GOBIN)/gobump show -r .
git リポジトリの状態を確認します。
.PHONY: check-git
ifneq ($(shell git status --porcelain),)
$(error git workspace is dirty)
endif
ifneq ($(shell git rev-parse --abbrev-ref HEAD),main)
$(error current branch is not main)
endif
そして gobump でバージョンを上げ、タグを設定し、push します。
.PHONY: publish
publish: deps-gobump check-git
$(GOBIN)/gobump up -w .
git commit -am "bump up version to $(VERSION)"
git tag "v$(VERSION)"
git push origin main
git push origin "refs/tags/v$(VERSION)"
ここまでで、make publish を叩いてバージョンをインクリメントすると自動的にリリースが走るようになりました。
おわりに
かなりざっくりですが、Go モジュールの CI 手法、リリース手法について紹介しました。自分自身まだ個々のトピックを深掘りできていないので、これから手を動かしながら理解を深めていければと思います。