はじめに
以下の記事で 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 手法、リリース手法について紹介しました。自分自身まだ個々のトピックを深掘りできていないので、これから手を動かしながら理解を深めていければと思います。