GoCasts آموزش Go به زبان ساده

بیش از ۱۰۰۰ شرکت‌کننده یادگیری Go و Backend رو از امروز شروع کن
ثبت‌نام دوره + تیم‌سازی

ساخت CLI حرفه‌ای با گولنگ - آموزش کامل Cobra

یکی از رایج‌ترین کاربردهای Go برای DevOps Engineers، ساخت ابزارهای CLI است. ابزارهایی مثل kubectl، docker، terraform و helm همگی با Go و کتابخانه Cobra ساخته شده‌اند.

در این آموزش، یک ابزار CLI واقعی برای مدیریت deployment می‌سازیم.

چرا Cobra؟

ویژگی Cobra flag استاندارد
Subcommands بله خیر
Auto-completion بله خیر
Help generation خودکار دستی
Config files با Viper دستی
Used by kubectl, docker, gh -

نصب و راه‌اندازی

# ایجاد پروژه جدید
mkdir myctl && cd myctl
go mod init myctl

# نصب Cobra
go get -u github.com/spf13/cobra@latest

# نصب Cobra CLI (اختیاری - برای scaffolding)
go install github.com/spf13/cobra-cli@latest

ساختار پروژه

myctl/
├── main.go
├── cmd/
│   ├── root.go
│   ├── deploy.go
│   ├── status.go
│   └── rollback.go
├── internal/
│   └── deploy/
│       └── deploy.go
├── go.mod
└── go.sum

مرحله ۱: Root Command

cmd/root.go:

package cmd

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
)

var (
    // Global flags
    verbose    bool
    configFile string
)

// rootCmd نمایانگر base command است
var rootCmd = &cobra.Command{
    Use:   "myctl",
    Short: "ابزار مدیریت deployment",
    Long: `myctl یک ابزار CLI برای مدیریت deployment است.

این ابزار به شما امکان می‌دهد:
  - Deploy کردن اپلیکیشن‌ها به محیط‌های مختلف
  - مشاهده وضعیت deployment‌ها
  - Rollback به نسخه‌های قبلی`,
    // اگر بدون subcommand اجرا شد
    Run: func(cmd *cobra.Command, args []string) {
        cmd.Help()
    },
}

// Execute اجرای CLI را شروع می‌کند
func Execute() {
    if err := rootCmd.Execute(); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

func init() {
    // Global flags - برای همه subcommands
    rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v",
        false, "نمایش جزئیات بیشتر")
    rootCmd.PersistentFlags().StringVarP(&configFile, "config", "c",
        "", "مسیر فایل config")
}

main.go:

package main

import "myctl/cmd"

func main() {
    cmd.Execute()
}

مرحله ۲: Deploy Command

cmd/deploy.go:

package cmd

import (
    "fmt"
    "os"
    "os/exec"
    "strings"

    "github.com/spf13/cobra"
)

var (
    environment string
    imageTag    string
    dryRun      bool
    namespace   string
)

var deployCmd = &cobra.Command{
    Use:   "deploy [app-name]",
    Short: "Deploy یک اپلیکیشن",
    Long: `Deploy یک اپلیکیشن به محیط مشخص شده.

مثال:
  myctl deploy api --env staging --tag v1.2.3
  myctl deploy api --env production --dry-run`,
    Args: cobra.ExactArgs(1), // دقیقاً یک argument
    RunE: runDeploy,          // RunE برای برگرداندن error
}

func init() {
    // اضافه کردن به root command
    rootCmd.AddCommand(deployCmd)

    // Local flags - فقط برای این command
    deployCmd.Flags().StringVarP(&environment, "env", "e",
        "staging", "محیط deployment (staging/production)")
    deployCmd.Flags().StringVarP(&imageTag, "tag", "t",
        "latest", "تگ Docker image")
    deployCmd.Flags().BoolVar(&dryRun, "dry-run",
        false, "فقط نمایش، بدون اجرا")
    deployCmd.Flags().StringVarP(&namespace, "namespace", "n",
        "default", "Kubernetes namespace")

    // علامت‌گذاری flag ضروری
    deployCmd.MarkFlagRequired("env")
}

func runDeploy(cmd *cobra.Command, args []string) error {
    appName := args[0]

    // اعتبارسنجی محیط
    validEnvs := map[string]bool{
        "staging":    true,
        "production": true,
    }
    if !validEnvs[environment] {
        return fmt.Errorf("محیط نامعتبر: %s (مجاز: staging, production)",
            environment)
    }

    // هشدار برای production
    if environment == "production" && !dryRun {
        fmt.Print("آیا از deploy به production مطمئن هستید؟ (yes/no): ")
        var confirm string
        fmt.Scanln(&confirm)
        if strings.ToLower(confirm) != "yes" {
            fmt.Println("لغو شد.")
            return nil
        }
    }

    if verbose {
        fmt.Printf("App: %s\n", appName)
        fmt.Printf("Environment: %s\n", environment)
        fmt.Printf("Image Tag: %s\n", imageTag)
        fmt.Printf("Namespace: %s\n", namespace)
    }

    // ساخت image name
    image := fmt.Sprintf("registry.example.com/%s:%s", appName, imageTag)

    if dryRun {
        fmt.Println("[DRY-RUN] دستورات زیر اجرا می‌شدند:")
        fmt.Printf("kubectl set image deployment/%s %s=%s -n %s\n",
            appName, appName, image, namespace)
        return nil
    }

    // اجرای kubectl
    kubectlCmd := exec.Command("kubectl", "set", "image",
        fmt.Sprintf("deployment/%s", appName),
        fmt.Sprintf("%s=%s", appName, image),
        "-n", namespace,
    )

    kubectlCmd.Stdout = os.Stdout
    kubectlCmd.Stderr = os.Stderr

    fmt.Printf("Deploying %s to %s...\n", appName, environment)
    if err := kubectlCmd.Run(); err != nil {
        return fmt.Errorf("deployment failed: %w", err)
    }

    fmt.Printf("Successfully deployed %s to %s\n", appName, environment)
    return nil
}

مرحله ۳: Status Command

cmd/status.go:

package cmd

import (
    "encoding/json"
    "fmt"
    "os/exec"
    "strings"

    "github.com/spf13/cobra"
)

var (
    outputFormat string
    allNamespaces bool
)

var statusCmd = &cobra.Command{
    Use:   "status [app-name]",
    Short: "نمایش وضعیت deployment",
    Long: `نمایش وضعیت فعلی یک deployment.

مثال:
  myctl status api
  myctl status api -o json
  myctl status --all`,
    Args: cobra.MaximumNArgs(1),
    RunE: runStatus,
}

func init() {
    rootCmd.AddCommand(statusCmd)

    statusCmd.Flags().StringVarP(&outputFormat, "output", "o",
        "table", "فرمت خروجی (table/json/yaml)")
    statusCmd.Flags().BoolVarP(&allNamespaces, "all", "A",
        false, "نمایش همه namespaces")
}

type DeploymentStatus struct {
    Name      string `json:"name"`
    Ready     string `json:"ready"`
    Available int    `json:"available"`
    Age       string `json:"age"`
    Image     string `json:"image"`
}

func runStatus(cmd *cobra.Command, args []string) error {
    var kubectlArgs []string

    if len(args) > 0 {
        // وضعیت یک deployment خاص
        kubectlArgs = []string{"get", "deployment", args[0],
            "-o", "jsonpath={.status.availableReplicas}/{.spec.replicas}"}
    } else {
        // لیست همه deployments
        kubectlArgs = []string{"get", "deployments"}
        if allNamespaces {
            kubectlArgs = append(kubectlArgs, "--all-namespaces")
        }
        kubectlArgs = append(kubectlArgs, "-o", "wide")
    }

    if namespace != "" && !allNamespaces {
        kubectlArgs = append(kubectlArgs, "-n", namespace)
    }

    output, err := exec.Command("kubectl", kubectlArgs...).Output()
    if err != nil {
        return fmt.Errorf("failed to get status: %w", err)
    }

    switch outputFormat {
    case "json":
        // تبدیل به JSON
        result := map[string]string{
            "status": strings.TrimSpace(string(output)),
        }
        jsonOutput, _ := json.MarshalIndent(result, "", "  ")
        fmt.Println(string(jsonOutput))
    default:
        fmt.Println(string(output))
    }

    return nil
}

مرحله ۴: Rollback Command

cmd/rollback.go:

package cmd

import (
    "fmt"
    "os"
    "os/exec"

    "github.com/spf13/cobra"
)

var (
    revision int
)

var rollbackCmd = &cobra.Command{
    Use:   "rollback [app-name]",
    Short: "Rollback به نسخه قبلی",
    Long: `Rollback یک deployment به revision قبلی.

مثال:
  myctl rollback api               # به revision قبلی
  myctl rollback api --revision 3  # به revision خاص`,
    Args: cobra.ExactArgs(1),
    RunE: runRollback,
}

func init() {
    rootCmd.AddCommand(rollbackCmd)

    rollbackCmd.Flags().IntVarP(&revision, "revision", "r",
        0, "شماره revision (0 = قبلی)")
}

func runRollback(cmd *cobra.Command, args []string) error {
    appName := args[0]

    var kubectlArgs []string

    if revision > 0 {
        kubectlArgs = []string{"rollout", "undo",
            fmt.Sprintf("deployment/%s", appName),
            fmt.Sprintf("--to-revision=%d", revision),
        }
    } else {
        kubectlArgs = []string{"rollout", "undo",
            fmt.Sprintf("deployment/%s", appName),
        }
    }

    if namespace != "" {
        kubectlArgs = append(kubectlArgs, "-n", namespace)
    }

    if dryRun {
        fmt.Println("[DRY-RUN] دستور زیر اجرا می‌شد:")
        fmt.Printf("kubectl %v\n", kubectlArgs)
        return nil
    }

    kubectlCmd := exec.Command("kubectl", kubectlArgs...)
    kubectlCmd.Stdout = os.Stdout
    kubectlCmd.Stderr = os.Stderr

    fmt.Printf("Rolling back %s...\n", appName)
    if err := kubectlCmd.Run(); err != nil {
        return fmt.Errorf("rollback failed: %w", err)
    }

    fmt.Printf("Successfully rolled back %s\n", appName)
    return nil
}

مرحله ۵: اضافه کردن Config با Viper

cmd/root.go (آپدیت شده):

package cmd

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
    "github.com/spf13/viper"
)

var (
    verbose    bool
    configFile string
)

var rootCmd = &cobra.Command{
    Use:   "myctl",
    Short: "ابزار مدیریت deployment",
}

func Execute() {
    if err := rootCmd.Execute(); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

func init() {
    cobra.OnInitialize(initConfig)

    rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v",
        false, "نمایش جزئیات بیشتر")
    rootCmd.PersistentFlags().StringVarP(&configFile, "config", "c",
        "", "مسیر فایل config")
}

func initConfig() {
    if configFile != "" {
        // استفاده از فایل مشخص شده
        viper.SetConfigFile(configFile)
    } else {
        // جستجو در مسیرهای پیش‌فرض
        home, err := os.UserHomeDir()
        if err == nil {
            viper.AddConfigPath(home)
        }
        viper.AddConfigPath(".")
        viper.SetConfigName(".myctl")
        viper.SetConfigType("yaml")
    }

    // خواندن environment variables
    viper.SetEnvPrefix("MYCTL")
    viper.AutomaticEnv()

    // خواندن config file
    if err := viper.ReadInConfig(); err == nil {
        if verbose {
            fmt.Printf("Using config: %s\n", viper.ConfigFileUsed())
        }
    }
}

فایل config نمونه .myctl.yaml:

# تنظیمات پیش‌فرض
default_namespace: production
default_environment: staging

# تنظیمات registry
registry:
  url: registry.example.com
  username: admin

# محیط‌ها
environments:
  staging:
    namespace: staging
    cluster: staging-cluster
  production:
    namespace: production
    cluster: prod-cluster

مرحله ۶: Auto-completion

// cmd/completion.go
package cmd

import (
    "os"

    "github.com/spf13/cobra"
)

var completionCmd = &cobra.Command{
    Use:   "completion [bash|zsh|fish]",
    Short: "تولید اسکریپت auto-completion",
    Long: `تولید اسکریپت auto-completion برای shell مشخص شده.

مثال:
  # Bash
  source <(myctl completion bash)

  # Zsh
  myctl completion zsh > "${fpath[1]}/_myctl"

  # Fish
  myctl completion fish | source`,
    Args:      cobra.ExactValidArgs(1),
    ValidArgs: []string{"bash", "zsh", "fish"},
    Run: func(cmd *cobra.Command, args []string) {
        switch args[0] {
        case "bash":
            rootCmd.GenBashCompletion(os.Stdout)
        case "zsh":
            rootCmd.GenZshCompletion(os.Stdout)
        case "fish":
            rootCmd.GenFishCompletion(os.Stdout, true)
        }
    },
}

func init() {
    rootCmd.AddCommand(completionCmd)
}

Build و استفاده

# Build
go build -o myctl

# یا با version info
go build -ldflags "-X main.version=1.0.0" -o myctl

# استفاده
./myctl --help
./myctl deploy api --env staging --tag v1.2.3
./myctl status api
./myctl rollback api

# Build برای همه پلتفرم‌ها
GOOS=linux   GOARCH=amd64 go build -o myctl-linux
GOOS=darwin  GOARCH=arm64 go build -o myctl-mac
GOOS=windows GOARCH=amd64 go build -o myctl.exe

خروجی نمونه

$ ./myctl --help
ابزار مدیریت deployment

این ابزار به شما امکان می‌دهد:
  - Deploy کردن اپلیکیشن‌ها به محیط‌های مختلف
  - مشاهده وضعیت deployment‌ها
  - Rollback به نسخه‌های قبلی

Usage:
  myctl [command]

Available Commands:
  completion  تولید اسکریپت auto-completion
  deploy      Deploy یک اپلیکیشن
  help        Help about any command
  rollback    Rollback به نسخه قبلی
  status      نمایش وضعیت deployment

Flags:
  -c, --config string   مسیر فایل config
  -h, --help            help for myctl
  -v, --verbose         نمایش جزئیات بیشتر

$ ./myctl deploy api --env staging --tag v1.2.3 --dry-run
[DRY-RUN] دستورات زیر اجرا می‌شدند:
kubectl set image deployment/api api=registry.example.com/api:v1.2.3 -n default

تبدیل به kubectl Plugin

می‌توانید ابزار خود را به یک plugin برای kubectl تبدیل کنید:

# نام فایل باید با kubectl- شروع شود
mv myctl kubectl-myctl

# قرار دادن در PATH
sudo mv kubectl-myctl /usr/local/bin/

# حالا می‌توانید اینطور استفاده کنید:
kubectl myctl deploy api --env staging

بهترین شیوه‌ها

شیوه توضیح
Use RunE برای برگرداندن errors
Validate Args استفاده از cobra.ExactArgs
Required Flags علامت‌گذاری flags ضروری
Help Text Long description کامل
Dry-run همیشه گزینه preview
Verbose سطوح مختلف logging
Config file پشتیبانی از Viper

جمع‌بندی

با Cobra می‌توانید:

  • CLI‌های حرفه‌ای با subcommands بسازید
  • Auto-completion داشته باشید
  • Config files را مدیریت کنید
  • ابزارهایی در سطح kubectl بسازید

این ابزار تمرینی را می‌توانید گسترش دهید و به یک ابزار واقعی تبدیل کنید.


مقالات مرتبط

منابع

بیش از ۱۰۰۰ شرکت‌کننده یادگیری Go و Backend رو از امروز شروع کن
ثبت‌نام دوره + تیم‌سازی