Criando Aplicações CLI em Go com Cobra e Viper
Desenvolvimento de CLI em Go com os frameworks Cobra e Viper
Aplicações de interface de linha de comando (CLI) são ferramentas essenciais para desenvolvedores, administradores de sistemas e profissionais de DevOps. Duas bibliotecas Go tornaram-se o padrão de facto para desenvolvimento de CLI em Go: Cobra para estrutura de comandos e Viper para gerenciamento de configuração.
Go emergiu como uma excelente linguagem para construir ferramentas de CLI devido ao seu desempenho, implantação simples e suporte a várias plataformas.

Por que escolher Go para Aplicações de CLI
Go oferece vantagens convincentes para o desenvolvimento de CLI:
- Distribuição em Binário Único: Sem dependências de tempo de execução ou gerenciadores de pacotes necessários
- Execução Rápida: Compilação nativa oferece excelente desempenho
- Suporte Multiplataforma: Compilação fácil para Linux, macOS, Windows e mais
- Biblioteca Padrão Robusta: Ferramentas ricas para E/S de arquivos, rede e processamento de texto
- Concorrência: Goroutines nativas para operações paralelas
- Tipagem Estática: Detecta erros em tempo de compilação
Ferramentas de CLI populares construídas com Go incluem Docker, Kubernetes (kubectl), Hugo, Terraform e GitHub CLI. Se você é novo em Go ou precisa de uma referência rápida, consulte nossa Lista de Referência do Go para sintaxe e padrões essenciais do Go.
Introdução ao Cobra
Cobra é uma biblioteca que fornece uma interface simples para criar aplicações de CLI modernas e poderosas. Criada por Steve Francia (spf13), o mesmo autor por trás do Hugo e do Viper, o Cobra é usado em muitos dos projetos Go mais populares.
Principais Funcionalidades do Cobra
Estrutura de Comandos: Cobra implementa o padrão de comandos, permitindo que você crie aplicações com comandos e subcomandos (como git commit ou docker run).
Tratamento de Flags: Flags locais e persistentes com parsing automático e conversão de tipo.
Ajuda Automática: Gera texto de ajuda e informações de uso automaticamente.
Sugestões Inteligentes: Fornece sugestões quando os usuários cometem erros de digitação (“Você quis dizer ‘status’?”).
Completas de Shell: Gera scripts de conclusão para bash, zsh, fish e PowerShell.
Saída Flexível: Funciona perfeitamente com formatares e estilos de saída personalizados.
Introdução ao Viper
Viper é uma solução completa de configuração para aplicações Go, projetada para funcionar perfeitamente com o Cobra. Ele gerencia configurações de múltiplas fontes com uma ordem de precedência clara.
Principais Funcionalidades do Viper
Múltiplas Fontes de Configuração:
- Arquivos de configuração (JSON, YAML, TOML, HCL, INI, envfile, propriedades Java)
- Variáveis de ambiente
- Flags de linha de comando
- Sistemas de configuração remotos (etcd, Consul)
- Valores padrão
Ordem de Prioridade de Configuração:
- Chamadas explícitas para Set
- Flags de linha de comando
- Variáveis de ambiente
- Arquivo de configuração
- Armazenamento de chave/valor
- Valores padrão
Monitoramento em Tempo Real: Monitora arquivos de configuração e recarrega automaticamente quando ocorrem alterações.
Conversão de Tipo: Conversão automática para vários tipos Go (string, int, bool, duration, etc.).
Primeiros Passos: Instalação
Primeiro, inicialize um novo módulo Go e instale ambas as bibliotecas:
go mod init myapp
go get -u github.com/spf13/cobra@latest
go get -u github.com/spf13/viper@latest
Opcionalmente, instale o gerador CLI Cobra para criar a estrutura:
go install github.com/spf13/cobra-cli@latest
Construindo Sua Primeira Aplicação de CLI
Vamos construir um exemplo prático: uma ferramenta de CLI de gerenciamento de tarefas com suporte a configuração.
Estrutura do Projeto
mytasks/
├── cmd/
│ ├── root.go
│ ├── add.go
│ ├── list.go
│ └── complete.go
├── config/
│ └── config.go
├── main.go
└── config.yaml
Ponto de Entrada Principal
// main.go
package main
import "mytasks/cmd"
func main() {
cmd.Execute()
}
Comando Raiz com Integração 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: "Um CLI simples de gerenciamento de tarefas",
Long: `MyTasks é um gerenciador de tarefas de CLI que ajuda você a organizar
suas tarefas diárias com facilidade. Construído com 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", "",
"arquivo de configuração (padrão é $HOME/.mytasks.yaml)")
rootCmd.PersistentFlags().String("db", "",
"localização do arquivo de banco de dados")
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 arquivo de configuração:", viper.ConfigFileUsed())
}
}
Adicionando 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 [descrição da tarefa]",
Short: "Adicionar uma nova tarefa",
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
task := args[0]
db := viper.GetString("database")
fmt.Printf("Adicionando tarefa: %s\n", task)
fmt.Printf("Prioridade: %s\n", priority)
fmt.Printf("Banco de dados: %s\n", db)
// Aqui você implementaria o armazenamento real da tarefa
},
}
func init() {
rootCmd.AddCommand(addCmd)
addCmd.Flags().StringVarP(&priority, "priority", "p", "medium",
"Prioridade da tarefa (low, medium, high)")
}
Comando Listar
// 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 as tarefas",
Run: func(cmd *cobra.Command, args []string) {
db := viper.GetString("database")
fmt.Printf("Listando tarefas de: %s\n", db)
fmt.Printf("Mostrar concluídas: %v\n", showCompleted)
// Aqui você implementaria a listagem real das tarefas
},
}
func init() {
rootCmd.AddCommand(listCmd)
listCmd.Flags().BoolVarP(&showCompleted, "completed", "c", false,
"Mostrar tarefas concluídas")
}
Implementando Persistência de Dados
Para uma ferramenta de gerenciamento de tarefas de produção, você precisará implementar armazenamento de dados real. Embora estejamos usando uma configuração simples de caminho de banco de dados aqui, você tem várias opções para persistir dados:
- SQLite: Banco de dados leve e sem servidor, perfeito para ferramentas de CLI
- PostgreSQL/MySQL: Bancos de dados completos para aplicações mais complexas
- Arquivos JSON/YAML: Armazenamento baseado em arquivos simples para necessidades leves
- Bancos de dados embutidos: BoltDB, BadgerDB para armazenamento chave-valor
Se você estiver trabalhando com bancos de dados relacionais como PostgreSQL, você quererá usar um ORM ou construtor de consultas. Para uma comparação abrangente de bibliotecas de banco de dados Go, veja nosso guia sobre Comparando ORMs Go para PostgreSQL: GORM vs Ent vs Bun vs sqlc.
Configuração Avançada com Viper
Exemplo de Arquivo de Configuração
# .mytasks.yaml
database: ~/.mytasks.db
format: table
colors: true
notifications:
enabled: true
sound: true
priorities:
high: red
medium: yellow
low: green
Lendo Configuração Aninhada
func getNotificationSettings() {
enabled := viper.GetBool("notifications.enabled")
sound := viper.GetBool("notifications.sound")
fmt.Printf("Notificações habilitadas: %v\n", enabled)
fmt.Printf("Som habilitado: %v\n", sound)
}
func getPriorityColors() map[string]string {
return viper.GetStringMapString("priorities")
}
Variáveis de Ambiente
Viper lê automaticamente variáveis de ambiente com o prefixo configurado:
export MYTASKS_DATABASE=/tmp/tasks.db
export MYTASKS_NOTIFICATIONS_ENABLED=false
mytasks list
Recarga de Configuração em Tempo Real
func watchConfig() {
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("Arquivo de configuração alterado:", e.Name)
// Recarregar componentes dependentes da configuração
})
}
Melhores Práticas
1. Usar o Gerador Cobra para Consistência
cobra-cli init
cobra-cli add serve
cobra-cli add create
2. Organizar Comandos em Arquivos Separados
Mantenha cada comando em seu próprio arquivo sob o diretório cmd/ para facilitar a manutenção.
3. Aproveitar Flags Persistentes
Use flags persistentes para opções que se aplicam a todos os subcomandos:
rootCmd.PersistentFlags().StringP("output", "o", "text",
"Formato de saída (text, json, yaml)")
4. Implementar Tratamento de Erros Adequado
var rootCmd = &cobra.Command{
Use: "myapp",
Short: "Minha aplicação",
RunE: func(cmd *cobra.Command, args []string) error {
if err := doSomething(); err != nil {
return fmt.Errorf("falha ao fazer algo: %w", err)
}
return nil
},
}
5. Fornecer Texto de Ajuda Significativo
var cmd = &cobra.Command{
Use: "deploy [ambiente]",
Short: "Implantar aplicação no ambiente especificado",
Long: `Implanta builds e implanta sua aplicação no
ambiente especificado. Ambientes suportados são:
- development (dev)
- staging
- production (prod)`,
Example: ` myapp deploy staging
myapp deploy production --version=1.2.3`,
}
6. Definir Padrões Sensatos
func init() {
viper.SetDefault("port", 8080)
viper.SetDefault("timeout", "30s")
viper.SetDefault("retries", 3)
}
7. Validar Configuração
func validateConfig() error {
port := viper.GetInt("port")
if port < 1024 || port > 65535 {
return fmt.Errorf("porta inválida: %d", port)
}
return nil
}
Testando Aplicações de CLI
Testando 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("execução do comando falhou: %v", err)
}
}
Testando com Diferentes Configurações
func TestWithConfig(t *testing.T) {
viper.Set("database", "/tmp/test.db")
viper.Set("debug", true)
// Execute seus testes
viper.Reset() // Limpeza
}
Gerando Completas de Shell
Cobra pode gerar scripts de conclusão para vários shells:
// cmd/completion.go
package cmd
import (
"os"
"github.com/spf13/cobra"
)
var completionCmd = &cobra.Command{
Use: "completion [bash|zsh|fish|powershell]",
Short: "Gerar script de conclusão",
Long: `Para carregar as conclusões:
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)
}
Compilação e Distribuição
Compilar para Múltiplas 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
Reduzir Tamanho do Binário
go build -ldflags="-s -w" -o mytasks
Adicionar Informações de Versão
// 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 informações de versão",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Versão: %s\n", Version)
fmt.Printf("Commit: %s\n", Commit)
fmt.Printf("Compilado: %s\n", BuildTime)
},
}
func init() {
rootCmd.AddCommand(versionCmd)
}
Compilar com informações de versão:
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
Exemplos do Mundo Real
Ferramentas de código aberto populares construídas com Cobra e Viper:
- kubectl: Ferramenta de linha de comando do Kubernetes
- Hugo: Gerador de sites estáticos
- GitHub CLI (gh): CLI oficial do GitHub
- Docker CLI: Gerenciamento de containers
- Helm: Gerenciador de pacotes do Kubernetes
- Skaffold: Ferramenta de fluxo de trabalho de desenvolvimento do Kubernetes
- Cobra CLI: Auto-hospedagem - Cobra usa a si mesma!
Além das ferramentas tradicionais de DevOps, as capacidades de CLI do Go se estendem a aplicações de IA e aprendizado de máquina. Se você estiver interessado em construir ferramentas de CLI que interajam com grandes modelos de linguagem, consulte nosso guia sobre Constraining LLMs with Structured Output using Ollama and Go, que mostra como construir aplicações Go que trabalham com modelos de IA.
Recursos Úteis
- Repositório GitHub do Cobra
- Repositório GitHub do Viper
- Guia do Usuário do Cobra
- Documentação do Viper
- Melhores Práticas para Aplicações CLI Go
Conclusão
Cobra e Viper juntos fornecem uma base poderosa para construir aplicações profissionais de CLI em Go. O Cobra gerencia a estrutura de comandos, parsing de flags e geração de ajuda, enquanto o Viper gerencia a configuração de múltiplas fontes com precedência inteligente.
Essa combinação permite que você crie ferramentas de CLI que são:
- Fáceis de usar com comandos intuitivos e ajuda automática
- Flexíveis com múltiplas fontes de configuração
- Profissionais com conclusões de shell e tratamento de erros adequado
- Mantíveis com organização de código limpa
- Portáveis entre diferentes plataformas
Seja você construa ferramentas para desenvolvedores, utilitários de sistema ou automação de DevOps, Cobra e Viper fornecem a base sólida que você precisa para criar aplicações de CLI que os usuários amarão.