GoでCobraとViperを使用したCLIアプリの構築
GoでCobraとViperフレームワークを使用したCLI開発
コマンドラインインターフェース(CLI)アプリケーションは、開発者、システム管理者、DevOpsプロフェッショナルにとって不可欠なツールです。 Go言語でCLIを開発するための2つのライブラリが、CLI開発におけるGoの標準として広く採用されています:コマンド構造にはCobra、設定管理にはViper。
Goは、パフォーマンス、簡単なデプロイ、クロスプラットフォームサポートにより、CLIツールの構築に優れた言語として台頭しています。

CLIアプリケーションにGoを選ぶ理由
GoはCLI開発において魅力的な利点を提供します:
- 単一バイナリ配布:ランタイム依存関係やパッケージマネージャーは不要
- 高速実行:ネイティブコンパイルにより優れたパフォーマンスを実現
- クロスプラットフォームサポート:Linux、macOS、Windowsなどへの簡単なコンパイル
- 強力な標準ライブラリ:ファイルI/O、ネットワーキング、テキスト処理のための豊富なツール
- 並行性:並列操作用の組み込みgoroutine
- 静的型付け:コンパイル時にエラーを検出可能
Goで構築された人気のあるCLIツールには、Docker、Kubernetes(kubectl)、Hugo、Terraform、GitHub CLIがあります。Goに初めて触れるか、クイックリファレンスが必要な場合は、Goチートシートを確認してください。必須のGo構文とパターンが含まれています。
Cobraの紹介
Cobraは、強力な現代的なCLIアプリケーションを作成するためのシンプルなインターフェースを提供するライブラリです。HugoとViperの作者であるSteve Francia(spf13)によって作成されたCobraは、多くの人気のあるGoプロジェクトで使用されています。
Cobraの主な特徴
コマンド構造:Cobraはコマンドパターンを実装しており、git commitやdocker runのようなコマンドとサブコマンドを持つアプリケーションを作成できます。
フラグ処理:ローカルおよび永続フラグの自動パースと型変換。
自動ヘルプ:ヘルプテキストと使用情報が自動生成されます。
インテリジェントな提案:ユーザーがタイプミスをした場合に提案を提供します(例:「“status"を意図しましたか?」)。
シェル補完:bash、zsh、fish、PowerShell用の補完スクリプトを生成。
柔軟な出力:カスタムフォーマッターや出力スタイルとシームレスに動作。
Viperの紹介
Viperは、Cobraとシームレスに動作するGoアプリケーションの完全な設定ソリューションです。複数のソースからの設定を明確な優先順位で処理します。
Viperの主な特徴
複数の設定ソース:
- 設定ファイル(JSON、YAML、TOML、HCL、envfile、Javaプロパティ)
- 環境変数
- コマンドラインフラグ
- リモート設定システム(etcd、Consul)
- デフォルト値
設定の優先順位:
Setへの明示的な呼び出し- コマンドラインフラグ
- 環境変数
- 設定ファイル
- キー/値ストア
- デフォルト値
ライブ監視:設定ファイルを監視し、変更が発生したときに自動的に再読み込みします。
型変換:さまざまなGo型(文字列、整数、ブール、期間など)への自動変換。
はじめに:インストール
まず、新しいGoモジュールを初期化し、両方のライブラリをインストールします:
go mod init myapp
go get -u github.com/spf13/cobra@latest
go get -u github.com/spf13/viper@latest
オプションとして、スケルトン生成のためにCobra CLIジェネレータをインストールします:
go install github.com/spf13/cobra-cli@latest
あなたの最初のCLIアプリケーションの構築
実用的な例を構築しましょう:設定サポート付きのタスク管理CLIツール。
プロジェクト構造
mytasks/
├── cmd/
│ ├── root.go
│ ├── add.go
│ ├── list.go
│ └── complete.go
├── config/
│ └── config.go
├── main.go
└── config.yaml
メインエントリポイント
// main.go
package main
import "mytasks/cmd"
func main() {
cmd.Execute()
}
Viperとの統合を持つルートコマンド
// cmd/root.go
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var cfgFile string
var rootCmd = &cobra.Command{
Use: "mytasks",
Short: "シンプルなタスク管理CLI",
Long: `MyTasksは、簡単にあなたの日常のタスクを整理できるCLIタスクマネージャーです。CobraとViperで構築されています。`,
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "",
"設定ファイル(デフォルトは $HOME/.mytasks.yaml)")
rootCmd.PersistentFlags().String("db", "",
"データベースファイルの場所")
viper.BindPFlag("database", rootCmd.PersistentFlags().Lookup("db"))
}
func initConfig() {
if cfgFile != "" {
viper.SetConfigFile(cfgFile)
} else {
home, err := os.UserHomeDir()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
viper.AddConfigPath(home)
viper.AddConfigPath(".")
viper.SetConfigType("yaml")
viper.SetConfigName(".mytasks")
}
viper.SetEnvPrefix("MYTASKS")
viper.AutomaticEnv()
if err := viper.ReadInConfig(); err == nil {
fmt.Println("使用中の設定ファイル:", viper.ConfigFileUsed())
}
}
サブコマンドの追加
// cmd/add.go
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var priority string
var addCmd = &cobra.Command{
Use: "add [タスクの説明]",
Short: "新しいタスクを追加",
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
task := args[0]
db := viper.GetString("database")
fmt.Printf("タスクを追加中: %s\n", task)
fmt.Printf("優先度: %s\n", priority)
fmt.Printf("データベース: %s\n", db)
// ここに実際のタスク保存を実装します
},
}
func init() {
rootCmd.AddCommand(addCmd)
addCmd.Flags().StringVarP(&priority, "priority", "p", "medium",
"タスクの優先度(low, medium, high)")
}
リストコマンド
// cmd/list.go
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var showCompleted bool
var listCmd = &cobra.Command{
Use: "list",
Short: "すべてのタスクを一覧表示",
Run: func(cmd *cobra.Command, args []string) {
db := viper.GetString("database")
fmt.Printf("タスクを一覧表示中: %s\n", db)
fmt.Printf("完了タスクを表示: %v\n", showCompleted)
// ここに実際のタスク一覧表示を実装します
},
}
func init() {
rootCmd.AddCommand(listCmd)
listCmd.Flags().BoolVarP(&showCompleted, "completed", "c", false,
"完了タスクを表示")
}
データの永続性の実装
本番環境用のタスク管理CLIでは、実際のデータストレージを実装する必要があります。ここでは単純なデータベースパス設定を使用していますが、データを永続化するにはいくつかのオプションがあります:
- SQLite:CLIツールに最適な軽量でサーバーレスなデータベース
- PostgreSQL/MySQL:より複雑なアプリケーションに適したフル機能のデータベース
- JSON/YAMLファイル:軽量なニーズに適したファイルベースのストレージ
- 埋め込みデータベース:BoltDB、BadgerDBなどのキーバリューストレージ
PostgreSQLなどのリレーショナルデータベースを使用する場合は、ORMやクエリビルダを使用することをお勧めします。Goデータベースライブラリの包括的な比較については、PostgreSQL用Go ORMの比較:GORM vs Ent vs Bun vs sqlcガイドをご覧ください。
Viperによる高度な設定
設定ファイルの例
# .mytasks.yaml
database: ~/.mytasks.db
format: table
colors: true
notifications:
enabled: true
sound: true
priorities:
high: red
medium: yellow
low: green
ネストされた設定の読み取り
func getNotificationSettings() {
enabled := viper.GetBool("notifications.enabled")
sound := viper.GetBool("notifications.sound")
fmt.Printf("通知が有効: %v\n", enabled)
fmt.Printf("サウンドが有効: %v\n", sound)
}
func getPriorityColors() map[string]string {
return viper.GetStringMapString("priorities")
}
環境変数
Viperは設定されたプレフィックスを持つ環境変数を自動的に読み込みます:
export MYTASKS_DATABASE=/tmp/tasks.db
export MYTASKS_NOTIFICATIONS_ENABLED=false
mytasks list
ライブ設定の再読み込み
func watchConfig() {
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("設定ファイルが変更されました:", e.Name)
// 設定に依存するコンポーネントを再読み込み
})
}
ベストプラクティス
1. 一貫性のためにCobraジェネレータを使用する
cobra-cli init
cobra-cli add serve
cobra-cli add create
2. コマンドを別ファイルに整理する
cmd/ディレクトリ内に各コマンドを個別のファイルに配置して、保守性を高めましょう。
3. 永続フラグを活用する
すべてのサブコマンドに適用されるオプションには永続フラグを使用します:
rootCmd.PersistentFlags().StringP("output", "o", "text",
"出力形式(text, json, yaml)")
4. 適切なエラーハンドリングを実装する
var rootCmd = &cobra.Command{
Use: "myapp",
Short: "私のアプリケーション",
RunE: func(cmd *cobra.Command, args []string) error {
if err := doSomething(); err != nil {
return fmt.Errorf("何かが失敗しました: %w", err)
}
return nil
},
}
5. 意味のあるヘルプテキストを提供する
var cmd = &cobra.Command{
Use: "deploy [environment]",
Short: "指定された環境にアプリケーションをデプロイ",
Long: `ビルドし、指定された環境にアプリケーションをデプロイします。サポートされている環境は:
- 開発(dev)
- ステージング
- 本番(prod)`,
Example: ` myapp deploy staging
myapp deploy production --version=1.2.3`,
}
6. 適切なデフォルト値を設定する
func init() {
viper.SetDefault("port", 8080)
viper.SetDefault("timeout", "30s")
viper.SetDefault("retries", 3)
}
7. 設定の検証を実装する
func validateConfig() error {
port := viper.GetInt("port")
if port < 1024 || port > 65535 {
return fmt.Errorf("無効なポート: %d", port)
}
return nil
}
CLIアプリケーションのテスト
コマンドのテスト
// cmd/root_test.go
package cmd
import (
"bytes"
"testing"
)
func TestRootCommand(t *testing.T) {
cmd := rootCmd
b := bytes.NewBufferString("")
cmd.SetOut(b)
cmd.SetArgs([]string{"--help"})
if err := cmd.Execute(); err != nil {
t.Fatalf("コマンドの実行に失敗しました: %v", err)
}
}
異なる設定でのテスト
func TestWithConfig(t *testing.T) {
viper.Set("database", "/tmp/test.db")
viper.Set("debug", true)
// テストを実行
viper.Reset() // 清掃
}
シェル補完の生成
Cobraはさまざまなシェル用の補完スクリプトを生成できます:
// cmd/completion.go
package cmd
import (
"os"
"github.com/spf13/cobra"
)
var completionCmd = &cobra.Command{
Use: "completion [bash|zsh|fish|powershell]",
Short: "補完スクリプトの生成",
Long: `補完をロードするには:
Bash:
$ source <(mytasks completion bash)
Zsh:
$ source <(mytasks completion zsh)
Fish:
$ mytasks completion fish | source
PowerShell:
PS> mytasks completion powershell | Out-String | Invoke-Expression
`,
DisableFlagsInUseLine: true,
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
Args: cobra.ExactValidArgs(1),
Run: func(cmd *cobra.Command, args []string) {
switch args[0] {
case "bash":
cmd.Root().GenBashCompletion(os.Stdout)
case "zsh":
cmd.Root().GenZshCompletion(os.Stdout)
case "fish":
cmd.Root().GenFishCompletion(os.Stdout, true)
case "powershell":
cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
}
},
}
func init() {
rootCmd.AddCommand(completionCmd)
}
ビルドと配布
複数プラットフォームへのビルド
# Linux
GOOS=linux GOARCH=amd64 go build -o mytasks-linux-amd64
# macOS
GOOS=darwin GOARCH=amd64 go build -o mytasks-darwin-amd64
GOOS=darwin GOARCH=arm64 go build -o mytasks-darwin-arm64
# Windows
GOOS=windows GOARCH=amd64 go build -o mytasks-windows-amd64.exe
バイナリサイズの削減
go build -ldflags="-s -w" -o mytasks
バージョン情報の追加
// cmd/version.go
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var (
Version = "dev"
Commit = "none"
BuildTime = "unknown"
)
var versionCmd = &cobra.Command{
Use: "version",
Short: "バージョン情報を表示",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("バージョン: %s\n", Version)
fmt.Printf("コミット: %s\n", Commit)
fmt.Printf("ビルド日時: %s\n", BuildTime)
},
}
func init() {
rootCmd.AddCommand(versionCmd)
}
バージョン情報を含めてビルド:
go build -ldflags="-X 'mytasks/cmd.Version=1.0.0' \
-X 'mytasks/cmd.Commit=$(git rev-parse HEAD)' \
-X 'mytasks/cmd.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)'" \
-o mytasks
実際の例
CobraとViperで構築された人気のあるオープンソースツール:
- kubectl:Kubernetesコマンドラインツール
- Hugo:スタティックサイトジェネレータ
- GitHub CLI(gh):GitHub公式CLI
- Docker CLI:コンテナ管理
- Helm:Kubernetesパッケージマネージャ
- Skaffold:Kubernetes開発ワークフロールートール
- Cobra CLI:セルフホスティング - Cobra自身を使用!
伝統的なDevOpsツールにとどまらず、GoのCLI機能はAIや機械学習アプリケーションにも拡張されています。大規模言語モデルとやり取りするCLIツールを構築したい場合は、OllamaとGoを使用した構造化出力によるLLMの制約ガイドをご覧ください。PythonとGoで動作するAIモデルと連携するGoアプリケーションの構築方法が示されています。
有用なリソース
結論
CobraとViperは、GoでプロフェッショナルなCLIアプリケーションを構築するための強力な基盤を提供します。Cobraはコマンド構造、フラグ解析、ヘルプ生成を処理し、Viperは複数のソースからの設定をスマートな優先順位で管理します。
この組み合わせにより、以下のCLIツールを作成できます:
- 直感的なコマンドと自動ヘルプにより使いやすい
- 複数の設定ソースに対応して柔軟
- シェル補完と適切なエラーハンドリングによりプロフェッショナル
- クリーンなコード構成により保守性が高
- 複数のプラットフォームにわたってポータブル
開発者ツール、システムユーティリティ、DevOps自動化を構築する際、CobraとViperはユーザーが愛するCLIアプリケーションを作成するために必要な堅牢な基盤を提供します。