DevOps Control Plane CLI (devctl)

A complete CLI tool built with Cobra and Viper that demonstrates subcommands, flag handling, config file loading, environment variable binding, and multiple output formats. It simulates a devctl control plane with deploy and status subcommands.

package main

import (
    "encoding/json"
    "fmt"
    "os"
    "text/tabwriter"

    "github.com/spf13/cobra"
    "github.com/spf13/viper"
)

type ServiceStatus struct {
    Name     string `json:"name"`
    Env      string `json:"env"`
    Status   string `json:"status"`
    Replicas int    `json:"replicas"`
}

var rootCmd = &cobra.Command{
    Use:   "devctl",
    Short: "DevOps control plane CLI",
}

var deployCmd = &cobra.Command{
    Use:   "deploy [service]",
    Short: "Deploy a service",
    Args:  cobra.ExactArgs(1),
    RunE: func(cmd *cobra.Command, args []string) error {
        env := viper.GetString("env")
        image, _ := cmd.Flags().GetString("image")
        fmt.Fprintf(os.Stderr, "✓ Deployed %s to %s (image: %s)\n", args[0], env, image)
        return nil
    },
}

var statusCmd = &cobra.Command{
    Use:   "status",
    Short: "Show service status",
    RunE: func(cmd *cobra.Command, args []string) error {
        services := []ServiceStatus{
            {Name: "api", Env: "prod", Status: "running", Replicas: 3},
            {Name: "worker", Env: "prod", Status: "running", Replicas: 2},
        }
        return printServices(viper.GetString("output"), services)
    },
}

func printServices(format string, services []ServiceStatus) error {
    switch format {
    case "json":
        enc := json.NewEncoder(os.Stdout)
        enc.SetIndent("", "  ")
        return enc.Encode(services)
    default:
        w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
        fmt.Fprintln(w, "NAME\tENV\tSTATUS\tREPLICAS")
        for _, s := range services {
            fmt.Fprintf(w, "%s\t%s\t%s\t%d\n", s.Name, s.Env, s.Status, s.Replicas)
        }
        return w.Flush()
    }
}

func init() {
    cobra.OnInitialize(func() {
        viper.SetConfigName(".devctl")
        viper.SetConfigType("yaml")
        viper.AddConfigPath("$HOME")
        viper.AddConfigPath(".")
        viper.SetEnvPrefix("DEVCTL")
        viper.AutomaticEnv()
        viper.ReadInConfig()
    })

    rootCmd.PersistentFlags().StringP("output", "o", "table", "Output format: table|json|yaml")
    rootCmd.PersistentFlags().StringP("env", "e", "staging", "Target environment")
    viper.BindPFlag("output", rootCmd.PersistentFlags().Lookup("output"))
    viper.BindPFlag("env", rootCmd.PersistentFlags().Lookup("env"))

    deployCmd.Flags().String("image", "", "Container image to deploy")
    deployCmd.MarkFlagRequired("image")

    rootCmd.AddCommand(deployCmd)
    rootCmd.AddCommand(statusCmd)
}

func main() {
    if err := rootCmd.Execute(); err != nil {
        os.Exit(1)
    }
}

💻 Run locally

Copy the code above and run it on your machine

© 2026 ByteLearn.dev. Free courses for developers. · Privacy