Membangun Aplikasi CLI dalam Go dengan Cobra & Viper

Pengembangan CLI dalam Go dengan kerangka Cobra dan Viper

Konten Halaman

Antarmuka baris perintah (CLI) adalah alat penting bagi pengembang, administrator sistem, dan profesional DevOps. Dua perpustakaan Go telah menjadi standar de facto untuk pengembangan CLI dalam Go: Cobra untuk struktur perintah dan Viper untuk manajemen konfigurasi.

Go telah muncul sebagai bahasa yang sangat baik untuk membangun alat CLI karena kinerjanya yang baik, pengiriman sederhana, dan dukungan lintas platform.

Tetris

Mengapa Memilih Go untuk Aplikasi CLI

Go menawarkan keunggulan yang menarik untuk pengembangan CLI:

  • Distribusi Binari Tunggal: Tidak diperlukan ketergantungan runtime atau manajer paket
  • Eksekusi Cepat: Kompilasi native memberikan kinerja yang sangat baik
  • Dukungan Lintas Platform: Kompilasi mudah untuk Linux, macOS, Windows, dan lainnya
  • Perpustakaan Standar yang Kuat: Alat bantu kaya untuk I/O file, jaringan, dan pemrosesan teks
  • Konkurensi: Goroutine bawaan untuk operasi paralel
  • Tipe Statis: Menangkap kesalahan pada saat kompilasi

Alat CLI populer yang dibangun dengan Go termasuk Docker, Kubernetes (kubectl), Hugo, Terraform, dan GitHub CLI. Jika Anda baru dengan Go atau membutuhkan referensi cepat, lihat Kartu Referensi Go kami untuk sintaks dan pola Go yang penting.

Pengantar tentang Cobra

Cobra adalah perpustakaan yang menyediakan antarmuka sederhana untuk membuat aplikasi CLI modern yang kuat. Dibuat oleh Steve Francia (spf13), penulis yang sama di balik Hugo dan Viper, Cobra digunakan dalam banyak proyek Go paling populer.

Fitur Utama Cobra

Struktur Perintah: Cobra menerapkan pola perintah, memungkinkan Anda membuat aplikasi dengan perintah dan subperintah (seperti git commit atau docker run).

Pengelolaan Flag: Flag lokal dan persisten dengan parsing otomatis dan konversi tipe.

Bantuan Otomatis: Menghasilkan teks bantuan dan informasi penggunaan secara otomatis.

Saran Cerdas: Menyediakan saran saat pengguna membuat ejaan yang salah (“Apakah Anda maksud ‘status’?”).

Kompleksi Shell: Menghasilkan skrip kompletasi untuk bash, zsh, fish, dan PowerShell.

Output Fleksibel: Bekerja dengan lancar dengan formatter kustom dan gaya output.

Pengantar tentang Viper

Viper adalah solusi konfigurasi lengkap untuk aplikasi Go, dirancang untuk bekerja dengan lancar dengan Cobra. Ia menangani konfigurasi dari berbagai sumber dengan urutan prioritas yang jelas.

Fitur Utama Viper

Sumber Konfigurasi Beragam:

  • File konfigurasi (JSON, YAML, TOML, HCL, INI, envfile, Java properties)
  • Variabel lingkungan
  • Flag baris perintah
  • Sistem konfigurasi jarak jauh (etcd, Consul)
  • Nilai default

Urutan Prioritas Konfigurasi:

  1. Panggilan eksplisit ke Set
  2. Flag baris perintah
  3. Variabel lingkungan
  4. File konfigurasi
  5. Penyimpanan key/value
  6. Nilai default

Pemantauan Langsung: Memantau file konfigurasi dan memuat ulang secara otomatis saat terjadi perubahan.

Konversi Tipe: Konversi otomatis ke berbagai tipe Go (string, int, bool, durasi, dll.).

Memulai: Instalasi

Pertama, inisialisasi modul Go baru dan instal kedua perpustakaan:

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

Secara opsional, instal generator CLI Cobra untuk scaffolding:

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

Membangun Aplikasi CLI Pertama

Mari kita bangun contoh praktis: alat manajemen tugas CLI dengan dukungan konfigurasi.

Struktur Proyek

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

Titik Masuk Utama

// main.go
package main

import "mytasks/cmd"

func main() {
    cmd.Execute()
}

Perintah Utama dengan Integrasi 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: "Sebuah alat manajemen tugas sederhana",
    Long: `MyTasks adalah alat manajemen tugas CLI yang membantu Anda mengatur 
tugas harian Anda dengan mudah. Dibangun dengan Cobra dan 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 konfigurasi (default adalah $HOME/.mytasks.yaml)")
    rootCmd.PersistentFlags().String("db", "", 
        "lokasi file 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("Menggunakan file konfigurasi:", viper.ConfigFileUsed())
    }
}

Menambahkan Subperintah

// cmd/add.go
package cmd

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

var priority string

var addCmd = &cobra.Command{
    Use:   "add [deskripsi tugas]",
    Short: "Tambahkan tugas baru",
    Args:  cobra.MinimumNArgs(1),
    Run: func(cmd *cobra.Command, args []string) {
        task := args[0]
        db := viper.GetString("database")
        
        fmt.Printf("Menambahkan tugas: %s\n", task)
        fmt.Printf("Prioritas: %s\n", priority)
        fmt.Printf("Database: %s\n", db)
        
        // Di sini Anda akan menerapkan penyimpanan tugas sebenarnya
    },
}

func init() {
    rootCmd.AddCommand(addCmd)
    
    addCmd.Flags().StringVarP(&priority, "priority", "p", "medium", 
        "prioritas tugas (rendah, sedang, tinggi)")
}

Perintah Daftar

// 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: "Daftar semua tugas",
    Run: func(cmd *cobra.Command, args []string) {
        db := viper.GetString("database")
        
        fmt.Printf("Mendaftar tugas dari: %s\n", db)
        fmt.Printf("Tampilkan tugas selesai: %v\n", showCompleted)
        
        // Di sini Anda akan menerapkan daftar tugas sebenarnya
    },
}

func init() {
    rootCmd.AddCommand(listCmd)
    
    listCmd.Flags().BoolVarP(&showCompleted, "completed", "c", false, 
        "tampilkan tugas selesai")
}

Menerapkan Pemeliharaan Data

Untuk aplikasi manajemen tugas CLI produksi, Anda perlu menerapkan penyimpanan data sebenarnya. Meskipun kami menggunakan konfigurasi jalur database sederhana di sini, Anda memiliki beberapa opsi untuk menyimpan data:

  • SQLite: Database ringan tanpa server yang sempurna untuk alat CLI
  • PostgreSQL/MySQL: Database lengkap untuk aplikasi yang lebih kompleks
  • File JSON/YAML: Penyimpanan berbasis file sederhana untuk kebutuhan ringan
  • Database Tersemat: BoltDB, BadgerDB untuk penyimpanan key-value

Jika Anda bekerja dengan database relasional seperti PostgreSQL, Anda ingin menggunakan ORM atau pembangun query. Untuk perbandingan menyeluruh dari perpustakaan database Go, lihat panduan kami tentang Membandingkan ORM Go untuk PostgreSQL: GORM vs Ent vs Bun vs sqlc.

Konfigurasi Lanjutan dengan Viper

Contoh File Konfigurasi

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

notifications:
  enabled: true
  sound: true

priorities:
  high: red
  medium: yellow
  low: green

Membaca Konfigurasi Bersarang

func getNotificationSettings() {
    enabled := viper.GetBool("notifications.enabled")
    sound := viper.GetBool("notifications.sound")
    
    fmt.Printf("Notifikasi aktif: %v\n", enabled)
    fmt.Printf("Suara aktif: %v\n", sound)
}

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

Variabel Lingkungan

Viper secara otomatis membaca variabel lingkungan dengan prefix yang dikonfigurasi:

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

Muat Ulang Konfigurasi Langsung

func watchConfig() {
    viper.WatchConfig()
    viper.OnConfigChange(func(e fsnotify.Event) {
        fmt.Println("File konfigurasi berubah:", e.Name)
        // Muat ulang komponen yang bergantung pada konfigurasi
    })
}

Praktik Terbaik

1. Gunakan Generator Cobra untuk Konsistensi

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

2. Organisasi Perintah dalam File Terpisah

Jaga setiap perintah dalam file sendiri di bawah direktori cmd/ untuk keterpeliharaan.

3. Manfaatkan Flag Persisten

Gunakan flag persisten untuk opsi yang berlaku untuk semua subperintah:

rootCmd.PersistentFlags().StringP("output", "o", "text", 
    "format output (text, json, yaml)")

4. Implementasikan Penangan Kesalahan yang Tepat

var rootCmd = &cobra.Command{
    Use:   "myapp",
    Short: "Aplikasi saya",
    RunE: func(cmd *cobra.Command, args []string) error {
        if err := doSomething(); err != nil {
            return fmt.Errorf("gagal melakukan sesuatu: %w", err)
        }
        return nil
    },
}

5. Sediakan Teks Bantuan yang Berguna

var cmd = &cobra.Command{
    Use:   "deploy [lingkungan]",
    Short: "Deploy aplikasi ke lingkungan yang ditentukan",
    Long: `Deploy membangun dan mendeploy aplikasi ke 
lingkungan yang ditentukan. Lingkungan yang didukung adalah:
  - pengembangan (dev)
  - staging
  - produksi (prod)`,
    Example: `  myapp deploy staging
  myapp deploy production --version=1.2.3`,
}

6. Tetapkan Default yang Masuk Akal

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

7. Validasi Konfigurasi

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

Pengujian Aplikasi CLI

Pengujian Perintah

// 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("eksekusi perintah gagal: %v", err)
    }
}

Pengujian dengan Konfigurasi Berbeda

func TestWithConfig(t *testing.T) {
    viper.Set("database", "/tmp/test.db")
    viper.Set("debug", true)
    
    // Jalankan pengujian Anda
    
    viper.Reset() // Bersihkan
}

Menghasilkan Skrip Kompleksi Shell

Cobra dapat menghasilkan skrip kompleksi untuk berbagai shell:

// cmd/completion.go
package cmd

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

var completionCmd = &cobra.Command{
    Use:   "completion [bash|zsh|fish|powershell]",
    Short: "Hasilkan skrip kompleksi",
    Long: `Untuk memuat kompleksi:

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

Membangun dan Distribusi

Membangun untuk Platform Beragam

# 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

Mengurangi Ukuran Binari

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

Menambahkan Informasi Versi

// 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: "Cetak informasi versi",
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Printf("Versi: %s\n", Version)
        fmt.Printf("Commit: %s\n", Commit)
        fmt.Printf("Dibuat: %s\n", BuildTime)
    },
}

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

Bangun dengan informasi versi:

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

Contoh Nyata

Alat open-source populer yang dibangun dengan Cobra dan Viper:

  • kubectl: Alat CLI Kubernetes
  • Hugo: Generator situs statis
  • GitHub CLI (gh): CLI resmi GitHub
  • Docker CLI: Manajemen kontainer
  • Helm: Manajer paket Kubernetes
  • Skaffold: Alat alur kerja pengembangan Kubernetes
  • Cobra CLI: Self-hosting - Cobra menggunakan dirinya sendiri!

Selain alat DevOps tradisional, kemampuan CLI Go juga mencakup aplikasi AI dan pembelajaran mesin. Jika Anda tertarik membangun alat CLI yang berinteraksi dengan model bahasa besar, lihat panduan kami tentang Membatasi LLM dengan Output Terstruktur menggunakan Ollama dan Go, yang menunjukkan bagaimana membangun aplikasi Go yang bekerja dengan model AI.

Sumber Daya Berguna

Kesimpulan

Cobra dan Viper bersama-sama menyediakan fondasi yang kuat untuk membangun aplikasi CLI profesional dalam Go. Cobra menangani struktur perintah, parsing flag, dan pembuatan bantuan, sementara Viper mengelola konfigurasi dari berbagai sumber dengan prioritas cerdas.

Kombinasi ini memungkinkan Anda membuat alat CLI yang:

  • Mudah digunakan dengan perintah intuitif dan bantuan otomatis
  • Fleksibel dengan berbagai sumber konfigurasi
  • Profesional dengan kompletasi shell dan penangan kesalahan yang tepat
  • Dapat dipelihara dengan organisasi kode yang bersih
  • Portabel lintas platform

Baik Anda sedang membangun alat pengembang, utilitas sistem, atau otomasi DevOps, Cobra dan Viper menyediakan fondasi padat yang Anda butuhkan untuk membuat aplikasi CLI yang akan disukai pengguna.