🐧

『GopherのためのCLIツール開発』最新事情 LT で登壇しました

に公開

Findy さん主催のイベント『GopherのためのCLIツール開発』最新事情 LT で 「Go で CLI を開発する Tips 2025」というタイトルで登壇しました。

https://findy.connpass.com/event/362163/

https://suzuki-shunsuke.github.io/slides/go-cli-2025-ja

本記事は登壇内容をブログに書き起こしたものです。

自分はこれまで様々な CLI を Go で作ってきました。

  • aqua - CLI Version Manager
  • tfcmt - terraform plan, apply の結果を GitHub の PR にコメント
  • pinact - GitHub Actions のバージョンを full length SHA で固定
  • github-comment - GitHub PR にコメント
  • tfmv - Terraform の resource や data block をリネームして moved block を生成
  • ghalint - GitHub Actions Linter
  • etc

今回はこの経験を活かし、 CLI の開発に役立つ tips を紹介しました。
一つのことを 5 分かけて話すというより、広く浅く話した感じです。
どれか一つでも参考になる情報があれば幸いです。

aqua の紹介

CLI Version Manager の aqua を使うと CLI とそのバージョンを YAML で宣言的に管理することができます。
今回のイベントの登壇者の方々のツールもサポートしていますし、 Go 関連のツールもサポートしているので Go で開発するうえでも便利です。

aqua.yaml
registries:
  - type: standard
    ref: v4.395.0 # renovate: depName=aquaproj/aqua-registry
packages:
  # 今日の登壇者のツールもサポート
  - name: syumai/sbx@v0.0.5
  - name: fujiwara/lambroll@v1.3.0
  - name: ktr0731/evans@v0.10.11
  - name: mattn/bsky@v0.0.73
  - name: sivchari/ccowl@v1.0.1
  - name: RyuyaIshibashi/aws-s3-siggy@v0.2.2
  # Go 関連のツールもサポート
  - name: mvdan/gofumpt@v0.8.0
  - name: golangci/golangci-lint@v2.2.1
  - name: goreleaser/goreleaser@v2.11.0

ちなみにスライドは marp で作りましたが、 marp も aqua でインストールできます。

ここから様々な tips を紹介します。

  1. 構造化ロガーで version 情報などを含める
  2. よく実装する機能をライブラリで共通化
  3. GitHub Actions で CI の共通化
  4. autofix.ci でコードの自動修正・生成
  5. private repository では Securefix Action が便利
  6. CLI の設定ファイルの JSON Schema をコードから生成
  7. CLI と一緒に Go と Go Module のライセンスも配布
  8. GitHub Artifact Attestations の生成

1. 構造化ロガーで version 情報などを含める

構造化ロガーを使ってログを出力し、ツールのバージョン情報などをログに含めると逐一ユーザーにバージョンを確認したりする必要がなくなり、サポートが楽になります。

$ yq --version
INFO[0000] download and unarchive the package            env=darwin/arm64 exe_name=yq package_name=mikefarah/yq package_version=v4.47.1 program=aqua program_version=2.53.6 registry=standard
yq (https://github.com/mikefarah/yq/) version v4.47.1

2. よく実装する機能をライブラリで共通化

様々な CLI を効率よく開発するため、多くの CLI で共通して必要になる機能・設定はライブラリとして共通化しています。
コピペではなく共通化することで継続的に品質を改善できます。

https://github.com/suzuki-shunsuke/urfave-cli-v3-util

例えば以下のような機能を共通化しています。

  • version コマンド
  • ロガーの初期化
  • bash や zsh などのシェル補完
  • 全 sub command の help をまとめて出力するコマンド (ドキュメント用)
  • GitHub access token を Windows Credential Manager や macOS Keychain, GNOME Keyring で管理するコマンド

3. GitHub Actions で CI の共通化

CI に関しても GitHub Actions の action や Reusable Workflow を使って共通化し、簡単に実装できるようにしています。

自分が OSS を作る際は基本自分以外の人も使えるような汎用な作りを目指しています。
しかし、 2, 3 で紹介したライブラリや action は OSS ではあるものの自分以外の人間が使うことはあまり想定していません。
自分が使うことを前提にある程度コードを決め打ちし、その分簡潔・簡単に使えるようにしています。
メンテする OSS の数が 1, 2 個とかであれば共通化するメリットはそこまでもないかもしれませんが、多くの OSS をメンテするようになると共通化の重要性が増します。

4. autofix.ci でコードの自動修正・生成

autofix.ci を使うと fork からの PR であっても CI でセキュアにコードを修正できます。
OSS では Go に限らず非常におすすめです。

https://autofix.ci/

自分の場合 go-autofix-action という共通の action で以下のようなことをやっています。

  • gofumpt でフォーマット
  • go mod tidy で go.mod, go.sum の更新
  • JSON Schema の生成 (後述)

5. private repository では Securefix Action が便利

autofix.ci は Private repository だと金銭面や会社のポリシー的に多少導入ハードルがあると思います。
Securefix Action という自分が開発する OSS を使うと Private Repository でも autofix.ci 同様セキュアにコードを修正できます。

https://zenn.dev/shunsuke_suzuki/articles/securefix-action

https://zenn.dev/shunsuke_suzuki/articles/securefix-action-v020

6. CLI の設定ファイルの JSON Schema をコードから生成

自分の CLI では設定ファイルに YAML を使うことが多いため、 JSON Schema を提供しています。
JSON Schema によって設定ファイルのバリデーションができますし、 VSCode などのエディタではリアルタイムのバリデーションや入力補完が可能になります。

yaml の先頭に書くと YAML Language Server で validation や入力補完が効くようになって便利
# yaml-language-server: $schema=https://raw.githubusercontent.com/aquaproj/aqua/main/json-schema/aqua-yaml.json

invopop/jsonschema を使って Go のコードから JSON Schema を生成しています。
このライブラリをラップして 3 行で JSON Schema を生成するための薄いライブラリを自作しています。

https://github.com/suzuki-shunsuke/gen-go-jsonschema

if err := jsonschema.Write(&aqua.Config{}, "json-schema/aqua-yaml.json"); err != nil {
	return fmt.Errorf("create or update a JSON Schema: %w", err)
}

7. CLI と一緒に Go と Go Module のライセンスも配布

ライブラリのライセンスにもよりますが、 Go でビルドしたバイナリを公開する場合、原則として依存する Go のライブラリのライセンスも配布しないといけません。
google/go-licenses というツールを使ってライセンスを生成し、バイナリと一緒に tarball や zip にして配布することでライセンス上の義務を果たすことができます。
詳細はブログに書いたのでそちらを参照してください。

https://zenn.dev/shunsuke_suzuki/articles/go-oss-licenses

8. GitHub Artifact Attestations の生成

https://docs.github.com/en/actions/how-tos/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds

Go CLI をリリースする際に GitHub Artiafct Attestations を生成しましょう。
インストール時に Attestation を検証することで、改竄を検知することができ、よりセキュアにインストールすることができます。
Cosignslsa-github-generator など他にも色々ありますが、まずは GitHub Artifact Attestations が簡単なのでおすすめです。
ただし、 Private repository だと GitHub Enterprise Cloud Plan が必要なはずです。
今回は主に OSS を想定しています。
公式の action を実行するだけで attestation を生成することができ、 attestation の検証も GitHub CLI を使って簡単にできます。

生成:

permissions:
  id-token: write
  attestations: write
- name: Generate artifact attestation
  uses: actions/attest-build-provenance@v2
  with:
    subject-path: 'PATH/TO/ARTIFACT'

検証:

gh attestation verify pinact_darwin_arm64.tar.gz \
  -R suzuki-shunsuke/pinact \
  --signer-workflow suzuki-shunsuke/go-release-workflow/.github/workflows/release.yaml

ただ attestation 生成してもユーザーに気づいて使ってもらわないと意味がないのでドキュメントにも検証方法を記載するようにしています。

例: pinact

Attestation の生成・検証は Supply Chain Security において非常に重要であり、 GitHub 公式の機能でもありますがあまり普及しているとは言えません。
そのため、もっと普及してほしいという思いも込めて今回紹介しました。

https://x.com/szkdash/status/1949784633383751741

https://aquaproj.github.io/docs/reference/security/

Discussion