Construyendo aplicaciones CLI en Go con Cobra y Viper
Desarrollo de CLI en Go con los marcos Cobra y Viper
Las aplicaciones de interfaz de línea de comandos (CLI) son herramientas esenciales para desarrolladores, administradores de sistemas y profesionales de DevOps.
Dos bibliotecas de Go han se convertido en el estándar de facto para el desarrollo de CLI en Go: Cobra para la estructura de comandos y Viper para la gestión de configuración.
Go ha emergido como un excelente lenguaje para construir herramientas de CLI debido a su rendimiento, despliegue sencillo y soporte multiplataforma.

¿Por qué elegir Go para aplicaciones CLI?
Go ofrece ventajas atractivas para el desarrollo de CLI:
- Distribución de un solo binario: No se requieren dependencias de tiempo de ejecución ni gestores de paquetes
- Ejecución rápida: La compilación nativa proporciona un excelente rendimiento
- Soporte multiplataforma: Compilación fácil para Linux, macOS, Windows y más
- Biblioteca estándar robusta: Herramientas ricas para E/S de archivos, redes y procesamiento de texto
- Concurrencia: Goroutines integradas para operaciones paralelas
- Tipado estático: Captura de errores en tiempo de compilación
Herramientas CLI populares construidas con Go incluyen Docker, Kubernetes (kubectl), Hugo, Terraform y GitHub CLI. Si eres nuevo en Go o necesitas una referencia rápida, consulta nuestra Hoja de trucos de Go para sintaxis y patrones esenciales de Go.
Introducción a Cobra
Cobra es una biblioteca que proporciona una interfaz sencilla para crear poderosas aplicaciones CLI modernas. Creada por Steve Francia (spf13), el mismo autor detrás de Hugo y Viper, Cobra se utiliza en muchos de los proyectos más populares de Go.
Características clave de Cobra
Estructura de comandos: Cobra implementa el patrón de comandos, permitiéndote crear aplicaciones con comandos y subcomandos (como git commit o docker run).
Manejo de banderas: Banderas locales y persistentes con análisis automático y conversión de tipos.
Ayuda automática: Genera texto de ayuda e información de uso automáticamente.
Sugerencias inteligentes: Proporciona sugerencias cuando los usuarios cometen errores tipográficos ("¿Quiso decir ‘status’?").
Completación de shell: Genera scripts de completación para bash, zsh, fish y PowerShell.
Salida flexible: Trabaja de forma fluida con formateadores personalizados y estilos de salida.
Introducción a Viper
Viper es una solución completa de configuración para aplicaciones de Go, diseñada para trabajar de forma fluida con Cobra. Maneja la configuración de múltiples fuentes con un orden claro de precedencia.
Características clave de Viper
Múltiples fuentes de configuración:
- Archivos de configuración (JSON, YAML, TOML, HCL, INI, envfile, propiedades de Java)
- Variables de entorno
- Banderas de línea de comandos
- Sistemas de configuración remota (etcd, Consul)
- Valores predeterminados
Orden de prioridad de configuración:
- Llamadas explícitas a Set
- Banderas de línea de comandos
- Variables de entorno
- Archivo de configuración
- Almacén de clave/valor
- Valores predeterminados
Vigilancia en vivo: Monitorea archivos de configuración y los recarga automáticamente cuando ocurren cambios.
Conversión de tipos: Conversión automática a varios tipos de Go (cadena, entero, booleano, duración, etc.).
Comenzando: Instalación
Primero, inicializa un nuevo módulo de Go e instala ambas bibliotecas:
go mod init myapp
go get -u github.com/spf13/cobra@latest
go get -u github.com/spf13/viper@latest
Opcionalmente, instala el generador de CLI de Cobra para scaffolding:
go install github.com/spf13/cobra-cli@latest
Construyendo tu primera aplicación CLI
Vamos a construir un ejemplo práctico: una herramienta CLI de gestión de tareas con soporte de configuración.
Estructura del proyecto
mytasks/
├── cmd/
│ ├── root.go
│ ├── add.go
│ ├── list.go
│ └── complete.go
├── config/
│ └── config.go
├── main.go
└── config.yaml
Punto de entrada principal
// main.go
package main
import "mytasks/cmd"
func main() {
cmd.Execute()
}
Comando principal con integración de 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: "Una herramienta CLI simple para gestión de tareas",
Long: `MyTasks es una herramienta CLI para gestión de tareas que te ayuda a organizar
tus tareas diarias con facilidad. Construida con Cobra y 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", "",
"archivo de configuración (por defecto es $HOME/.mytasks.yaml)")
rootCmd.PersistentFlags().String("db", "",
"ubicación del archivo de base de datos")
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("Usando archivo de configuración:", viper.ConfigFileUsed())
}
}
Añadiendo subcomandos
// cmd/add.go
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var priority string
var addCmd = &cobra.Command{
Use: "add [descripción de la tarea]",
Short: "Añadir una nueva tarea",
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
task := args[0]
db := viper.GetString("database")
fmt.Printf("Añadiendo tarea: %s\n", task)
fmt.Printf("Prioridad: %s\n", priority)
fmt.Printf("Base de datos: %s\n", db)
// Aquí implementarías el almacenamiento real de tareas
},
}
func init() {
rootCmd.AddCommand(addCmd)
addCmd.Flags().StringVarP(&priority, "priority", "p", "medium",
"Prioridad de la tarea (baja, media, alta)")
}
Comando de lista
// 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: "Listar todas las tareas",
Run: func(cmd *cobra.Command, args []string) {
db := viper.GetString("database")
fmt.Printf("Listando tareas desde: %s\n", db)
fmt.Printf("Mostrar completadas: %v\n", showCompleted)
// Aquí implementarías la lista real de tareas
},
}
func init() {
rootCmd.AddCommand(listCmd)
listCmd.Flags().BoolVarP(&showCompleted, "completed", "c", false,
"Mostrar tareas completadas")
}
Implementando persistencia de datos
Para una aplicación CLI de gestión de tareas en producción, necesitarás implementar un almacenamiento real de datos. Mientras estamos usando una ruta de base de datos simple aquí, tienes varias opciones para persistir datos:
- SQLite: Base de datos ligera, sin servidor perfecta para herramientas CLI
- PostgreSQL/MySQL: Bases de datos completas para aplicaciones más complejas
- Archivos JSON/YAML: Almacenamiento basado en archivos simple para necesidades ligeras
- Bases de datos embebidas: BoltDB, BadgerDB para almacenamiento de clave-valor
Si estás trabajando con bases de datos relacionales como PostgreSQL, querrás usar un ORM o constructor de consultas. Para una comparación completa de bibliotecas de bases de datos en Go, consulta nuestra guía sobre Comparando ORMs de Go para PostgreSQL: GORM vs Ent vs Bun vs sqlc.
Configuración avanzada con Viper
Ejemplo de archivo de configuración
# .mytasks.yaml
database: ~/.mytasks.db
format: table
colors: true
notifications:
enabled: true
sound: true
priorities:
high: red
medium: yellow
low: green
Leyendo configuración anidada
func getNotificationSettings() {
enabled := viper.GetBool("notifications.enabled")
sound := viper.GetBool("notifications.sound")
fmt.Printf("Notificaciones habilitadas: %v\n", enabled)
fmt.Printf("Sonido habilitado: %v\n", sound)
}
func getPriorityColors() map[string]string {
return viper.GetStringMapString("priorities")
}
Variables de entorno
Viper lee automáticamente las variables de entorno con el prefijo configurado:
export MYTASKS_DATABASE=/tmp/tasks.db
export MYTASKS_NOTIFICATIONS_ENABLED=false
mytasks list
Recarga de configuración en vivo
func watchConfig() {
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("Archivo de configuración cambiado:", e.Name)
// Recargar componentes dependientes de la configuración
})
}
Buenas prácticas
1. Usa el generador de Cobra para consistencia
cobra-cli init
cobra-cli add serve
cobra-cli add create
2. Organiza los comandos en archivos separados
Mantén cada comando en su propio archivo bajo el directorio cmd/ para mantener la legibilidad.
3. Aprovecha las banderas persistentes
Usa banderas persistentes para opciones que se aplican a todos los subcomandos:
rootCmd.PersistentFlags().StringP("output", "o", "text",
"Formato de salida (texto, json, yaml)")
4. Implementa un manejo adecuado de errores
var rootCmd = &cobra.Command{
Use: "myapp",
Short: "Mi aplicación",
RunE: func(cmd *cobra.Command, args []string) error {
if err := doSomething(); err != nil {
return fmt.Errorf("falló al hacer algo: %w", err)
}
return nil
},
}
5. Proporciona texto de ayuda significativo
var cmd = &cobra.Command{
Use: "deploy [entorno]",
Short: "Despliega la aplicación en el entorno especificado",
Long: `Despliega construcciones y despliega tu aplicación en el
entorno especificado. Entornos admitidos son:
- desarrollo (dev)
- staging
- producción (prod)`,
Example: ` myapp deploy staging
myapp deploy production --version=1.2.3`,
}
6. Establece valores predeterminados sensatos
func init() {
viper.SetDefault("port", 8080)
viper.SetDefault("timeout", "30s")
viper.SetDefault("retries", 3)
}
7. Valida la configuración
func validateConfig() error {
port := viper.GetInt("port")
if port < 1024 || port > 65535 {
return fmt.Errorf("puerto inválido: %d", port)
}
return nil
}
Pruebas de aplicaciones CLI
Pruebas de comandos
// 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("ejecución del comando fallida: %v", err)
}
}
Pruebas con diferentes configuraciones
func TestWithConfig(t *testing.T) {
viper.Set("database", "/tmp/test.db")
viper.Set("debug", true)
// Ejecuta tus pruebas
viper.Reset() // Limpieza
}
Generando scripts de completación de shell
Cobra puede generar scripts de completación para varios shells:
// cmd/completion.go
package cmd
import (
"os"
"github.com/spf13/cobra"
)
var completionCmd = &cobra.Command{
Use: "completion [bash|zsh|fish|powershell]",
Short: "Generar script de completación",
Long: `Para cargar completaciones:
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)
}
Construcción y distribución
Construir para múltiples plataformas
# 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
Reducir el tamaño del binario
go build -ldflags="-s -w" -o mytasks
Añadir información de versión
// 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: "Imprimir información de versión",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Versión: %s\n", Version)
fmt.Printf("Commit: %s\n", Commit)
fmt.Printf("Compilado: %s\n", BuildTime)
},
}
func init() {
rootCmd.AddCommand(versionCmd)
}
Compilar con información de versión:
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
Ejemplos del mundo real
Herramientas de código abierto populares construidas con Cobra y Viper:
- kubectl: Herramienta de línea de comandos de Kubernetes
- Hugo: Generador de sitios estáticos
- GitHub CLI (gh): CLI oficial de GitHub
- Docker CLI: Gestión de contenedores
- Helm: Gestor de paquetes de Kubernetes
- Skaffold: Herramienta de flujo de trabajo de desarrollo de Kubernetes
- Cobra CLI: Autohospedado - Cobra se usa a sí mismo!
Más allá de las herramientas tradicionales de DevOps, las capacidades de CLI de Go se extienden a aplicaciones de inteligencia artificial y aprendizaje automático. Si estás interesado en construir herramientas CLI que interactúen con modelos de lenguaje grandes, consulta nuestra guía sobre Restringir LLMs con salida estructurada usando Ollama y Go, que muestra cómo construir aplicaciones Go que trabajen con modelos de IA.
Recursos útiles
- Repositorio de GitHub de Cobra
- Repositorio de GitHub de Viper
- Guía del usuario de Cobra
- Documentación de Viper
- Mejores prácticas para aplicaciones CLI en Go
Conclusión
Cobra y Viper juntos proporcionan una base poderosa para construir aplicaciones CLI profesionales en Go. Cobra maneja la estructura de comandos, el análisis de banderas y la generación de ayuda, mientras que Viper gestiona la configuración de múltiples fuentes con precedencia inteligente.
Esta combinación te permite crear herramientas CLI que sean:
- Fáciles de usar con comandos intuitivos y ayuda automática
- Flexibles con múltiples fuentes de configuración
- Profesionales con completación de shell y manejo adecuado de errores
- Mantenibles con organización de código limpia
- Portables entre diferentes plataformas
Ya estés construyendo herramientas para desarrolladores, utilidades del sistema o automatización de DevOps, Cobra y Viper proporcionan la base sólida que necesitas para crear aplicaciones CLI que los usuarios amarán.