Tworzenie aplikacji CLI w Go z użyciem Cobra i Viper
Rozwój CLI w Go z użyciem frameworków Cobra i Viper
Interfejs wiersza poleceń (CLI) to istotne narzędzia dla programistów, administratorów systemów oraz specjalistów DevOps.
Dwie biblioteki w języku Go stały się standardem dla rozwoju CLI w Go: Cobra do struktury poleceń i Viper do zarządzania konfiguracją.
Język Go zaczął się wyróżniać jako doskonały język do tworzenia narzędzi CLI dzięki swojej wydajności, prostemu wdrażaniu oraz wsparciu dla wielu platform.

Dlaczego warto wybrać Go do aplikacji CLI
Go oferuje przekonujące zalety dla rozwoju aplikacji CLI:
- Dystrybucja pojedynczego pliku: Nie wymaga zależności uruchomieniowych ani menedżerów pakietów
- Szybkie wykonanie: Kompilacja natywna zapewnia bardzo dobre wydajność
- Wsparcie wieloplatformowe: Łatwe kompilowanie dla Linux, macOS, Windows i wielu innych
- Silna biblioteka standardowa: Bogate narzędzia do operacji plików, sieci i przetwarzania tekstu
- Wątkowość: Wbudowane goroutines do operacji równoległych
- Typowanie statyczne: Wykrywanie błędów w czasie kompilacji
Popularne narzędzia CLI zbudowane w Go obejmują Docker, Kubernetes (kubectl), Hugo, Terraform i GitHub CLI. Jeśli jesteś nowy w Go lub potrzebujesz szybkiego odniesienia, sprawdź nasz Arkusz wskazówek dla Go z istotnymi składnią i wzorcami Go.
Wprowadzenie do Cobra
Cobra to biblioteka oferująca prosty interfejs do tworzenia potężnych, nowoczesnych aplikacji CLI. Stworzona przez Steve’a Francia (spf13), tego samego autora, który tworzył Hugo i Viper, Cobra jest używana w wielu najpopularniejszych projektach w Go.
Kluczowe cechy Cobra
Struktura poleceń: Cobra implementuje wzorzec komend, umożliwiając tworzenie aplikacji z komendami i podkomendami (np. git commit lub docker run).
Obsługa flag: Lokalne i trwałe flagi z automatycznym parsowaniem i konwersją typów.
Automatyczna pomoc: Generuje tekst pomocy i informacje o użyciu automatycznie.
Inteligentne sugestie: Udostępnia sugestie, gdy użytkownicy popełniają błędy ortograficzne (“Czy chodziło o ‘status’?”).
Kompletacje powłoki: Generuje skrypty kompletacji dla bash, zsh, fish i PowerShell.
Elastyczne wyjścia: Działa płynnie z niestandardowymi formatami i stylami wyjścia.
Wprowadzenie do Viper
Viper to kompletna propozycja konfiguracji dla aplikacji w Go, zaprojektowana do płynnego działania z Cobra. Obsługuje konfigurację z wielu źródeł z wyraźnym porządkiem priorytetów.
Kluczowe cechy Viper
Wiele źródeł konfiguracji:
- Pliki konfiguracyjne (JSON, YAML, TOML, HCL, INI, envfile, Java properties)
- Zmienne środowiskowe
- Flagi wiersza poleceń
- Systemy konfiguracji zdalnej (etcd, Consul)
- Wartości domyślne
Porządek priorytetów konfiguracji:
- Jawne wywołania Set
- Flagi wiersza poleceń
- Zmienne środowiskowe
- Plik konfiguracyjny
- Magazyn klucz/wartość
- Wartości domyślne
Monitorowanie w czasie rzeczywistym: Monitoruje pliki konfiguracyjne i automatycznie odświeża je w przypadku zmian.
Konwersja typów: Automatyczna konwersja do różnych typów Go (ciąg, liczba całkowita, wartość logiczna, czas itp.).
Rozpoczynanie: Instalacja
Najpierw zainicjalizuj nowy moduł Go i zainstaluj obie biblioteki:
go mod init myapp
go get -u github.com/spf13/cobra@latest
go get -u github.com/spf13/viper@latest
Opcjonalnie, zainstaluj generator CLI Cobra do tworzenia szkieletów:
go install github.com/spf13/cobra-cli@latest
Budowanie pierwszej aplikacji CLI
Zbudujmy praktyczny przykład: narzędzie CLI do zarządzania zadaniami z obsługą konfiguracji.
Struktura projektu
mytasks/
├── cmd/
│ ├── root.go
│ ├── add.go
│ ├── list.go
│ └── complete.go
├── config/
│ └── config.go
├── main.go
└── config.yaml
Główny punkt wejścia
// main.go
package main
import "mytasks/cmd"
func main() {
cmd.Execute()
}
Polecenie główne z integracją Viper
// cmd/root.go
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var cfgFile string
var rootCmd = &cobra.Command{
Use: "mytasks",
Short: "Prosty kli do zarządzania zadaniami",
Long: `MyTasks to kli do zarządzania zadaniami, który pomaga w organizowaniu
codziennych zadań. Stworzony z użyciem Cobra i Viper.`,
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "",
"plik konfiguracyjny (domyślnie $HOME/.mytasks.yaml)")
rootCmd.PersistentFlags().String("db", "",
"ścieżka do pliku bazy danych")
viper.BindPFlag("database", rootCmd.PersistentFlags().Lookup("db"))
}
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(".mytasks")
}
viper.SetEnvPrefix("MYTASKS")
viper.AutomaticEnv()
if err := viper.ReadInConfig(); err == nil {
fmt.Println("Używany plik konfiguracyjny:", viper.ConfigFileUsed())
}
}
Dodawanie podkomend
// cmd/add.go
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var priority string
var addCmd = &cobra.Command{
Use: "add [opis zadania]",
Short: "Dodaj nowe zadanie",
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
task := args[0]
db := viper.GetString("database")
fmt.Printf("Dodawanie zadania: %s\n", task)
fmt.Printf("Priorytet: %s\n", priority)
fmt.Printf("Baza danych: %s\n", db)
// Tutaj zaimplementujesz rzeczywiste przechowywanie zadań
},
}
func init() {
rootCmd.AddCommand(addCmd)
addCmd.Flags().StringVarP(&priority, "priority", "p", "medium",
"Priorytet zadania (niski, średni, wysoki)")
}
Polecenie listy
// cmd/list.go
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var showCompleted bool
var listCmd = &cobra.Command{
Use: "list",
Short: "Wyświetl wszystkie zadania",
Run: func(cmd *cobra.Command, args []string) {
db := viper.GetString("database")
fmt.Printf("Wyświetlanie zadań z: %s\n", db)
fmt.Printf("Pokaż ukończone: %v\n", showCompleted)
// Tutaj zaimplementujesz rzeczywiste wyświetlanie zadań
},
}
func init() {
rootCmd.AddCommand(listCmd)
listCmd.Flags().BoolVarP(&showCompleted, "completed", "c", false,
"Pokaż ukończone zadania")
}
Implementacja przechowywania danych
Dla produkcyjnego narzędzia do zarządzania zadaniami CLI, musisz zaimplementować rzeczywiste przechowywanie danych. Choć tutaj używamy prostego konfiguracji ścieżki bazy danych, masz kilka opcji przechowywania danych:
- SQLite: Lekki, bezserwerowy baza danych idealna do narzędzi CLI
- PostgreSQL/MySQL: Pełnoprawne bazy danych dla bardziej złożonych aplikacji
- Pliki JSON/YAML: Proste przechowywanie danych w plikach dla potrzeb lekkich
- Bazy danych wbudowane: BoltDB, BadgerDB do przechowywania klucz/wartość
Jeśli pracujesz z relacyjnymi bazami danych takimi jak PostgreSQL, chcesz użyć ORM lub budownika zapytań. Dla kompletnego porównania bibliotek baz danych w Go, zobacz nasz przewodnik po Porównaniu ORM dla PostgreSQL w Go: GORM vs Ent vs Bun vs sqlc.
Zaawansowana konfiguracja z Viper
Przykład pliku konfiguracyjnego
# .mytasks.yaml
database: ~/.mytasks.db
format: table
colors: true
notifications:
enabled: true
sound: true
priorities:
high: red
medium: yellow
low: green
Odczytywanie zagnieżdżonej konfiguracji
func getNotificationSettings() {
enabled := viper.GetBool("notifications.enabled")
sound := viper.GetBool("notifications.sound")
fmt.Printf("Powiadomienia włączone: %v\n", enabled)
fmt.Printf("Dźwięk włączony: %v\n", sound)
}
func getPriorityColors() map[string]string {
return viper.GetStringMapString("priorities")
}
Zmienne środowiskowe
Viper automatycznie odczytuje zmienne środowiskowe z konfigurowanym prefiksem:
export MYTASKS_DATABASE=/tmp/tasks.db
export MYTASKS_NOTIFICATIONS_ENABLED=false
mytasks list
Odświeżanie konfiguracji w czasie rzeczywistym
func watchConfig() {
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("Zmieniono plik konfiguracyjny:", e.Name)
// Odśwież komponenty zależne od konfiguracji
})
}
Najlepsze praktyki
1. Użyj generatora Cobra dla spójności
cobra-cli init
cobra-cli add serve
cobra-cli add create
2. Organizuj polecenia w osobnych plikach
Zachowuj każde polecenie w osobnym pliku w katalogu cmd/ dla łatwiejszego utrzymania.
3. Wykorzystaj trwałe flagi
Używaj trwałych flag dla opcji, które mają zastosowanie do wszystkich podkomend:
rootCmd.PersistentFlags().StringP("output", "o", "text",
"Format wyjścia (tekst, json, yaml)")
4. Zaimplementuj odpowiednie obsługę błędów
var rootCmd = &cobra.Command{
Use: "myapp",
Short: "Moja aplikacja",
RunE: func(cmd *cobra.Command, args []string) error {
if err := doSomething(); err != nil {
return fmt.Errorf("nie udało się wykonać czegoś: %w", err)
}
return nil
},
}
5. Udostępnij znaczący tekst pomocy
var cmd = &cobra.Command{
Use: "deploy [environment]",
Short: "Wdrażanie aplikacji w określonym środowisku",
Long: `Wdraża budowanie i wdrażanie aplikacji w określonym
środowisku. Obsługiwane środowiska to:
- rozwijanie (dev)
- testowanie (staging)
- produkcja (prod)`,
Example: ` myapp deploy staging
myapp deploy production --version=1.2.3`,
}
6. Ustaw rozsądne domyślne wartości
func init() {
viper.SetDefault("port", 8080)
viper.SetDefault("timeout", "30s")
viper.SetDefault("retries", 3)
}
7. Walidacja konfiguracji
func validateConfig() error {
port := viper.GetInt("port")
if port < 1024 || port > 65535 {
return fmt.Errorf("nieprawidłowy port: %d", port)
}
return nil
}
Testowanie aplikacji CLI
Testowanie poleceń
// cmd/root_test.go
package cmd
import (
"bytes"
"testing"
)
func TestRootCommand(t *testing.T) {
cmd := rootCmd
b := bytes.NewBufferString("")
cmd.SetOut(b)
cmd.SetArgs([]string{"--help"})
if err := cmd.Execute(); err != nil {
t.Fatalf("wykonanie polecenia nie powiodło się: %v", err)
}
}
Testowanie z różnymi konfiguracjami
func TestWithConfig(t *testing.T) {
viper.Set("database", "/tmp/test.db")
viper.Set("debug", true)
// Uruchom swoje testy
viper.Reset() // Czyszczenie
}
Generowanie skryptów kompletacji powłoki
Cobra może generować skrypty kompletacji dla różnych powłok:
// cmd/completion.go
package cmd
import (
"os"
"github.com/spf13/cobra"
)
var completionCmd = &cobra.Command{
Use: "completion [bash|zsh|fish|powershell]",
Short: "Generuj skrypt kompletacji",
Long: `Aby załadować kompletacje:
Bash:
$ source <(mytasks completion bash)
Zsh:
$ source <(mytasks completion zsh)
Fish:
$ mytasks completion fish | source
PowerShell:
PS> mytasks completion powershell | Out-String | Invoke-Expression
`,
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().GenPowerShellCompletionWithDesc(os.Stdout)
}
},
}
func init() {
rootCmd.AddCommand(completionCmd)
}
Budowanie i dystrybucja
Budowanie dla wielu platform
# Linux
GOOS=linux GOARCH=amd64 go build -o mytasks-linux-amd64
# macOS
GOOS=darwin GOARCH=amd64 go build -o mytasks-darwin-amd64
GOOS=darwin GOARCH=arm64 go build -o mytasks-darwin-arm64
# Windows
GOOS=windows GOARCH=amd64 go build -o mytasks-windows-amd64.exe
Zmniejszenie rozmiaru binarki
go build -ldflags="-s -w" -o mytasks
Dodanie informacji o wersji
// cmd/version.go
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var (
Version = "dev"
Commit = "none"
BuildTime = "unknown"
)
var versionCmd = &cobra.Command{
Use: "version",
Short: "Wydrukuj informacje o wersji",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Wersja: %s\n", Version)
fmt.Printf("Commit: %s\n", Commit)
fmt.Printf("Zbudowane: %s\n", BuildTime)
},
}
func init() {
rootCmd.AddCommand(versionCmd)
}
Budowanie z informacjami o wersji:
go build -ldflags="-X 'mytasks/cmd.Version=1.0.0' \
-X 'mytasks/cmd.Commit=$(git rev-parse HEAD)' \
-X 'mytasks/cmd.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)'" \
-o mytasks
Przykłady z życia
Popularne narzędzia open source zbudowane z użyciem Cobra i Viper:
- kubectl: Narzędzie CLI dla Kubernetes
- Hugo: Generator statycznych stron
- GitHub CLI (gh): Oficjalne narzędzie CLI GitHub
- Docker CLI: Zarządzanie kontenerami
- Helm: Menedżer pakietów Kubernetes
- Skaffold: Narzędzie do pracy z Kubernetes
- Cobra CLI: Samoobsługowy - Cobra używa samego siebie!
Ponad tradycyjne narzędzia DevOps, możliwości CLI w Go rozszerzają się również do aplikacji AI i uczenia maszynowego. Jeśli jesteś zainteresowany tworzeniem narzędzi CLI, które interagują z dużymi modelami językowymi, sprawdź nasz przewodnik po Ograniczaniu LLM za pomocą strukturalnego wyjścia z użyciem Ollama i Go.
Przydatne zasoby
- Repozytorium GitHub dla Cobra
- Repozytorium GitHub dla Viper
- Przewodnik użytkownika dla Cobra
- Dokumentacja Viper
- Najlepsze praktyki dla aplikacji CLI w Go
Podsumowanie
Cobra i Viper razem tworzą mocną podstawę do budowania profesjonalnych aplikacji CLI w Go. Cobra obsługuje strukturę poleceń, analizę flag i generację pomocy, podczas gdy Viper zarządza konfiguracją z wielu źródeł z inteligentnym priorytetem.
To połączenie umożliwia tworzenie narzędzi CLI, które są:
- Łatwe w użyciu z intuicyjnymi poleceniami i automatyczną pomocą
- Elastyczne z wieloma źródłami konfiguracji
- Profesjonalne z kompletacją powłoki i odpowiednią obsługą błędów
- Utrzywalne z czystą organizacją kodu
- Przenośne na różne platformy
Nie ważne, czy tworzysz narzędzia dla programistów, narzędzia systemowe czy automatyzację DevOps, Cobra i Viper dostarczają solidnej podstawy, którą potrzebujesz do tworzenia aplikacji CLI, które użytkownicy będą uwielbiać.