Cobra를 활용한 Go CLI 애플리케이션 설계 패턴

Cobra 프레임워크 개요

Cobra는 Go 언어 기반 명령줄 도구 개발을 위한 포괄적 프레임워크다. 쿠버네티스, 도커 CLI, 휴고 등 대규모 오픈소스 프로트에서 검증된 이 라이브러리는 명령 구조 설계부터 쉘 자동완성, 문서 생성까지 CLI 개발 전 과정을 지원한다.

핵심 구성 요소

Cobra의 아키텍처는 세 가지 추상화 계층으로 이루어진다.

  • 명령 객체(Command): 실행 가능한 작업 단위
  • 위치 인자(Argument): 명령에 전달되는 필수/선택적 값
  • 옵션 플래그(Flag): 명령 동작을 수정하는 키-값 쌍

프로젝트 초기 설정

go install github.com/spf13/cobra-cli@latest
cobra-cli init mytool
cd mytool && go mod tidy

명령 계층 구현

하위 명령을 포함하는 복합 구조를 구성하는 방법을 살펴본다.

package cmd

import (
	"fmt"
	"github.com/spf13/cobra"
)

var deployCmd = &cobra.Command{
	Use:   "deploy [환경명]",
	Short: "애플리케이션 배포 작업",
	Long:  `지정된 환경(dev/staging/prod)에 애플리케이션을 배포합니다.`,
	Args:  cobra.ExactArgs(1),
	RunE: func(cmd *cobra.Command, params []string) error {
		targetEnv := params[0]
		skipVerify, _ := cmd.Flags().GetBool("skip-validation")
		
		fmt.Printf("🚀 %s 환경 배포 시작\n", targetEnv)
		if !skipVerify {
			fmt.Println("✓ 사전 검증 수행 중...")
		}
		return nil
	},
}

func init() {
	rootCmd.AddCommand(deployCmd)
	deployCmd.Flags().BoolP("skip-validation", "s", false, "배포 전 검증 단계 생략")
}

지속적 플래그와 설정 연동

전역 옵션과 Viper 설정 라이브러리를 통합하여 환경별 설정을 관리할 수 있다.

var cfgPath string

var rootCmd = &cobra.Command{
	Use:   "mytool",
	PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
		if cfgPath != "" {
			viper.SetConfigFile(cfgPath)
			return viper.ReadInConfig()
		}
		return nil
	},
}

func init() {
	cobra.OnInitialize(initConfig)
	rootCmd.PersistentFlags().StringVar(&cfgPath, "config", "", "설정 파일 경로")
}

쉘 자동완성 생성

사용자 경험 향상을 위해 다양한 쉘에 대한 탭 완성 스크립트를 생성한다.

var completionCmd = &cobra.Command{
	Use:   "completion [bash|zsh|fish|powershell]",
	Short: "쉘 자동완성 스크립트 생성",
	Args:  cobra.ExactValidArgs(1),
	ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
	Run: func(cmd *cobra.Command, shellType []string) {
		switch shellType[0] {
		case "bash":
			cmd.Root().GenBashCompletion(cmd.OutOrStdout())
		case "zsh":
			cmd.Root().GenZshCompletion(cmd.OutOrStdout())
		case "fish":
			cmd.Root().GenFishCompletion(cmd.OutOrStdout(), true)
		case "powershell":
			cmd.Root().GenPowerShellCompletionWithDesc(cmd.OutOrStdout())
		}
	},
}

동적 문서 생성

코드 기반으로 마크다운 문서와 man 페이지를 자동 생성하여 문서-코드 동기화 문제를 해결한다.

import "github.com/spf13/cobra/doc"

func generateDocs() error {
	cmd := &cobra.Command{Use: "mytool"}
	
	// 마크다운 문서 디렉터리 생성
	if err := doc.GenMarkdownTree(cmd, "./docs/md"); err != nil {
		return err
	}
	
	// man 페이지 생성 (섹션 1: 사용자 명령)
	header := &doc.GenManHeader{
		Title:   "MYTOOL",
		Section: "1",
		Source:  "MyTool 프로젝트",
	}
	return doc.GenManTree(cmd, header, "./docs/man")
}

고급 패턴: 미들웨어 체인

명 실행 전후에 공통 처리 로직을 주입하는 패턴을 적용할 수 있다.

func withTelemetry(next cobra.RunEFunc) cobra.RunEFunc {
	return func(cmd *cobra.Command, args []string) error {
		start := time.Now()
		traceID := uuid.New().String()
		
		cmd.SetContext(context.WithValue(cmd.Context(), "trace.id", traceID))
		
		err := next(cmd, args)
		
		duration := time.Since(start)
		recordMetrics(cmd.Name(), duration, err)
		
		return err
	}
}

// 사용 예시
var migrateCmd = &cobra.Command{
	Use:   "migrate",
	RunE:  withTelemetry(executeMigration),
}

테스트 가능한 구조 설계

의존성 주입을 통해 명령 로직을 격리하여 단위 테스트를 용이하게 한다.

type DeployService interface {
	Validate(ctx context.Context, env string) error
	Execute(ctx context.Context, env string, opts DeployOptions) error
}

func NewDeployCommand(svc DeployService) *cobra.Command {
	return &cobra.Command{
		Use: "deploy",
		RunE: func(cmd *cobra.Command, args []string) error {
			ctx := cmd.Context()
			if err := svc.Validate(ctx, args[0]); err != nil {
				return fmt.Errorf("검증 실패: %w", err)
			}
			return svc.Execute(ctx, args[0], parseOptions(cmd))
		},
	}
}

태그: Cobra go cli spf13 Viper

6월 26일 22:20에 게시됨