Skip to content

CLI Frameworks

Compare Go CLI frameworks to choose the right foundation for your tool.

Match the Ecosystem

Cobra powers kubectl, docker, and gh. Your users already know its patterns. Start there unless you have specific reasons otherwise.


Cobra

The de facto standard for Go CLIs. Powers kubectl, docker, gh, and most Kubernetes ecosystem tools.

package cmd

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
    Use:   "myctl",
    Short: "Kubernetes orchestration CLI",
    Long:  `A CLI for managing deployments and cache operations.`,
}

func Execute() {
    if err := rootCmd.Execute(); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

func init() {
    rootCmd.PersistentFlags().StringP("namespace", "n", "default", "Kubernetes namespace")
    rootCmd.PersistentFlags().Bool("verbose", false, "Enable verbose output")
}

Strengths:

  • Mature ecosystem with extensive documentation
  • Built-in help generation, shell completion
  • Hierarchical command structure
  • Pairs well with Viper for configuration

When to use: Most Kubernetes-native CLIs. Default choice unless you have specific reasons otherwise.

Help Text Generation

Cobra automatically generates help text from your command definitions:

var checkCmd = &cobra.Command{
    Use:   "check [flags]",
    Short: "Check cache status",
    Long: `Check the current cache state and determine if a rebuild is required.

The check command validates the cache against the current state of
deployments in the cluster and reports whether action is needed.

Exit codes:
  0 - Cache is valid
  1 - Cache needs rebuild
  2 - Error occurred`,
    Example: `  # Check cache status
  myctl check

  # Check with JSON output
  myctl check --json

  # Check specific namespace
  myctl check -n production`,
    RunE: runCheck,
}

urfave/cli

Simpler API, good for straightforward CLIs without deep command hierarchies.

package main

import (
    "fmt"
    "log"
    "os"

    "github.com/urfave/cli/v2"
)

func main() {
    app := &cli.App{
        Name:  "myctl",
        Usage: "Kubernetes orchestration CLI",
        Flags: []cli.Flag{
            &cli.StringFlag{
                Name:    "namespace",
                Aliases: []string{"n"},
                Value:   "default",
                Usage:   "Kubernetes namespace",
            },
        },
        Commands: []*cli.Command{
            {
                Name:  "check",
                Usage: "Check cache status",
                Action: func(c *cli.Context) error {
                    fmt.Println("Checking cache...")
                    return nil
                },
            },
        },
    }

    if err := app.Run(os.Args); err != nil {
        log.Fatal(err)
    }
}

Strengths:

  • Single-file friendly
  • Less boilerplate for simple CLIs
  • Good for scripts evolving into tools

When to use: Simple CLIs with few commands, prototypes.


Kong

Type-safe CLI parsing using struct tags. Newer but gaining adoption.

package main

import (
    "fmt"

    "github.com/alecthomas/kong"
)

type CLI struct {
    Namespace string `short:"n" default:"default" help:"Kubernetes namespace"`
    Verbose   bool   `help:"Enable verbose output"`

    Check   CheckCmd   `cmd:"" help:"Check cache status"`
    Rebuild RebuildCmd `cmd:"" help:"Rebuild cache"`
}

type CheckCmd struct {
    All bool `help:"Check all namespaces"`
}

func (c *CheckCmd) Run() error {
    fmt.Println("Checking cache...")
    return nil
}

type RebuildCmd struct{}

func (r *RebuildCmd) Run() error {
    fmt.Println("Rebuilding cache...")
    return nil
}

func main() {
    var cli CLI
    ctx := kong.Parse(&cli)
    err := ctx.Run()
    ctx.FatalIfErrorf(err)
}

Strengths:

  • Type-safe command definitions
  • Compile-time validation of CLI structure
  • Clean struct-based API

When to use: New projects that value type safety, teams familiar with struct tags.


Best Practices

Flag Naming

Pattern Example Notes
Kebab-case for flags --dry-run Standard convention
Short flags for common options -n for namespace Match kubectl patterns
Avoid abbreviations --namespace not --ns Clarity over brevity

Required vs Optional

// Required flags
cmd.MarkFlagRequired("name")

// Mutually exclusive
cmd.MarkFlagsMutuallyExclusive("file", "stdin")

// Required together
cmd.MarkFlagsRequiredTogether("username", "password")

Match kubectl conventions. Your users already know them.

Comments