Costruire applicazioni CLI in Go con Cobra & Viper

Sviluppo CLI in Go con i framework Cobra e Viper

Indice

Le applicazioni dell’interfaccia a riga di comando (CLI) sono strumenti essenziali per gli sviluppatori, gli amministratori di sistema e i professionisti DevOps. Due librerie Go sono diventate lo standard de facto per lo sviluppo CLI in Go: Cobra per la struttura dei comandi e Viper per la gestione delle configurazioni](https://www.glukhov.org/it/post/2025/11/go-cli-applications-with-cobra-and-viper/ “Sviluppo CLI in Go”).

Go è emerso come un linguaggio eccellente per la costruzione di strumenti CLI grazie alle sue prestazioni, alla semplice distribuzione e al supporto multi-piattaforma.

Tetris

Perché scegliere Go per le applicazioni CLI

Go offre vantaggi convincenti per lo sviluppo CLI:

  • Distribuzione singola del binario: Non sono necessari dipendenze di runtime o gestori di pacchetti
  • Esecuzione rapida: La compilazione nativa fornisce prestazioni eccellenti
  • Supporto multi-piattaforma: Facile compilazione per Linux, macOS, Windows e altro
  • Libreria standard potente: Strumenti ricchi per I/O file, rete e elaborazione del testo
  • Concorrenza: Goroutine integrate per operazioni parallele
  • Tipizzazione statica: Cattura gli errori in fase di compilazione

Strumenti CLI popolari costruiti con Go includono Docker, Kubernetes (kubectl), Hugo, Terraform e GitHub CLI. Se sei nuovo a Go o hai bisogno di un riferimento rapido, consulta la nostra Tabella di riferimento per Go per la sintassi essenziale e i pattern di Go.

Introduzione a Cobra

Cobra è una libreria che fornisce un’interfaccia semplice per creare potenti applicazioni CLI moderne. Creato da Steve Francia (spf13), lo stesso autore di Hugo e Viper, Cobra è utilizzato in molti dei progetti Go più popolari.

Funzionalità principali di Cobra

Struttura dei comandi: Cobra implementa il modello di comando, permettendoti di creare applicazioni con comandi e sottocomandi (come git commit o docker run).

Gestione dei flag: Flag locali e persistenti con analisi automatica e conversione di tipo.

Aiuto automatico: Genera testo di aiuto e informazioni sull’uso automaticamente.

Suggerimenti intelligenti: Fornisce suggerimenti quando gli utenti commettono errori di battitura (“Hai inteso ‘status’?”).

Completamenti per shell: Genera script di completamento per bash, zsh, fish e PowerShell.

Output flessibile: Funziona senza problemi con formattatori personalizzati e stili di output.

Introduzione a Viper

Viper è una soluzione completa per la configurazione delle applicazioni Go, progettata per funzionare in modo fluido con Cobra. Gestisce la configurazione da diverse fonti con un ordine di precedenza chiaro.

Funzionalità principali di Viper

Fonti multiple di configurazione:

  • File di configurazione (JSON, YAML, TOML, HCL, INI, envfile, Java properties)
  • Variabili d’ambiente
  • Flag della riga di comando
  • Sistemi di configurazione remota (etcd, Consul)
  • Valori predefiniti

Ordine di precedenza della configurazione:

  1. Chiamate esplicite a Set
  2. Flag della riga di comando
  3. Variabili d’ambiente
  4. File di configurazione
  5. Archivio chiave/valore
  6. Valori predefiniti

Monitoraggio in tempo reale: Monitora i file di configurazione e li ricarica automaticamente quando cambiano.

Conversione di tipo: Conversione automatica in vari tipi Go (stringa, intero, booleano, durata, ecc.).

Inizio: Installazione

Per prima cosa, inizializza un nuovo modulo Go e installa entrambe le librerie:

go mod init myapp
go get -u github.com/spf13/cobra@latest
go get -u github.com/spf13/viper@latest

Opzionalmente, installa il generatore CLI di Cobra per la scaffolding:

go install github.com/spf13/cobra-cli@latest

Costruendo la tua prima applicazione CLI

Costruiamo un esempio pratico: uno strumento CLI per la gestione delle attività con supporto per la configurazione.

Struttura del progetto

mytasks/
├── cmd/
│   ├── root.go
│   ├── add.go
│   ├── list.go
│   └── complete.go
├── config/
│   └── config.go
├── main.go
└── config.yaml

Punto di ingresso principale

// main.go
package main

import "mytasks/cmd"

func main() {
    cmd.Execute()
}

Comando principale con integrazione 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: "Un semplice strumento CLI per la gestione delle attività",
    Long: `MyTasks è uno strumento CLI per la gestione delle attività che ti aiuta a organizzare 
le tue attività quotidiane con facilità. Costruito con Cobra e 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", "", 
        "file di configurazione (predefinito è $HOME/.mytasks.yaml)")
    rootCmd.PersistentFlags().String("db", "", 
        "posizione del file del database")
    
    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("Utilizzo del file di configurazione:", viper.ConfigFileUsed())
    }
}

Aggiunta di sottocomandi

// cmd/add.go
package cmd

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

var priority string

var addCmd = &cobra.Command{
    Use:   "add [descrizione della task]",
    Short: "Aggiungi una nuova task",
    Args:  cobra.MinimumNArgs(1),
    Run: func(cmd *cobra.Command, args []string) {
        task := args[0]
        db := viper.GetString("database")
        
        fmt.Printf("Aggiunta task: %s\n", task)
        fmt.Printf("Priorità: %s\n", priority)
        fmt.Printf("Database: %s\n", db)
        
        // Qui implementeresti l'archiviazione effettiva delle task
    },
}

func init() {
    rootCmd.AddCommand(addCmd)
    
    addCmd.Flags().StringVarP(&priority, "priority", "p", "medium", 
        "Priorità della task (bassa, media, alta)")
}

Comando List

// 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: "Elenca tutte le task",
    Run: func(cmd *cobra.Command, args []string) {
        db := viper.GetString("database")
        
        fmt.Printf("Elenca le task da: %s\n", db)
        fmt.Printf("Mostra completate: %v\n", showCompleted)
        
        // Qui implementeresti l'elenco effettivo delle task
    },
}

func init() {
    rootCmd.AddCommand(listCmd)
    
    listCmd.Flags().BoolVarP(&showCompleted, "completed", "c", false, 
        "Mostra le task completate")
}

Implementazione della persistenza dei dati

Per un’applicazione CLI di gestione delle attività in produzione, dovrai implementare un’archiviazione effettiva dei dati. Mentre stiamo utilizzando una semplice configurazione del percorso del database qui, hai diverse opzioni per persistere i dati:

  • SQLite: Database leggero, senza server perfetto per gli strumenti CLI
  • PostgreSQL/MySQL: Database completi per applicazioni più complesse
  • File JSON/YAML: Archiviazione semplice basata su file per esigenze leggere
  • Database embedded: BoltDB, BadgerDB per l’archiviazione chiave/valore

Se stai lavorando con database relazionali come PostgreSQL, vorrai utilizzare un ORM o un costruttore di query. Per un confronto completo delle librerie Go per database, vedi la nostra guida su Confronto degli ORM Go per PostgreSQL: GORM vs Ent vs Bun vs sqlc.

Configurazione avanzata con Viper

Esempio di file di configurazione

# .mytasks.yaml
database: ~/.mytasks.db
format: table
colors: true

notifications:
  enabled: true
  sound: true

priorities:
  high: red
  medium: yellow
  low: green

Lettura della configurazione annidata

func getNotificationSettings() {
    enabled := viper.GetBool("notifications.enabled")
    sound := viper.GetBool("notifications.sound")
    
    fmt.Printf("Notifiche abilitate: %v\n", enabled)
    fmt.Printf("Suono abilitato: %v\n", sound)
}

func getPriorityColors() map[string]string {
    return viper.GetStringMapString("priorities")
}

Variabili d’ambiente

Viper legge automaticamente le variabili d’ambiente con il prefisso configurato:

export MYTASKS_DATABASE=/tmp/tasks.db
export MYTASKS_NOTIFICATIONS_ENABLED=false
mytasks list

Ricarica della configurazione in tempo reale

func watchConfig() {
    viper.WatchConfig()
    viper.OnConfigChange(func(e fsnotify.Event) {
        fmt.Println("File di configurazione modificato:", e.Name)
        // Ricarica i componenti dipendenti dalla configurazione
    })
}

Linee guida per la migliore pratica

1. Utilizza il generatore di Cobra per la coerenza

cobra-cli init
cobra-cli add serve
cobra-cli add create

2. Organizza i comandi in file separati

Mantieni ogni comando in un file separato nella directory cmd/ per la manutenibilità.

3. Sfrutta i flag persistenti

Utilizza i flag persistenti per le opzioni che si applicano a tutti i sottocomandi:

rootCmd.PersistentFlags().StringP("output", "o", "text", 
    "Formato di output (testo, json, yaml)")

4. Implementa un’adeguata gestione degli errori

var rootCmd = &cobra.Command{
    Use:   "myapp",
    Short: "La mia applicazione",
    RunE: func(cmd *cobra.Command, args []string) error {
        if err := doSomething(); err != nil {
            return fmt.Errorf("fallito nell'eseguire qualcosa: %w", err)
        }
        return nil
    },
}

5. Fornisci testi di aiuto significativi

var cmd = &cobra.Command{
    Use:   "deploy [ambiente]",
    Short: "Deploys l'applicazione nell'ambiente specificato",
    Long: `Deploys i build e deploy dell'applicazione nell'ambiente 
specificato. Gli ambienti supportati sono:
  - sviluppo (dev)
  - staging
  - produzione (prod)`,
    Example: `  myapp deploy staging
  myapp deploy produzione --version=1.2.3`,
}

6. Imposta valori predefiniti sensati

func init() {
    viper.SetDefault("port", 8080)
    viper.SetDefault("timeout", "30s")
    viper.SetDefault("retries", 3)
}

7. Valida la configurazione

func validateConfig() error {
    port := viper.GetInt("port")
    if port < 1024 || port > 65535 {
        return fmt.Errorf("porta non valida: %d", port)
    }
    return nil
}

Test delle applicazioni CLI

Test dei comandi

// 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("esecuzione del comando fallita: %v", err)
    }
}

Test con diverse configurazioni

func TestWithConfig(t *testing.T) {
    viper.Set("database", "/tmp/test.db")
    viper.Set("debug", true)
    
    // Esegui i tuoi test
    
    viper.Reset() // Pulizia
}

Generazione di script di completamento per shell

Cobra può generare script di completamento per diverse shell:

// cmd/completion.go
package cmd

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

var completionCmd = &cobra.Command{
    Use:   "completion [bash|zsh|fish|powershell]",
    Short: "Genera lo script di completamento",
    Long: `Per caricare i completamenti:

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)
}

Costruzione e distribuzione

Costruisci per diverse piattaforme

# 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

Riduci la dimensione del binario

go build -ldflags="-s -w" -o mytasks

Aggiungi informazioni sulla versione

// 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: "Stampa le informazioni sulla versione",
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Printf("Versione: %s\n", Version)
        fmt.Printf("Commit: %s\n", Commit)
        fmt.Printf("Costruito: %s\n", BuildTime)
    },
}

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

Costruisci con informazioni sulla versione:

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

Esempi reali

Strumenti open source popolari costruiti con Cobra e Viper:

  • kubectl: Strumento CLI di Kubernetes
  • Hugo: Generatore di siti statici
  • GitHub CLI (gh): CLI ufficiale di GitHub
  • Docker CLI: Gestione dei container
  • Helm: Gestore di pacchetti Kubernetes
  • Skaffold: Strumento per il flusso di lavoro di sviluppo Kubernetes
  • Cobra CLI: Auto-hosting - Cobra si utilizza da solo!

Oltre agli strumenti tradizionali DevOps, le capacità CLI di Go si estendono anche alle applicazioni di intelligenza artificiale e machine learning. Se sei interessato a costruire strumenti CLI che interagiscono con i modelli linguistici di grandi dimensioni, consulta la nostra guida su Limitare gli LLM con Output Strutturato utilizzando Ollama e Go, che mostra come costruire applicazioni Go che funzionano con i modelli AI.

Risorse utili

Conclusione

Cobra e Viper insieme forniscono una solida base per costruire applicazioni CLI professionali in Go. Cobra gestisce la struttura dei comandi, l’analisi dei flag e la generazione dell’aiuto, mentre Viper gestisce la configurazione da diverse fonti con un’adeguata precedenza.

Questa combinazione ti permette di creare strumenti CLI che siano:

  • Facili da usare con comandi intuitivi e aiuto automatico
  • Flessibili con diverse fonti di configurazione
  • Professionali con completamenti per shell e corretta gestione degli errori
  • Manutenibili con un’organizzazione del codice pulita
  • Portabili su diverse piattaforme

Che tu stia costruendo strumenti per sviluppatori, utilità di sistema o automazione DevOps, Cobra e Viper forniscono la solida base necessaria per creare applicazioni CLI che gli utenti ameranno.