CLI-apps bouwen in Go met Cobra & Viper
CLI-ontwikkeling in Go met Cobra- en Viper-frameworks
Command-line interface (CLI) toepassingen zijn essentiële tools voor ontwikkelaars, systeembeheerders en DevOps-professionals. Twee Go-bibliotheken zijn geworden de standaard voor CLI-ontwikkeling in Go: Cobra voor commandostructuur en Viper voor configuratiebeheer.
Go is ontstaan als een uitstekende taal voor het bouwen van CLI-tools vanwege de prestaties, eenvoudige implementatie en cross-platform ondersteuning.

Waarom Go kiezen voor CLI-toepassingen
Go biedt overtuigende voordelen voor CLI-ontwikkeling:
- Eén-binair distributie: Geen runtime-afhankelijkheden of pakketbeheerders vereist
- Snelle uitvoering: Native compilatie biedt uitstekende prestaties
- Cross-platform ondersteuning: Eenvoudige compilatie voor Linux, macOS, Windows en meer
- Sterke standaardbibliotheek: Rijke tooling voor bestands-I/O, netwerken en tekstverwerking
- Concurrentie: Ingebouwde goroutines voor parallele bewerkingen
- Statische typen: Fouten opvangen tijdens de compilatie
Populaire CLI-tools gebouwd met Go omvatten Docker, Kubernetes (kubectl), Hugo, Terraform en GitHub CLI. Als je nieuw bent in Go of een snelle naslag nodig hebt, bekijk dan onze Go Cheat Sheet voor essentiële Go-syntaxis en patronen.
Inleiding tot Cobra
Cobra is een bibliotheek die een eenvoudige interface biedt voor het maken van krachtige moderne CLI-toepassingen. Gemaakt door Steve Francia (spf13), de zelfde auteur achter Hugo en Viper, wordt Cobra gebruikt in veel van de populairste Go-projecten.
Belangrijke Cobra-functies
Commandostructuur: Cobra implementeert het commandompatroon, waardoor je toepassingen kunt maken met commando’s en subcommando’s (zoals git commit of docker run).
Flagbeheer: Zowel lokale als blijvende flags met automatische parsing en typeconversie.
Automatische hulp: Genereert hulp tekst en gebruiksinformatie automatisch.
Slimme suggesties: Geeft suggesties wanneer gebruikers typos maken (“Meende je ‘status’?”).
Shell-completies: Genereert compleet scripts voor bash, zsh, fish en PowerShell.
Flexibele uitvoer: Werkt naadloos met aangepaste formatters en uitvoerstijlen.
Inleiding tot Viper
Viper is een volledige configuratieoplossing voor Go-toepassingen, ontworpen om naadloos te werken met Cobra. Het beheert configuratie van meerdere bronnen met een duidelijke voorkeursvolgorde.
Belangrijke Viper-functies
Meerdere configuratiebronnen:
- Configuratiebestanden (JSON, YAML, TOML, HCL, INI, envfile, Java properties)
- Omgevingsvariabelen
- Commando-flags
- Verwijderde configuratiesystemen (etcd, Consul)
- Standaardwaarden
Configuratievoorkeursvolgorde:
- Explicit calls to Set
- Commando-flags
- Omgevingsvariabelen
- Configuratiebestand
- Sleutel/waardeopslag
- Standaardwaarden
Live watching: Bewaak configuratiebestanden en herlaad automatisch wanneer wijzigingen optreden.
Typeconversie: Automatische conversie naar verschillende Go-typen (string, int, bool, duur, enz.).
Aan de slag: Installatie
Eerst, initialiseer een nieuw Go-modul en installeer beide bibliotheken:
go mod init myapp
go get -u github.com/spf13/cobra@latest
go get -u github.com/spf13/viper@latest
Optioneel, installeer de Cobra CLI-generator voor het opzetten:
go install github.com/spf13/cobra-cli@latest
Bouwen van je eerste CLI-toepassing
Laten we een praktijkvoorbeeld bouwen: een taakbeheer CLI-tool met configuratieondersteuning.
Projectstructuur
mytasks/
├── cmd/
│ ├── root.go
│ ├── add.go
│ ├── list.go
│ └── complete.go
├── config/
│ └── config.go
├── main.go
└── config.yaml
Hoofdinstelling
// main.go
package main
import "mytasks/cmd"
func main() {
cmd.Execute()
}
Hoofdcommando met Viper-integratie
// 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: "Een eenvoudig taakbeheer CLI",
Long: `MyTasks is een CLI-taakbeheerder die je helpt om
je dagelijkse taken gemakkelijk te organiseren. Gemaakt met Cobra en 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", "",
"configbestand (standaard is $HOME/.mytasks.yaml)")
rootCmd.PersistentFlags().String("db", "",
"locatie van het databestand")
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("Gebruikte configuratiebestand:", viper.ConfigFileUsed())
}
}
Subcommando’s toevoegen
// cmd/add.go
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var priority string
var addCmd = &cobra.Command{
Use: "add [taakbeschrijving]",
Short: "Voeg een nieuwe taak toe",
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
taak := args[0]
db := viper.GetString("database")
fmt.Printf("Toevoegen van taak: %s\n", taak)
fmt.Printf("Prioriteit: %s\n", priority)
fmt.Printf("Database: %s\n", db)
// Hier zou je de werkelijke taakopslag implementeren
},
}
func init() {
rootCmd.AddCommand(addCmd)
addCmd.Flags().StringVarP(&priority, "priority", "p", "medium",
"Taakprioriteit (laag, gemiddeld, hoog)")
}
Lijstcommando
// 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: "Toon alle taken",
Run: func(cmd *cobra.Command, args []string) {
db := viper.GetString("database")
fmt.Printf("Lijst met taken van: %s\n", db)
fmt.Printf("Toon voltooide: %v\n", showCompleted)
// Hier zou je de werkelijke taaklijst implementeren
},
}
func init() {
rootCmd.AddCommand(listCmd)
listCmd.Flags().BoolVarP(&showCompleted, "completed", "c", false,
"Toon voltooide taken")
}
Implementatie van gegevensopslag
Voor een productie taakbeheer CLI, zul je werkelijke gegevensopslag moeten implementeren. Hoewel we hier een eenvoudige databestandslocatie gebruiken, heb je verschillende opties voor het opslaan van gegevens:
- SQLite: Lichte, serverloze database ideaal voor CLI-tools
- PostgreSQL/MySQL: Volledig uitgeruste databases voor complexere toepassingen
- JSON/YAML-bestanden: Eenvoudige bestandsgebaseerde opslag voor lichte behoeften
- Ingewikkelde databases: BoltDB, BadgerDB voor sleutel-waardeopslag
Als je werkt met relationele databases zoals PostgreSQL, wil je een ORM of querybuilder gebruiken. Voor een uitgebreid overzicht van Go-databasebibliotheken, zie onze gids over Vergelijken van Go-ORMs voor PostgreSQL: GORM vs Ent vs Bun vs sqlc.
Geavanceerde configuratie met Viper
Voorbeeld van configuratiebestand
# .mytasks.yaml
database: ~/.mytasks.db
format: table
colors: true
notifications:
enabled: true
sound: true
priorities:
high: red
medium: yellow
low: green
Lezen van geneste configuratie
func getNotificationSettings() {
enabled := viper.GetBool("notifications.enabled")
sound := viper.GetBool("notifications.sound")
fmt.Printf("Notificaties ingeschakeld: %v\n", enabled)
fmt.Printf("Geluid ingeschakeld: %v\n", sound)
}
func getPriorityColors() map[string]string {
return viper.GetStringMapString("priorities")
}
Omgevingsvariabelen
Viper leest automatisch omgevingsvariabelen met de geconfigureerde voorvoegsel:
export MYTASKS_DATABASE=/tmp/tasks.db
export MYTASKS_NOTIFICATIONS_ENABLED=false
mytasks list
Live configuratieherlaad
func watchConfig() {
viper.WatchConfig()
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("Configbestand gewijzigd:", e.Name)
// Herlaad configuratie-afhankelijke componenten
})
}
Beste praktijken
1. Gebruik de Cobra-generator voor consistentie
cobra-cli init
cobra-cli add serve
cobra-cli add create
2. Organiseer commando’s in aparte bestanden
Houd elk commando in zijn eigen bestand onder de cmd/ map voor onderhoudbaarheid.
3. Gebruik blijvende flags
Gebruik blijvende flags voor opties die van toepassing zijn op alle subcommando’s:
rootCmd.PersistentFlags().StringP("output", "o", "text",
"Uitvoerformaat (tekst, json, yaml)")
4. Implementeer juiste foutafhandeling
var rootCmd = &cobra.Command{
Use: "myapp",
Short: "Mijn toepassing",
RunE: func(cmd *cobra.Command, args []string) error {
if err := doSomething(); err != nil {
return fmt.Errorf("mislukt om iets te doen: %w", err)
}
return nil
},
}
5. Geef betekenisvolle hulp tekst
var cmd = &cobra.Command{
Use: "deploy [omgeving]",
Short: "Implementeer toepassing in de opgegeven omgeving",
Long: `Implementeer bouwt en implementeert je toepassing in de
opgegeven omgeving. Ondersteunde omgevingen zijn:
- ontwikkeling (dev)
- testomgeving
- productie (prod)`,
Example: ` myapp deploy testomgeving
myapp deploy productie --versie=1.2.3`,
}
6. Stel logische standaardwaarden in
func init() {
viper.SetDefault("poort", 8080)
viper.SetDefault("time-out", "30s")
viper.SetDefault("herproberen", 3)
}
7. Valideer configuratie
func validateConfig() error {
poort := viper.GetInt("poort")
if poort < 1024 || poort > 65535 {
return fmt.Errorf("ongeldige poort: %d", poort)
}
return nil
}
Testen van CLI-toepassingen
Testen van commando’s
// 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("commando uitvoering mislukt: %v", err)
}
}
Testen met verschillende configuraties
func TestWithConfig(t *testing.T) {
viper.Set("database", "/tmp/test.db")
viper.Set("debug", true)
// Voer je tests uit
viper.Reset() // Opschonen
}
Genereren van shell-completies
Cobra kan compleet scripts genereren voor verschillende shells:
// cmd/completion.go
package cmd
import (
"os"
"github.com/spf13/cobra"
)
var completionCmd = &cobra.Command{
Use: "completion [bash|zsh|fish|powershell]",
Short: "Genereer compleet script",
Long: `Om compleet scripts te laden:
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)
}
Bouwen en distributie
Bouwen voor meerdere platforms
# 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
Verminder de bestandsgrootte
go build -ldflags="-s -w" -o mytasks
Voeg versieinformatie toe
// cmd/version.go
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var (
Versie = "dev"
Commit = "none"
BuildTijd = "onbekend"
)
var versionCmd = &cobra.Command{
Use: "versie",
Short: "Toon versieinformatie",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Versie: %s\n", Versie)
fmt.Printf("Commit: %s\n", Commit)
fmt.Printf("Gebouwd: %s\n", BuildTijd)
},
}
func init() {
rootCmd.AddCommand(versionCmd)
}
Bouw met versieinformatie:
go build -ldflags="-X 'mytasks/cmd.Versie=1.0.0' \
-X 'mytasks/cmd.Commit=$(git rev-parse HEAD)' \
-X 'mytasks/cmd.BuildTijd=$(date -u +%Y-%m-%dT%H:%M:%SZ)'" \
-o mytasks
Reële voorbeelden
Populaire open-source tools gebouwd met Cobra en Viper:
- kubectl: Kubernetes commando-line tool
- Hugo: Statische site generator
- GitHub CLI (gh): Officiële GitHub CLI
- Docker CLI: Containerbeheer
- Helm: Kubernetes pakketbeheerder
- Skaffold: Kubernetes ontwikkelingswerkstroomtool
- Cobra CLI: Self-hosting - Cobra gebruikt zichzelf!
Buiten traditionele DevOps-tools, breiden Go’s CLI-mogelijkheden zich uit naar AI en machine learning toepassingen. Als je geïnteresseerd bent in het bouwen van CLI-tools die werken met grote taalmodellen, bekijk dan onze gids over Beperken van LLMs met Structured Output met Ollama en Go, die laat zien hoe je Go-toepassingen kunt bouwen die werken met AI-modellen.
Nuttige bronnen
- Cobra GitHub Repository
- Viper GitHub Repository
- Cobra User Guide
- Viper Documentatie
- Go CLI Applications Best Practices
Conclusie
Cobra en Viper samen vormen een krachtige basis voor het bouwen van professionele CLI-toepassingen in Go. Cobra beheert de commandostructuur, flag parsing en hulpgeneratie, terwijl Viper configuratie beheert van meerdere bronnen met slimme voorkeursvolgorde.
Deze combinatie stelt je in staat om CLI-tools te maken die zijn:
- Eenvoudig te gebruiken met intuïtieve commando’s en automatische hulp
- Flexibel met meerdere configuratiebronnen
- Professioneel met shell-completies en juiste foutafhandeling
- Onderhoudbaar met een nette codeorganisatie
- Overdraagbaar over verschillende platforms
Of je nu ontwikkelaartools, systeemhulpmiddelen of DevOps-automatisering bouwt, bieden Cobra en Viper de solide basis die je nodig hebt om CLI-toepassingen te maken die gebruikers zullen waarderen.