ساخت CLI حرفهای با گولنگ - آموزش کامل Cobra
2025/11/24یکی از رایجترین کاربردهای 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 بسازید
این ابزار تمرینی را میتوانید گسترش دهید و به یک ابزار واقعی تبدیل کنید.