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.
Project Structure
devctl/
├── main.go
└── .devctl.yaml # optional config fileSetup
mkdir devctl
cd devctl
go mod init github.com/yourorg/devctl
go get github.com/spf13/cobra@latest
go get github.com/spf13/viper@latest
go get gopkg.in/yaml.v3Config File (optional)
Create ~/.devctl.yaml or .devctl.yaml in your working directory to set defaults:
env: staging
output: tableEnvironment variables override the config file. DEVCTL_ENV=production sets the env without passing --env.
main.go
package main
import (
"encoding/json"
"fmt"
"os"
"text/tabwriter"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"gopkg.in/yaml.v3"
)
type ServiceStatus struct {
Name string `json:"name" yaml:"name"`
Env string `json:"env" yaml:"env"`
Status string `json:"status" yaml:"status"`
Replicas int `json:"replicas" yaml:"replicas"`
}
var rootCmd = &cobra.Command{
Use: "devctl",
Short: "DevOps control plane CLI",
Long: "devctl manages deployments and checks service status across environments.",
}
var deployCmd = &cobra.Command{
Use: "deploy [service]",
Short: "Deploy a service to the target environment",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
env := viper.GetString("env")
image, _ := cmd.Flags().GetString("image")
if env == "" {
return fmt.Errorf("env is required")
}
fmt.Fprintf(os.Stderr, "Deploying %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: viper.GetString("env"), Status: "running", Replicas: 3},
{Name: "worker", Env: viper.GetString("env"), Status: "running", Replicas: 2},
{Name: "cache", Env: viper.GetString("env"), Status: "degraded", Replicas: 1},
}
return printServices(viper.GetString("output"), services)
},
}
// printServices renders services in the requested format: table (default), json, or yaml.
func printServices(format string, services []ServiceStatus) error {
switch format {
case "json":
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(services)
case "yaml":
return yaml.NewEncoder(os.Stdout).Encode(services)
default: // table
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)
}
}Running It
# Build
go build -o devctl .
# Deploy to staging (default)
./devctl deploy api --image myregistry/api:v1.2.3
# Deploying api to staging (image: myregistry/api:v1.2.3)
# Deploy to production
./devctl deploy api --image myregistry/api:v1.2.3 --env production
# Deploying api to production (image: myregistry/api:v1.2.3)
# Status as table (default)
./devctl status
# NAME ENV STATUS REPLICAS
# api staging running 3
# worker staging running 2
# cache staging degraded 1
# Status as JSON
./devctl status -o json
# [{"name":"api","env":"staging","status":"running","replicas":3}, ...]
# Status as YAML
./devctl status -o yaml
# - name: api
# env: staging
# status: running
# replicas: 3
# Override env via environment variable
DEVCTL_ENV=production ./devctl status