Home » Как использовать пакет Cobra в Go
Как использовать пакет Cobra в Go

Как использовать пакет Cobra в Go

Если вы админите сервера и наконец-то решили разобраться с Go, то наверняка сталкивались с задачей создания CLI-утилит. Хочется что-то простое, но мощное — чтобы можно было быстро накидать пару команд для управления конфигами, запуска скриптов или мониторинга. Пакет Cobra — это именно то, что нужно. Он используется в kubectl, docker, etcd и куче других инструментов, которые мы используем каждый день. Сегодня разберём, как с ним работать, чтобы вы могли создавать свои CLI-утилиты для автоматизации рутинных задач.

Что такое Cobra и зачем оно нужно

Cobra — это библиотека для создания CLI-приложений в Go. Она решает три основные задачи:

  • Парсинг аргументов командной строки
  • Создание иерархии команд (как у git: git commit, git push)
  • Автоматическая генерация справки и автодополнения

Основные принципы работы:

  • Команды — основные действия вашей утилиты
  • Аргументы — позиционные параметры
  • Флаги — именованные параметры (-v, –verbose)

Быстрый старт с Cobra

Начнём с простого примера — создадим утилиту для управления сервером:

mkdir server-cli
cd server-cli
go mod init server-cli
go get github.com/spf13/cobra@latest

Создаём основной файл main.go:

package main

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

var rootCmd = &cobra.Command{
    Use:   "server-cli",
    Short: "Утилита для управления сервером",
    Long:  "CLI-утилита для управления конфигурацией и мониторинга сервера",
}

var statusCmd = &cobra.Command{
    Use:   "status",
    Short: "Показать статус сервера",
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("Сервер работает нормально")
    },
}

var restartCmd = &cobra.Command{
    Use:   "restart [service]",
    Short: "Перезапустить сервис",
    Args:  cobra.MinimumNArgs(1),
    Run: func(cmd *cobra.Command, args []string) {
        service := args[0]
        fmt.Printf("Перезапускаем сервис: %s\n", service)
    },
}

func init() {
    rootCmd.AddCommand(statusCmd)
    rootCmd.AddCommand(restartCmd)
}

func main() {
    if err := rootCmd.Execute(); err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
}

Теперь можно собрать и протестировать:

go build -o server-cli
./server-cli --help
./server-cli status
./server-cli restart nginx

Работа с флагами

Флаги — это сердце любой CLI-утилиты. Cobra поддерживает persistent флаги (наследуются всеми подкомандами) и локальные флаги:

var verbose bool
var configFile string

func init() {
    // Persistent флаг - доступен для всех команд
    rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "подробный вывод")
    rootCmd.PersistentFlags().StringVar(&configFile, "config", "", "путь к конфигу")
    
    // Локальный флаг только для restart
    var force bool
    restartCmd.Flags().BoolVarP(&force, "force", "f", false, "принудительный перезапуск")
    
    // Обязательный флаг
    restartCmd.MarkFlagRequired("force")
}

Обновим команду restart:

var restartCmd = &cobra.Command{
    Use:   "restart [service]",
    Short: "Перезапустить сервис",
    Args:  cobra.MinimumNArgs(1),
    Run: func(cmd *cobra.Command, args []string) {
        service := args[0]
        force, _ := cmd.Flags().GetBool("force")
        
        if verbose {
            fmt.Printf("Подробный режим включён\n")
        }
        
        if force {
            fmt.Printf("Принудительно перезапускаем сервис: %s\n", service)
        } else {
            fmt.Printf("Мягко перезапускаем сервис: %s\n", service)
        }
    },
}

Практический пример: утилита для мониторинга

Создадим более сложную утилиту для мониторинга сервера. Она будет проверять состояние сервисов, использование ресурсов и отправлять уведомления:

package main

import (
    "fmt"
    "os"
    "os/exec"
    "strings"
    "time"
    
    "github.com/spf13/cobra"
)

var (
    configFile string
    verbose    bool
    interval   int
)

var rootCmd = &cobra.Command{
    Use:   "monitor",
    Short: "Утилита мониторинга сервера",
}

var checkCmd = &cobra.Command{
    Use:   "check",
    Short: "Проверить состояние системы",
}

var serviceCmd = &cobra.Command{
    Use:   "service [name]",
    Short: "Проверить состояние сервиса",
    Args:  cobra.ExactArgs(1),
    Run: func(cmd *cobra.Command, args []string) {
        serviceName := args[0]
        checkService(serviceName)
    },
}

var diskCmd = &cobra.Command{
    Use:   "disk",
    Short: "Проверить использование диска",
    Run: func(cmd *cobra.Command, args []string) {
        checkDisk()
    },
}

var watchCmd = &cobra.Command{
    Use:   "watch",
    Short: "Непрерывный мониторинг",
    Run: func(cmd *cobra.Command, args []string) {
        watchSystem()
    },
}

func checkService(name string) {
    cmd := exec.Command("systemctl", "is-active", name)
    output, err := cmd.Output()
    
    status := strings.TrimSpace(string(output))
    if err != nil {
        fmt.Printf("❌ Сервис %s: недоступен\n", name)
        return
    }
    
    if status == "active" {
        fmt.Printf("✅ Сервис %s: работает\n", name)
    } else {
        fmt.Printf("⚠️  Сервис %s: %s\n", name, status)
    }
}

func checkDisk() {
    cmd := exec.Command("df", "-h", "/")
    output, err := cmd.Output()
    if err != nil {
        fmt.Printf("❌ Не удалось проверить диск: %v\n", err)
        return
    }
    
    lines := strings.Split(string(output), "\n")
    if len(lines) >= 2 {
        fields := strings.Fields(lines[1])
        if len(fields) >= 5 {
            fmt.Printf("💾 Диск: использовано %s из %s (%s)\n", 
                fields[2], fields[1], fields[4])
        }
    }
}

func watchSystem() {
    fmt.Printf("🔍 Запуск мониторинга (интервал: %d секунд)\n", interval)
    
    for {
        fmt.Printf("\n--- %s ---\n", time.Now().Format("15:04:05"))
        
        // Проверяем основные сервисы
        services := []string{"nginx", "ssh", "systemd-resolved"}
        for _, service := range services {
            checkService(service)
        }
        
        checkDisk()
        
        time.Sleep(time.Duration(interval) * time.Second)
    }
}

func init() {
    rootCmd.PersistentFlags().StringVar(&configFile, "config", "", "путь к конфигу")
    rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "подробный вывод")
    
    watchCmd.Flags().IntVarP(&interval, "interval", "i", 10, "интервал проверки в секундах")
    
    checkCmd.AddCommand(serviceCmd)
    checkCmd.AddCommand(diskCmd)
    
    rootCmd.AddCommand(checkCmd)
    rootCmd.AddCommand(watchCmd)
}

func main() {
    if err := rootCmd.Execute(); err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
}

Теперь утилита поддерживает команды:

./monitor check service nginx
./monitor check disk
./monitor watch --interval 5

Продвинутые возможности

Cobra предоставляет множество дополнительных возможностей:

Валидация аргументов

var deployCmd = &cobra.Command{
    Use:   "deploy [environment]",
    Short: "Развернуть приложение",
    Args: cobra.MatchAll(
        cobra.ExactArgs(1),
        cobra.OnlyValidArgs,
    ),
    ValidArgs: []string{"dev", "staging", "prod"},
    Run: func(cmd *cobra.Command, args []string) {
        env := args[0]
        fmt.Printf("Развёртывание на %s\n", env)
    },
}

Кастомная валидация

var backupCmd = &cobra.Command{
    Use:   "backup [path]",
    Short: "Создать резервную копию",
    Args: func(cmd *cobra.Command, args []string) error {
        if len(args) != 1 {
            return fmt.Errorf("требуется путь к директории")
        }
        
        if _, err := os.Stat(args[0]); os.IsNotExist(err) {
            return fmt.Errorf("директория не существует: %s", args[0])
        }
        
        return nil
    },
    Run: func(cmd *cobra.Command, args []string) {
        path := args[0]
        fmt.Printf("Создаём бэкап: %s\n", path)
    },
}

Подкоманды с группировкой

var nginxCmd = &cobra.Command{
    Use:   "nginx",
    Short: "Управление Nginx",
}

var nginxStartCmd = &cobra.Command{
    Use:   "start",
    Short: "Запустить Nginx",
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("Запускаем Nginx...")
    },
}

var nginxStopCmd = &cobra.Command{
    Use:   "stop",
    Short: "Остановить Nginx",
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("Останавливаем Nginx...")
    },
}

var nginxConfigCmd = &cobra.Command{
    Use:   "config",
    Short: "Проверить конфигурацию",
    Run: func(cmd *cobra.Command, args []string) {
        cmd := exec.Command("nginx", "-t")
        output, err := cmd.CombinedOutput()
        if err != nil {
            fmt.Printf("❌ Ошибка конфигурации:\n%s\n", output)
        } else {
            fmt.Println("✅ Конфигурация корректна")
        }
    },
}

func init() {
    nginxCmd.AddCommand(nginxStartCmd)
    nginxCmd.AddCommand(nginxStopCmd)
    nginxCmd.AddCommand(nginxConfigCmd)
    
    rootCmd.AddCommand(nginxCmd)
}

Интеграция с другими пакетами

Cobra отлично работает с другими популярными пакетами:

Viper для конфигурации

go get github.com/spf13/viper
import "github.com/spf13/viper"

func init() {
    cobra.OnInitialize(initConfig)
    rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file")
}

func initConfig() {
    if cfgFile != "" {
        viper.SetConfigFile(cfgFile)
    } else {
        viper.AddConfigPath(".")
        viper.SetConfigName("config")
        viper.SetConfigType("yaml")
    }
    
    viper.AutomaticEnv()
    
    if err := viper.ReadInConfig(); err == nil {
        fmt.Println("Используется конфиг:", viper.ConfigFileUsed())
    }
}

Logrus для логирования

go get github.com/sirupsen/logrus
import "github.com/sirupsen/logrus"

var logger = logrus.New()

func init() {
    rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "подробный вывод")
    
    rootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
        if verbose {
            logger.SetLevel(logrus.DebugLevel)
        }
    }
}

Сравнение с альтернативами

Библиотека Размер Сложность Возможности Производительность
Cobra Средний Средняя Очень богатые Хорошая
flag (стандартная) Маленький Простая Базовые Отличная
urfave/cli Средний Простая Хорошие Хорошая
kingpin Средний Средняя Хорошие Хорошая

Cobra выигрывает в случаях, когда нужна сложная иерархия команд, автодополнение и богатые возможности валидации.

Генерация автодополнения

Одна из крутых фич Cobra — автогенерация скриптов автодополнения для разных shell’ов:

var completionCmd = &cobra.Command{
    Use:   "completion [bash|zsh|fish|powershell]",
    Short: "Генерация скрипта автодополнения",
    Long: `Генерация скрипта автодополнения для вашего shell.

Для bash:
    source <(yourprogram completion bash)

Для zsh:
    source <(yourprogram completion zsh)`,
    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().GenPowerShellCompletion(os.Stdout)
        }
    },
}

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

Кастомное автодополнение

Можно создать интеллектуальное автодополнение:

var serviceCmd = &cobra.Command{
    Use:   "service [name]",
    Short: "Управление сервисом",
    ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
        // Получаем список активных сервисов
        out, err := exec.Command("systemctl", "list-units", "--type=service", "--state=active", "--no-legend").Output()
        if err != nil {
            return nil, cobra.ShellCompDirectiveError
        }
        
        var services []string
        lines := strings.Split(string(out), "\n")
        for _, line := range lines {
            if line == "" {
                continue
            }
            parts := strings.Fields(line)
            if len(parts) > 0 {
                serviceName := strings.TrimSuffix(parts[0], ".service")
                services = append(services, serviceName)
            }
        }
        
        return services, cobra.ShellCompDirectiveNoFileComp
    },
    Run: func(cmd *cobra.Command, args []string) {
        // логика команды
    },
}

Деплой утилиты

Для развёртывания CLI-утилиты на сервере удобно использовать простой Makefile:

BINARY_NAME=server-cli
VERSION=1.0.0

build:
	go build -ldflags="-X main.Version=${VERSION}" -o ${BINARY_NAME}

build-linux:
	GOOS=linux GOARCH=amd64 go build -ldflags="-X main.Version=${VERSION}" -o ${BINARY_NAME}-linux

install: build
	sudo cp ${BINARY_NAME} /usr/local/bin/
	sudo chmod +x /usr/local/bin/${BINARY_NAME}

clean:
	rm -f ${BINARY_NAME}
	rm -f ${BINARY_NAME}-linux

.PHONY: build build-linux install clean

И не забудьте про версионирование:

var Version = "dev"

var versionCmd = &cobra.Command{
    Use:   "version",
    Short: "Показать версию",
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Printf("server-cli version %s\n", Version)
    },
}

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

Использование в production

Несколько советов для боевого использования:

  • Логирование: Всегда логируйте важные операции
  • Конфигурация: Используйте Viper для гибкой настройки
  • Ошибки: Обрабатывайте ошибки правильно и информативно
  • Тестирование: Покрывайте команды unit-тестами
func TestStatusCommand(t *testing.T) {
    cmd := &cobra.Command{
        Use: "status",
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Println("OK")
        },
    }
    
    b := bytes.NewBufferString("")
    cmd.SetOut(b)
    cmd.Execute()
    
    out, err := ioutil.ReadAll(b)
    if err != nil {
        t.Fatal(err)
    }
    
    if string(out) != "OK\n" {
        t.Fatalf("expected OK, got %s", string(out))
    }
}

Интеграция с CI/CD

Пример GitHub Actions для автоматической сборки:

name: Build and Release

on:
  push:
    tags:
      - 'v*'

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    
    - name: Set up Go
      uses: actions/setup-go@v2
      with:
        go-version: 1.19
    
    - name: Build
      run: |
        GOOS=linux GOARCH=amd64 go build -o server-cli-linux
        GOOS=darwin GOARCH=amd64 go build -o server-cli-darwin
        GOOS=windows GOARCH=amd64 go build -o server-cli-windows.exe
    
    - name: Release
      uses: softprops/action-gh-release@v1
      with:
        files: |
          server-cli-linux
          server-cli-darwin
          server-cli-windows.exe

Нестандартные применения

Cobra можно использовать не только для CLI, но и для:

  • Интерактивные меню: Комбинация с promptui
  • Webhook-обработчики: CLI как фронтенд к API
  • Конфигурационные утилиты: Генерация конфигов для других сервисов
  • Мониторинг и алертинг: Интеграция с Prometheus, Grafana

Пример интерактивного меню:

go get github.com/manifoldco/promptui
import "github.com/manifoldco/promptui"

var interactiveCmd = &cobra.Command{
    Use:   "interactive",
    Short: "Интерактивный режим",
    Run: func(cmd *cobra.Command, args []string) {
        prompt := promptui.Select{
            Label: "Выберите действие",
            Items: []string{"Статус сервера", "Перезагрузить сервис", "Показать логи"},
        }
        
        _, result, err := prompt.Run()
        if err != nil {
            fmt.Printf("Ошибка: %v\n", err)
            return
        }
        
        switch result {
        case "Статус сервера":
            fmt.Println("Сервер работает нормально")
        case "Перезагрузить сервис":
            // дополнительный промпт для выбора сервиса
        case "Показать логи":
            // показать логи
        }
    },
}

Автоматизация и скрипты

Cobra открывает новые возможности для автоматизации:

  • Цепочки команд: Выполнение последовательности операций
  • Условная логика: Выполнение команд в зависимости от состояния
  • Интеграция с cron: Регулярные задачи через CLI
  • Удалённое выполнение: SSH-команды через CLI

Пример cron-задачи:

# /etc/cron.d/server-monitor
*/5 * * * * root /usr/local/bin/server-cli check disk && /usr/local/bin/server-cli check service nginx

Если планируете развернуть такие утилиты в production, рекомендую обратить внимание на качественный VPS или выделенный сервер — стабильность инфраструктуры критична для автоматизации.

Заключение и рекомендации

Cobra — это мощный инструмент для создания CLI-утилит в Go. Он идеально подходит для:

  • Системных администраторов: Создание утилит для управления серверами
  • DevOps-инженеров: Автоматизация развёртывания и мониторинга
  • Разработчиков: Создание инструментов для разработки

Когда использовать Cobra:

  • Нужна сложная иерархия команд
  • Требуется богатая валидация аргументов
  • Важно автодополнение
  • Планируется активное развитие утилиты

Когда лучше выбрать альтернативы:

  • Простая утилита с 1-2 командами — используйте стандартный flag
  • Критична производительность — рассмотрите более лёгкие варианты
  • Нужна максимальная простота — попробуйте urfave/cli

Cobra используется в kubectl, docker, hugo, etcd и множестве других проектов. Это проверенное решение, которое поможет быстро создать профессиональную CLI-утилиту. Начните с простых команд, постепенно добавляйте функциональность, и вскоре у вас будет мощный инструмент для автоматизации повседневных задач.

Полезные ссылки:


В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.

Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.

Leave a reply

Your email address will not be published. Required fields are marked