Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 6 additions & 133 deletions cmd/cephctl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,143 +2,16 @@ package main

import (
"context"
"fmt"
"os"

kingpin "github.com/alecthomas/kingpin/v2"
log "github.com/sirupsen/logrus"

"github.com/runityru/cephctl/ceph"
applyCmd "github.com/runityru/cephctl/commands/apply"
diffCmd "github.com/runityru/cephctl/commands/diff"
dumpCephConfigCmd "github.com/runityru/cephctl/commands/dump/cephconfig"
dumpCephOSDConfigCmd "github.com/runityru/cephctl/commands/dump/cephosdconfig"
healthcheckCmd "github.com/runityru/cephctl/commands/healthcheck"
"github.com/runityru/cephctl/differ"
"github.com/runityru/cephctl/printer"
"github.com/runityru/cephctl/service"
)

var (
appVersion = "n/a (dev build)"
buildTimestamp = "undefined"

app = kingpin.New("cephctl", "Small utility to control Ceph cluster configuration just like any other declarative configuration")

cephBinary = app.
Flag("ceph-binary", "Specify path to ceph binary").
Short('b').
Envar("CEPHCTL_CEPH_BINARY").
Default("/usr/bin/ceph").
String()

debug = app.
Flag("debug", "Enable debug mode").
Short('d').
Envar("CEPHCTL_DEBUG").
Bool()

trace = app.
Flag("trace", "Enable trace mode (debug mode on steroids)").
Short('t').
Envar("CEPHCTL_TRACE").
Bool()

colorize = app.
Flag("color", "Colorize diff output").
Short('c').
Envar("CEPHCTL_COLOR").
Default("true").
Bool()

apply = app.Command("apply", "Apply ceph configuration")
applySpecFile = apply.Arg("filename", "Filename with configuration specification").Required().String()

diff = app.Command("diff", "Show difference between running and desired configurations")

diffSpecFile = diff.Arg("filename", "Filename with configuration specification").Required().String()

dump = app.Command("dump", "Dump runtime configuration")
dumpCephConfig = dump.Command("cephconfig", "dump Ceph runtime configuration")
dumpCephOSDConfig = dump.Command("cephosdconfig", "dump Ceph OSD configuration")

healthcheck = app.Command("healthcheck", "Perform a cluster healthcheck and print report")

version = app.Command("version", "Print version and exit")
)

func main() {
ctx := context.Background()
appCmd := kingpin.MustParse(app.Parse(os.Args[1:]))

if *trace {
log.SetLevel(log.TraceLevel)
log.SetFormatter(&log.TextFormatter{
FullTimestamp: true,
})
log.Trace("Trace mode is enabled. Beware of verbosity!")
} else if *debug {
log.SetLevel(log.DebugLevel)
log.SetFormatter(&log.TextFormatter{
FullTimestamp: true,
})
log.Debug("Debug mode is enabled.")
}

svc := service.New(ceph.New(*cephBinary), differ.New())
prntr := printer.New(*colorize)

switch appCmd {
case apply.FullCommand():
log.Debug("running apply command")
if err := applyCmd.Apply(ctx, applyCmd.ApplyConfig{
Service: svc,
SpecFile: *applySpecFile,
}); err != nil {
panic(err)
}

case diff.FullCommand():
log.Debug("running diff command")
if err := diffCmd.Diff(ctx, diffCmd.DiffConfig{
Printer: prntr,
Service: svc,
SpecFile: *diffSpecFile,
}); err != nil {
panic(err)
}

case dumpCephConfig.FullCommand():
log.Debug("running dump cephconfig command")
if err := dumpCephConfigCmd.DumpCephConfig(ctx, dumpCephConfigCmd.DumpCephConfigConfig{
Printer: prntr,
Service: svc,
}); err != nil {
panic(err)
}

case dumpCephOSDConfig.FullCommand():
log.Debug("running dump cephosdconfig command")
if err := dumpCephOSDConfigCmd.DumpCephOSDConfig(ctx, dumpCephOSDConfigCmd.DumpCephOSDConfigConfig{
Printer: prntr,
Service: svc,
}); err != nil {
panic(err)
}

case healthcheck.FullCommand():
if err := healthcheckCmd.Healthcheck(ctx, healthcheckCmd.HealthcheckConfig{
Printer: prntr,
Service: svc,
}); err != nil {
panic(err)
}

case version.FullCommand():
fmt.Printf(
"%s v%s / built at %s\n",
os.Args[0], appVersion, buildTimestamp,
)
// ExecuteContext runs the cobra command tree.
//
// SilenceUsage=true: usage is not printed when a RunE handler returns an
// error. SilenceErrors=false (default): cobra prints the error text to
// stderr (ErrOrStderr), after which we exit with code 1.
if err := rootCmd.ExecuteContext(context.Background()); err != nil {
os.Exit(1)
}
}
196 changes: 196 additions & 0 deletions cmd/cephctl/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package main

import (
"os"
"strconv"
"strings"

log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"

"github.com/runityru/cephctl/ceph"
applyCmd "github.com/runityru/cephctl/commands/apply"
diffCmd "github.com/runityru/cephctl/commands/diff"
dumpCephConfigCmd "github.com/runityru/cephctl/commands/dump/cephconfig"
dumpCephOSDConfigCmd "github.com/runityru/cephctl/commands/dump/cephosdconfig"
healthcheckCmd "github.com/runityru/cephctl/commands/healthcheck"
"github.com/runityru/cephctl/differ"
"github.com/runityru/cephctl/printer"
"github.com/runityru/cephctl/service"
)

// Variables are bound to the root command's persistent flags via BoolVar/
// StringVarP. Their values are updated when cmd.Flags().Set(...) is called
// (including from env).
var (
cephBinary string
debug bool
trace bool
colorize bool
)

var rootCmd = &cobra.Command{
Use: "cephctl",
Short: "Small utility to control Ceph cluster configuration just like any other declarative configuration",
// SilenceUsage: do not print usage when a RunE handler returns an error.
SilenceUsage: true,
// SilenceErrors is left false (default): cobra prints the error text to
// stderr, and main() terminates the process with code 1.
PersistentPreRun: func(cmd *cobra.Command, args []string) {
// Env support: an environment variable value is applied as the default.
// If the flag was set explicitly by the user (f.Changed), env is ignored.
// This mirrors kingpin's Envar semantics (env = default, flag overrides).
applyStringEnv(cmd, "ceph-binary", "CEPHCTL_CEPH_BINARY")
applyBoolEnv(cmd, "debug", "CEPHCTL_DEBUG")
applyBoolEnv(cmd, "trace", "CEPHCTL_TRACE")
applyBoolEnv(cmd, "color", "CEPHCTL_COLOR")

if trace {
log.SetLevel(log.TraceLevel)
log.SetFormatter(&log.TextFormatter{FullTimestamp: true})
log.Trace("Trace mode is enabled. Beware of verbosity!")
} else if debug {
log.SetLevel(log.DebugLevel)
log.SetFormatter(&log.TextFormatter{FullTimestamp: true})
log.Debug("Debug mode is enabled.")
}
},
// Without a subcommand, print the root command's help.
Run: func(cmd *cobra.Command, args []string) {
_ = cmd.Help()
},
}

func init() {
// Global (persistent) flags — semantics preserved from the kingpin version.
rootCmd.PersistentFlags().StringVarP(&cephBinary, "ceph-binary", "b", "/usr/bin/ceph", "Specify path to ceph binary")
rootCmd.PersistentFlags().BoolVarP(&debug, "debug", "d", false, "Enable debug mode")
rootCmd.PersistentFlags().BoolVarP(&trace, "trace", "t", false, "Enable trace mode (debug mode on steroids)")
rootCmd.PersistentFlags().BoolVarP(&colorize, "color", "c", true, "Colorize diff output")

rootCmd.AddCommand(applyCommand)
rootCmd.AddCommand(diffCommand)
rootCmd.AddCommand(dumpCommand)
rootCmd.AddCommand(healthcheckCommand)
rootCmd.AddCommand(versionCommand)

dumpCommand.AddCommand(dumpCephConfigCommand)
dumpCommand.AddCommand(dumpCephOSDConfigCommand)
}

var applyCommand = &cobra.Command{
Use: "apply <filename>",
Short: "Apply ceph configuration",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
log.Debug("running apply command")
svc := service.New(ceph.New(cephBinary), differ.New())
return applyCmd.Apply(cmd.Context(), applyCmd.ApplyConfig{
Service: svc,
SpecFile: args[0],
})
},
}

var diffCommand = &cobra.Command{
Use: "diff <filename>",
Short: "Show difference between running and desired configurations",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
log.Debug("running diff command")
svc := service.New(ceph.New(cephBinary), differ.New())
prntr := printer.New(colorize)
return diffCmd.Diff(cmd.Context(), diffCmd.DiffConfig{
Printer: prntr,
Service: svc,
SpecFile: args[0],
})
},
}

var dumpCommand = &cobra.Command{
Use: "dump",
Short: "Dump runtime configuration",
// dump without a child command — print the group's help.
Run: func(cmd *cobra.Command, args []string) {
_ = cmd.Help()
},
}

var dumpCephConfigCommand = &cobra.Command{
Use: "cephconfig",
Short: "dump Ceph runtime configuration",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
log.Debug("running dump cephconfig command")
svc := service.New(ceph.New(cephBinary), differ.New())
prntr := printer.New(colorize)
return dumpCephConfigCmd.DumpCephConfig(cmd.Context(), dumpCephConfigCmd.DumpCephConfigConfig{
Printer: prntr,
Service: svc,
})
},
}

var dumpCephOSDConfigCommand = &cobra.Command{
Use: "cephosdconfig",
Short: "dump Ceph OSD configuration",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
log.Debug("running dump cephosdconfig command")
svc := service.New(ceph.New(cephBinary), differ.New())
prntr := printer.New(colorize)
return dumpCephOSDConfigCmd.DumpCephOSDConfig(cmd.Context(), dumpCephOSDConfigCmd.DumpCephOSDConfigConfig{
Printer: prntr,
Service: svc,
})
},
}

var healthcheckCommand = &cobra.Command{
Use: "healthcheck",
Short: "Perform a cluster healthcheck and print report",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
svc := service.New(ceph.New(cephBinary), differ.New())
prntr := printer.New(colorize)
return healthcheckCmd.Healthcheck(cmd.Context(), healthcheckCmd.HealthcheckConfig{
Printer: prntr,
Service: svc,
})
},
}

// applyStringEnv applies a string env value as the default for a flag, unless
// the flag was set explicitly by the user.
func applyStringEnv(cmd *cobra.Command, flagName, envName string) {
f := cmd.Flags().Lookup(flagName)
if f == nil || f.Changed {
return
}
if v, ok := os.LookupEnv(envName); ok {
_ = cmd.Flags().Set(flagName, v)
}
}

// applyBoolEnv applies a truthy env value (1/true/yes/on) as the default for a
// bool flag, unless the flag was set explicitly by the user.
func applyBoolEnv(cmd *cobra.Command, flagName, envName string) {
f := cmd.Flags().Lookup(flagName)
if f == nil || f.Changed {
return
}
if v, ok := os.LookupEnv(envName); ok {
_ = cmd.Flags().Set(flagName, strconv.FormatBool(parseBool(v)))
}
}

// parseBool treats an env string as truthy for bool flags.
func parseBool(v string) bool {
switch strings.ToLower(strings.TrimSpace(v)) {
case "1", "true", "yes", "on":
return true
default:
return false
}
}
29 changes: 29 additions & 0 deletions cmd/cephctl/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package main

import (
"fmt"
"os"

"github.com/spf13/cobra"
)

// appVersion and buildTimestamp are overridden at build time via ldflags:
// -X main.appVersion={{.Version}} -X main.buildTimestamp={{.Date}}
// see .goreleaser.yaml.
var (
appVersion = "n/a (dev build)"
buildTimestamp = "undefined"
)

var versionCommand = &cobra.Command{
Use: "version",
Short: "Print version and exit",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Printf(
"%s v%s / built at %s\n",
os.Args[0], appVersion, buildTimestamp,
)
return nil
},
}
Loading
Loading