golang-cli-cobra-viper について
Go でコマンドラインアプリを作るための開発スキルです。Cobra によるコマンド構成と Viper による設定管理を、補完やテストと合わせて扱います。
主な機能
- 01Cobra のサブコマンドとフラグ
- 02Viper による複数ソースの設定
- 03Cobra と Viper の連携
- 04シェル補完の生成
- 05CLI のテストとエラー処理
活用シーン
- 01複数コマンドを持つ Go CLI の構築
- 02フラグ、環境変数、設定ファイルの統合
- 03開発ツールへのシェル補完の追加
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ bobmatnyc/ claude-mpm-skills --skill golang-cli-cobra-viperインストール後、Claude Code で/golang-cli-cobra-viperを入力して読み込まれたか確認します。
同梱ファイルmetadata.json
詳細
Cobra と Viper による Go CLI 開発
概要
Cobra と Viper は、本番品質の Go CLI を構築するための業界標準ライブラリです。Cobra はコマンド構造と引数解析を提供し、Viper は明確な優先順位ルールに基づいて複数のソースから設定を管理します。
主な機能:
- 🎯 Cobra コマンド:サブコマンドに対応した POSIX 準拠の CLI(
app verb noun --flag) - ⚙️ Viper 設定:フラグ、環境変数、設定ファイルからの統一された設定
- 🔄 連携:Cobra と Viper をシームレスに接続するパターン
- 🐚 シェル補完:bash、zsh、fish、PowerShell 向けに自動生成される補完機能
- ✅ 本番対応:kubectl、docker、gh、hugo で実績のあるライブラリです
利用例:Kubernetes(kubectl)、Docker CLI、GitHub CLI(gh)、Hugo、Helm、および 100 以上の主要プロジェクト
このスキルを使用するタイミング
次のような場合に、このスキルを有効にします。
- サブコマンドを備えた複数コマンドの CLI ツールを構築する場合
- 開発者向けツール、プロジェクトジェネレーター、またはスキャフォールディングユーティリティを作成する場合
- サービスやインフラストラクチャ向けの管理用 CLI を実装する場合
- 柔軟な設定(フラグ > 環境変数 > 設定ファイル > デフォルト値)が必要な場合
- 頻繁に使う CLI にシェル補完を追加する場合
- DevOps 自動化ツールやデプロイスクリプトを構築する場合
Cobra フレームワーク
コマンド構造のパターン
Cobra は、git や kubectl で広く使われるようになった APPNAME VERB NOUN --FLAG パターンに従います。
// cmd/root.go
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var cfgFile string
var rootCmd = &cobra.Command{
Use: "myapp",
Short: "A powerful CLI tool for developers",
Long: `MyApp is a CLI tool that demonstrates best practices
for building production-quality command-line applications.
Complete documentation is available at https://myapp.example.com`,
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func init() {
cobra.OnInitialize(initConfig)
// Persistent flags (available to all subcommands)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.myapp.yaml)")
rootCmd.PersistentFlags().Bool("verbose", false, "verbose output")
// Bind persistent flags to viper
viper.BindPFlag("config", rootCmd.PersistentFlags().Lookup("config"))
viper.BindPFlag("verbose", rootCmd.PersistentFlags().Lookup("verbose"))
}
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(".myapp")
}
viper.SetEnvPrefix("MYAPP")
viper.AutomaticEnv()
if err := viper.ReadInConfig(); err == nil {
if viper.GetBool("verbose") {
fmt.Println("Using config file:", viper.ConfigFileUsed())
}
}
}
引数を持つサブコマンド
// cmd/deploy.go
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var deployCmd = &cobra.Command{
Use: "deploy [environment]",
Short: "Deploy application to specified environment",
Long: `Deploy the application to the specified environment.
Supports: dev, staging, production`,
Args: cobra.ExactArgs(1),
ValidArgs: []string{"dev", "staging", "production"},
PreRunE: func(cmd *cobra.Command, args []string) error {
// Validation logic runs before RunE
env := args[0]
if env == "production" && !viper.GetBool("force") {
return fmt.Errorf("production deploys require --force flag")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
env := args[0]
region := viper.GetString("region")
force := viper.GetBool("force")
fmt.Printf("Deploying to %s in region %s (force=%v)\n", env, region, force)
// Actual deployment logic
return deploy(env, region, force)
},
PostRunE: func(cmd *cobra.Command, args []string) error {
// Cleanup or notifications
fmt.Println("Deployment complete")
return nil
},
}
func init() {
rootCmd.AddCommand(deployCmd)
// Local flags (only for this command)
deployCmd.Flags().StringP("region", "r", "us-east-1", "AWS region")
deployCmd.Flags().BoolP("force", "f", false, "Force deployment without confirmation")
// Bind flags to viper
viper.BindPFlag("region", deployCmd.Flags().Lookup("region"))
viper.BindPFlag("force", deployCmd.Flags().Lookup("force"))
}
func deploy(env, region string, force bool) error {
// Implementation
return nil
}
永続フラグとローカルフラグ
// Persistent flags: Available to command and all subcommands
rootCmd.PersistentFlags().String("config", "", "config file path")
rootCmd.PersistentFlags().Bool("verbose", false, "verbose output")
// Local flags: Only available to this specific command
deployCmd.Flags().String("region", "us-east-1", "deployment region")
deployCmd.Flags().Bool("force", false, "force deployment")
// Required flags
deployCmd.MarkFlagRequired("region")
// Flag dependencies
deployCmd.MarkFlagsRequiredTogether("username", "password")
deployCmd.MarkFlagsMutuallyExclusive("json", "yaml")
PreRun/PostRun フック
Cobra は、セットアップとクリーンアップ用の実行フックを提供します。
var serverCmd = &cobra.Command{
Use: "server",
Short: "Start API server",
// Execution order (all optional):
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
// Runs before PreRunE, inherited by subcommands
return setupLogging()
},
PreRunE: func(cmd *cobra.Command, args []string) error {
// Validation and setup before RunE
return validateConfig()
},
RunE: func(cmd *cobra.Command, args []string) error {
// Main command logic
return startServer()
},
PostRunE: func(cmd *cobra.Command, args []string) error {
// Cleanup after RunE
return cleanup()
},
PersistentPostRunE: func(cmd *cobra.Command, args []string) error {
// Runs after PostRunE, inherited by subcommands
return flushLogs()
},
}
重要:Run、PreRun、PostRun の代わりに、エラーを返すバージョンである RunE、PreRunE、PostRunE を使用してください。
Viper による設定管理
設定の優先順位
Viper では、次の厳密な優先順位に従います(高い順)。
- 明示的な設定(
viper.Set("key", value)) - コマンドラインフラグ(
viper.BindPFlagでバインド) - 環境変数(
MYAPP_KEY=value) - 設定ファイル(
~/.myapp.yaml、./config.yaml) - キー/値ストア(etcd、Consul — オプション)
- デフォルト値(
viper.SetDefault("key", value))
func initConfig() {
// 1. Set defaults (lowest priority)
viper.SetDefault("port", 8080)
viper.SetDefault("database.host", "localhost")
viper.SetDefault("database.port", 5432)
// 2. Config file locations (checked in order)
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath("/etc/myapp/")
viper.AddConfigPath("$HOME/.myapp")
viper.AddConfigPath(".")
// 3. Environment variables (prefix + automatic mapping)
viper.SetEnvPrefix("MYAPP")
viper.AutomaticEnv() // MYAPP_PORT, MYAPP_DATABASE_HOST, etc.
// 4. Read config file (optional)
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// Config file not found - use defaults and env vars
} else {
// Config file found but error reading it
return err
}
}
// 5. Flags will be bound in init() functions (highest priority)
}
環境変数のマッピング
Viper は、接頭辞とドット表記を使用して環境変数を自動的にマッピングします。
viper.SetEnvPrefix("MYAPP") // Prefix for env vars
viper.AutomaticEnv() // Enable automatic mapping
// Config key → Environment variable
// "port" → MYAPP_PORT
// "database.host" → MYAPP_DATABASE_HOST
// "database.port" → MYAPP_DATABASE_PORT
// "aws.s3.region" → MYAPP_AWS_S3_REGION
標準的でない環境変数名を使用する場合は、手動でマッピングします。
viper.BindEnv("database.host", "DB_HOST") // Custom env var name
viper.BindEnv("database.password", "DB_PASSWORD") // Different naming convention
設定ファイルの形式
Viper は、YAML、JSON、TOML、HCL、INI、envfile、Java properties など、複数の形式に対応しています。
config.yaml:
port: 8080
log_level: info
database:
host: localhost
port: 5432
user: postgres
ssl_mode: require
aws:
region: us-east-1
s3:
bucket: my-app-bucket
設定値へのアクセス:
port := viper.GetInt("port") // 8080
dbHost := viper.GetString("database.host") // "localhost"
s3Bucket := viper.GetString("aws.s3.bucket") // "my-app-bucket"
// Type-safe access
if viper.IsSet("database.ssl_mode") {
sslMode := viper.GetString("database.ssl_mode")
}
// Unmarshal into struct
type Config struct {
Port int `mapstructure:"port"`
LogLevel string `mapstructure:"log_level"`
Database struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
User string `mapstructure:"user"`
SSLMode string `mapstructure:"ssl_mode"`
} `mapstructure:"database"`
}
var config Config
if err := viper.Unmarshal(&config); err != nil {
return err
}
Cobra と Viper の連携
重要な連携パターン
Cobra と Viper を連携させる鍵は、フラグを Viper のキーにバインドすることです。
// cmd/root.go
func init() {
cobra.OnInitialize(initConfig) // Load config before command execution
// Define flags
rootCmd.PersistentFlags().String("config", "", "config file")
rootCmd.PersistentFlags().String("log-level", "info", "log level")
rootCmd.PersistentFlags().Int("port", 8080, "server port")
// Bind flags to Viper (critical step!)
viper.BindPFlag("config", rootCmd.PersistentFlags().Lookup("config"))
viper.BindPFlag("log_level", rootCmd.PersistentFlags().Lookup("log-level"))
viper.BindPFlag("port", rootCmd.PersistentFlags().Lookup("port"))
}
func initConfig() {
// This runs BEFORE command execution via cobra.OnInitialize
if cfgFile := viper.GetString("config"); cfgFile != "" {
viper.SetConfigFile(cfgFile)
} else {
viper.AddConfigPath("$HOME/.myapp")
viper.AddConfigPath(".")
viper.SetConfigName("config")
}
viper.SetEnvPrefix("MYAPP")
viper.AutomaticEnv()
viper.ReadInConfig() // Ignore errors - config file is optional
}
フラグのバインド方法:
// Strategy 1: Bind each flag individually (explicit)
viper.BindPFlag("log_level", rootCmd.Flags().Lookup("log-level"))
// Strategy 2: Bind all flags automatically (convenient)
viper.BindPFlags(rootCmd.Flags())
// Strategy 3: Hybrid approach (recommended)
// - Bind persistent flags globally
// - Bind local flags in each command's init()
rootCmd.PersistentFlags().String("config", "", "config file")
viper.BindPFlags(rootCmd.PersistentFlags())
deployCmd.Flags().String("region", "us-east-1", "AWS region")
viper.BindPFlag("deploy.region", deployCmd.Flags().Lookup("region"))
設定読み込みのための PersistentPreRun
PersistentPreRunE を使用して、設定を読み込んで検証します。
var rootCmd = &cobra.Command{
Use: "myapp",
Short: "My application",
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
// Runs before ALL commands (inherited by subcommands)
// 1. Validate required config
if !viper.IsSet("api_key") {
return fmt.Errorf("API key not configured (set MYAPP_API_KEY or add to config file)")
}
// 2. Setup logging based on config
logLevel := viper.GetString("log_level")
if err := setupLogging(logLevel); err != nil {
return fmt.Errorf("invalid log level: %w", err)
}
// 3. Initialize clients/services
apiKey := viper.GetString("api_key")
if err := initAPIClient(apiKey); err != nil {
return fmt.Errorf("failed to initialize API client: %w", err)
}
return nil
},
}
シェル補完
Cobra は、bash、zsh、fish、PowerShell 向けのシェル補完スクリプトを生成します。
補完コマンドの追加
// cmd/completion.go
package cmd
import (
"os"
"github.com/spf13/cobra"
)
var completionCmd = &cobra.Command{
Use: "completion [bash|zsh|fish|powershell]",
Short: "Generate shell completion script",
Long: `Generate shell completion script for myapp.
To load completions:
Bash:
$ source <(myapp completion bash)
# To load automatically, add to ~/.bashrc:
$ echo 'source <(myapp completion bash)' >> ~/.bashrc
Zsh:
$ source <(myapp completion zsh)
# To load automatically, add to ~/.zshrc:
$ echo 'source <(myapp completion zsh)' >> ~/.zshrc
Fish:
$ myapp completion fish | source
# To load automatically:
$ myapp completion fish > ~/.config/fish/completions/myapp.fish
PowerShell:
PS> myapp completion powershell | Out-String | Invoke-Expression
# To load automatically, add to PowerShell profile.
`,
DisableFlagsInUseLine: true,
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
Args: cobra.ExactValidArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
switch args[0] {
case "bash":
return cmd.Root().GenBashCompletion(os.Stdout)
case "zsh":
return cmd.Root().GenZshCompletion(os.Stdout)
case "fish":
return cmd.Root().GenFishCompletion(os.Stdout, true)
case "powershell":
return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
}
return nil
},
}
func init() {
rootCmd.AddCommand(completionCmd)
}
カスタム補完関数
コマンド引数に対して動的な補完を提供します。
var deployCmd = &cobra.Command{
Use: "deploy [environment]",
Short: "Deploy to environment",
Args: cobra.ExactArgs(1),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
// Return available environments
envs := []string{"dev", "staging", "production"}
return envs, cobra.ShellCompDirectiveNoFileComp
},
RunE: func(cmd *cobra.Command, args []string) error {
return deploy(args[0])
},
}
// Custom flag completion
deployCmd.RegisterFlagCompletionFunc("region", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
regions := []string{"us-east-1", "us-west-2", "eu-west-1"}
return regions, cobra.ShellCompDirectiveNoFileComp
})
CLI のベストプラクティス
ユーザーフレンドリーなエラーメッセージ
// ❌ BAD: Technical jargon
return fmt.Errorf("db connection failed: EOF")
// ✅ GOOD: Actionable error message
return fmt.Errorf("cannot connect to database at %s:%d\nPlease check:\n - Database is running\n - Credentials are correct (MYAPP_DB_PASSWORD)\n - Network connectivity", host, port)
// ✅ GOOD: Suggest remediation
if !viper.IsSet("api_key") {
return fmt.Errorf("API key not configured\nSet environment variable: export MYAPP_API_KEY=your-key\nOr add to config file: ~/.myapp.yaml")
}
進捗インジケーター
import "github.com/briandowns/spinner"
func deploy(env string) error {
s := spinner.New(spinner.CharSets[11], 100*time.Millisecond)
s.Suffix = " Deploying to " + env + "..."
s.Start()
defer s.Stop()
// Deployment logic
if err := performDeployment(env); err != nil {
s.Stop()
return err
}
s.Stop()
fmt.Println("✓ Deployment successful")
return nil
}
出力形式
import (
"encoding/json"
"github.com/olekukonko/tablewriter"
)
func displayResults(items []Item, format string) error {
switch format {
case "json":
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(items)
case "table":
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"ID", "Name", "Status"})
for _, item := range items {
table.Append([]string{item.ID, item.Name, item.Status})
}
table.Render()
return nil
default:
return fmt.Errorf("unknown format: %s (use json or table)", format)
}
}
ログとユーザー向け出力
import (
"log"
"os"
)
var (
// User-facing output (stdout)
out = os.Stdout
// Logging and errors (stderr)
logger = log.New(os.Stderr, "[myapp] ", log.LstdFlags)
)
func RunCommand() error {
// User output: stdout
fmt.Fprintln(out, "Processing files...")
// Debug/verbose logging: stderr
if viper.GetBool("verbose") {
logger.Println("Reading config from", viper.ConfigFileUsed())
}
// Errors: stderr
if err := process(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
return err
}
// Success message: stdout
fmt.Fprintln(out, "✓ Complete")
return nil
}
CLI アプリケーションのテスト
コマンド実行のテスト
// cmd/deploy_test.go
package cmd
import (
"bytes"
"testing"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func executeCommand(root *cobra.Command, args ...string) (output string, err error) {
buf := new(bytes.Buffer)
root.SetOut(buf)
root.SetErr(buf)
root.SetArgs(args)
err = root.Execute()
return buf.String(), err
}
func TestDeployCommand(t *testing.T) {
tests := []struct {
name string
args []string
wantErr bool
wantOut string
}{
{
name: "deploy to dev",
args: []string{"deploy", "dev"},
wantErr: false,
wantOut: "Deploying to dev",
},
{
name: "deploy to production without force",
args: []string{"deploy", "production"},
wantErr: true,
wantOut: "production deploys require --force flag",
},
{
name: "deploy to production with force",
args: []string{"deploy", "production", "--force"},
wantErr: false,
wantOut: "Deploying to production",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
output, err := executeCommand(rootCmd, tt.args...)
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
assert.Contains(t, output, tt.wantOut)
})
}
}
Viper 設定を使ったテスト
func TestCommandWithConfig(t *testing.T) {
// Reset viper state before each test
viper.Reset()
// Set test configuration
viper.Set("region", "eu-west-1")
viper.Set("api_key", "test-key-123")
output, err := executeCommand(rootCmd, "deploy", "staging")
require.NoError(t, err)
assert.Contains(t, output, "eu-west-1")
}
出力の取得
func TestOutputFormat(t *testing.T) {
// Capture stdout
oldStdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
defer func() { os.Stdout = oldStdout }()
// Execute command
err := listCmd.RunE(listCmd, []string{})
require.NoError(t, err)
// Read output
w.Close()
var buf bytes.Buffer
io.Copy(&buf, r)
output := buf.String()
assert.Contains(t, output, "ID")
assert.Contains(t, output, "Name")
}
判断基準
Cobra を使用するタイミング
Cobra を使用する場合:
- ✅ サブコマンドを備えた複数コマンドの CLI を構築する(例:
git clone、docker run) - ✅ POSIX 準拠のフラグ解析が必要(
--flag、-f) - ✅ 組み込みのヘルプ生成(
--help)が必要 - ✅ シェル補完への対応が必要
- ✅ 他の開発者が使うプロフェッショナルな CLI を構築する
Cobra を使用しない場合:
- ❌ 単一コマンドのシンプルなスクリプト(
flagパッケージを使用) - ❌ フラグが 1~2 個だけの内部専用ツール
- ❌ プロトタイプや使い捨てのスクリプト
Viper を使う場面
次の場合は Viper を使います:
- ✅ 複数のソース(フラグ、環境変数、ファイル)から設定を取得する必要がある
- ✅ 明確な設定の優先順位ルールが必要
- ✅ 複数の設定ファイル形式(YAML、JSON、TOML)に対応する
- ✅ プレフィックス付きの環境変数マッピングが必要
- ✅ 設定のライブ再読み込みが必要(設定ファイルの変更を監視する)
次の場合は Viper を使いません:
- ❌ コマンドラインフラグのみを使う(Cobra だけで十分です)
- ❌ 設定値がハードコードされている
- ❌ 設定のないシンプルなスクリプト
シェル補完を追加する場面
次の場合はシェル補完を追加します:
- ✅ CLI を開発者が頻繁に(毎日/毎時間)使う
- ✅ サブコマンドや複雑なフラグの組み合わせが多い
- ✅ 引数に既知の有効な値がある(例:環境、リージョン)
- ✅ プロフェッショナル向けの開発者ツールを構築する
次の場合はシェル補完を省略します:
- ❌ CLI の使用頻度が低い(月に1回以下)
- ❌ オプションが少ないシンプルなコマンド
- ❌ 社内専用ツール
永続フラグを使う場面
次の場合は永続フラグを使います:
- ✅ フラグがすべてのサブコマンドに適用される(例:
--verbose、--config) - ✅ コマンド間で共通の設定を共有する
- ✅ グローバルな動作を変更する(例:
--dry-run、--output)
次の場合はローカルフラグを使います:
- ✅ フラグが特定のコマンドにのみ関係する
- ✅ コマンド固有のパラメーター(例:deploy コマンドの
--region)
アンチパターン
❌ PreRunE/RunE でエラーを処理しない
誤り:
var deployCmd = &cobra.Command{
Use: "deploy",
Run: func(cmd *cobra.Command, args []string) {
deploy(args[0]) // Ignores error!
},
}
正しい例:
var deployCmd = &cobra.Command{
Use: "deploy",
RunE: func(cmd *cobra.Command, args []string) error {
return deploy(args[0]) // Proper error handling
},
}
❌ 明確な優先順位を定めずに設定ソースを混在させる
誤り:
// Confusing: Which takes precedence?
config.Port = viper.GetInt("port")
if os.Getenv("PORT") != "" {
config.Port = atoi(os.Getenv("PORT"))
}
if *flagPort != 0 {
config.Port = *flagPort
}
正しい例:
// Clear: Viper handles precedence automatically
viper.BindPFlag("port", rootCmd.Flags().Lookup("port"))
viper.SetEnvPrefix("MYAPP")
viper.AutomaticEnv()
viper.SetDefault("port", 8080)
config.Port = viper.GetInt("port") // Respects: flag > env > config > default
❌ フラグを Viper にバインドし忘れる
誤り:
rootCmd.Flags().String("region", "us-east-1", "AWS region")
// Flag not bound to Viper - won't respect precedence!
func deploy() {
region := viper.GetString("region") // Always returns config file value
}
正しい例:
rootCmd.Flags().String("region", "us-east-1", "AWS region")
viper.BindPFlag("region", rootCmd.Flags().Lookup("region")) // Bind it!
func deploy() {
region := viper.GetString("region") // Respects flag > env > config
}
❌ CLI コマンドをテストしない
誤り:
// No tests for CLI commands - bugs slip through
正しい例:
func TestDeployCommand(t *testing.T) {
output, err := executeCommand(rootCmd, "deploy", "staging", "--region", "eu-west-1")
require.NoError(t, err)
assert.Contains(t, output, "Deploying to staging")
assert.Contains(t, output, "eu-west-1")
}
❌ エラーメッセージが不適切
誤り:
return fmt.Errorf("connection failed") // Unhelpful
正しい例:
return fmt.Errorf("cannot connect to database at %s:%d\nCheck:\n - Database is running\n - Credentials (MYAPP_DB_PASSWORD)\n - Firewall rules", host, port)
❌ RunE ではなく Run を使う
誤り:
var rootCmd = &cobra.Command{
Use: "myapp",
Run: func(cmd *cobra.Command, args []string) {
if err := execute(); err != nil {
fmt.Println(err) // Error not propagated
}
},
}
正しい例:
var rootCmd = &cobra.Command{
Use: "myapp",
RunE: func(cmd *cobra.Command, args []string) error {
return execute() // Cobra handles error display and exit code
},
}
本番環境向けの例
本番環境に対応した最小限の CLI 構成:
myapp/
├── cmd/
│ ├── root.go # Root command + config loading
│ ├── deploy.go # Deploy subcommand
│ ├── status.go # Status subcommand
│ └── completion.go # Shell completion
├── main.go # Entry point
├── config.yaml # Example config file
└── go.mod
main.go:
package main
import "myapp/cmd"
func main() {
cmd.Execute()
}
cmd/root.go:「コマンド構成パターン」セクションを参照してください
ビルドとインストール:
# Development
go run main.go deploy staging --region us-west-2
# Production build
go build -o myapp
# Install globally
go install
# Enable shell completion
myapp completion bash > /etc/bash_completion.d/myapp
リソース
公式ドキュメント:
- Cobra ユーザーガイド - 公式フレームワークドキュメント
- Viper ドキュメント - 設定管理ガイド
学習リソース:
- 「Building CLI Apps in Go with Cobra & Viper」(2025年11月)- 包括的なチュートリアル
- 「The Cobra & Viper Journey」- CLI 開発の学習パス
- Cobra Generator - 新しい CLI のひな型生成ツール
本番環境での使用例:
関連スキル:
golang-testing-strategies- CLI コマンドを包括的にテストするgolang-http-servers- 設定を使って API サーバーを構築するgolang-concurrency-patterns- CLI ツールでの非同期処理
成功の基準
次の条件を満たせば、Cobra + Viper を習得したと言えます:
- ✅ コマンドが POSIX の規約(
VERB NOUN --FLAG)に従っている - ✅ 設定の優先順位が明確である:フラグ > 環境変数 > 設定 > デフォルト値
- ✅ すべてのフラグが Viper にバインドされ、設定に統一的にアクセスできる
- ✅ 主要なすべてのシェル向けにシェル補完が生成されている
- ✅ エラーメッセージが次の行動につながり、ユーザーフレンドリーである
- ✅ CLI コマンドに包括的なテストがある
- ✅ ヘルプテキストが自動生成され、正確である
- ✅ グローバルなセットアップ/検証に PersistentPreRunE を使っている
- ✅ 関心事が分離されている:ユーザー向け出力(stdout)とログ出力(stderr)
- ✅ 設定ファイルは任意であり、フラグ/環境変数だけでも CLI が動作する