diff --git a/cmd/configfile.go b/cmd/configfile.go new file mode 100644 index 0000000..9385d7c --- /dev/null +++ b/cmd/configfile.go @@ -0,0 +1,36 @@ +package cmd + +import ( + "os" + "strings" + + "github.com/spf13/viper" + "k8s.io/client-go/util/homedir" +) + +func loadConfigFile() { + viper.SetConfigType("yaml") + viper.SetConfigName(appName) + + // all possible config file paths, by priority + viper.AddConfigPath("/etc/katafygio/") + if home := homedir.HomeDir(); home != "" { + viper.AddConfigPath(home) + } + viper.AddConfigPath(".") + + // prefer the config file path provided by cli flag, if any + if _, err := os.Stat(cfgFile); !os.IsNotExist(err) { + viper.SetConfigFile(cfgFile) + } + + // allow config params through prefixed env variables + viper.SetEnvPrefix("KF") + replacer := strings.NewReplacer("-", "_", ".", "_DOT_") + viper.SetEnvKeyReplacer(replacer) + viper.AutomaticEnv() + + if err := viper.ReadInConfig(); err == nil { + RootCmd.Printf("Using config file: %s", viper.ConfigFileUsed()) + } +} diff --git a/cmd/execute.go b/cmd/execute.go index 4f9d4be..3378d14 100644 --- a/cmd/execute.go +++ b/cmd/execute.go @@ -2,68 +2,43 @@ package cmd import ( "fmt" - "log" - "os" - "strings" "time" "github.com/spf13/cobra" "github.com/spf13/viper" - "k8s.io/client-go/util/homedir" "github.com/bpineau/katafygio/config" - klog "github.com/bpineau/katafygio/pkg/log" + "github.com/bpineau/katafygio/pkg/log" "github.com/bpineau/katafygio/pkg/run" ) const appName = "katafygio" var ( - version = "0.3.0" - - cfgFile string - apiServer string - kubeConf string - dryRun bool - dumpMode bool - logLevel string - logOutput string - logServer string - filter string - localDir string - gitURL string - healthP int - resync int - exclkind []string - exclobj []string - - versionCmd = &cobra.Command{ - Use: "version", - Short: "Print the version number", - Run: func(cmd *cobra.Command, args []string) { - RootCmd.Printf("%s version %s\n", appName, version) - }, - } - - // RootCmd represents the base command when called without any subcommands + // RootCmd is our main entry point, launching pkg/run.Run() RootCmd = &cobra.Command{ Use: appName, Short: "Backup Kubernetes cluster as yaml files", Long: "Backup Kubernetes cluster as yaml files in a git repository.\n" + - "--exclude-kind (x) and --exclude-object (-y) may be specified several times.", + "--exclude-kind (-x) and --exclude-object (-y) may be specified several times.", RunE: func(cmd *cobra.Command, args []string) error { + resync := time.Duration(viper.GetInt("resync-interval")) * time.Second + logger := log.New(viper.GetString("log.level"), + viper.GetString("log.server"), + viper.GetString("log.output")) + conf := &config.KfConfig{ DryRun: viper.GetBool("dry-run"), DumpMode: viper.GetBool("dump-only"), - Logger: klog.New(viper.GetString("log.level"), viper.GetString("log.server"), viper.GetString("log.output")), + Logger: logger, LocalDir: viper.GetString("local-dir"), GitURL: viper.GetString("git-url"), Filter: viper.GetString("filter"), ExcludeKind: viper.GetStringSlice("exclude-kind"), ExcludeObject: viper.GetStringSlice("exclude-object"), HealthPort: viper.GetInt("healthcheck-port"), - ResyncIntv: time.Duration(viper.GetInt("resync-interval")) * time.Second, + ResyncIntv: resync, } err := conf.Init(viper.GetString("api-server"), viper.GetString("kube-config")) @@ -71,7 +46,7 @@ var ( return fmt.Errorf("Failed to initialize the configuration: %v", err) } - run.Run(conf) + run.Run(conf) // <- this is where things happens return nil }, } @@ -81,89 +56,3 @@ var ( func Execute() error { return RootCmd.Execute() } - -func bindPFlag(key string, cmd string) { - if err := viper.BindPFlag(key, RootCmd.PersistentFlags().Lookup(cmd)); err != nil { - log.Fatal("Failed to bind cli argument:", err) - } -} - -func init() { - cobra.OnInitialize(initConfig) - RootCmd.AddCommand(versionCmd) - - defaultCfg := "/etc/katafygio/" + appName + ".yaml" - RootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", defaultCfg, "Configuration file") - - RootCmd.PersistentFlags().StringVarP(&apiServer, "api-server", "s", "", "Kubernetes api-server url") - bindPFlag("api-server", "api-server") - - RootCmd.PersistentFlags().StringVarP(&kubeConf, "kube-config", "k", "", "Kubernetes config path") - bindPFlag("kube-config", "kube-config") - if err := viper.BindEnv("kube-config", "KUBECONFIG"); err != nil { - log.Fatal("Failed to bind cli argument:", err) - } - - RootCmd.PersistentFlags().BoolVarP(&dryRun, "dry-run", "d", false, "Dry-run mode: don't store anything") - bindPFlag("dry-run", "dry-run") - - RootCmd.PersistentFlags().BoolVarP(&dumpMode, "dump-only", "m", false, "Dump mode: dump everything once and exit") - bindPFlag("dump-only", "dump-only") - - RootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "v", "info", "Log level") - bindPFlag("log.level", "log-level") - - RootCmd.PersistentFlags().StringVarP(&logOutput, "log-output", "o", "stderr", "Log output") - bindPFlag("log.output", "log-output") - - RootCmd.PersistentFlags().StringVarP(&logServer, "log-server", "r", "", "Log server (if using syslog)") - bindPFlag("log.server", "log-server") - - RootCmd.PersistentFlags().StringVarP(&localDir, "local-dir", "e", "./kubernetes-backup", "Where to dump yaml files") - bindPFlag("local-dir", "local-dir") - - RootCmd.PersistentFlags().StringVarP(&gitURL, "git-url", "g", "", "Git repository URL") - bindPFlag("git-url", "git-url") - - RootCmd.PersistentFlags().StringSliceVarP(&exclkind, "exclude-kind", "x", nil, "Ressource kind to exclude. Eg. 'deployment'") - bindPFlag("exclude-kind", "exclude-kind") - - RootCmd.PersistentFlags().StringSliceVarP(&exclobj, "exclude-object", "y", nil, "Object to exclude. Eg. 'configmap:kube-system/kube-dns'") - bindPFlag("exclude-object", "exclude-object") - - RootCmd.PersistentFlags().StringVarP(&filter, "filter", "l", "", "Label filter. Select only objects matching the label.") - bindPFlag("filter", "filter") - - RootCmd.PersistentFlags().IntVarP(&healthP, "healthcheck-port", "p", 0, "Port for answering healthchecks on /health url") - bindPFlag("healthcheck-port", "healthcheck-port") - - RootCmd.PersistentFlags().IntVarP(&resync, "resync-interval", "i", 900, "Full resync interval in seconds (0 to disable)") - bindPFlag("resync-interval", "resync-interval") -} - -func initConfig() { - viper.SetConfigType("yaml") - viper.SetConfigName(appName) - - // all possible config file paths, by priority - viper.AddConfigPath("/etc/katafygio/") - if home := homedir.HomeDir(); home != "" { - viper.AddConfigPath(home) - } - viper.AddConfigPath(".") - - // prefer the config file path provided by cli flag, if any - if _, err := os.Stat(cfgFile); !os.IsNotExist(err) { - viper.SetConfigFile(cfgFile) - } - - // allow config params through prefixed env variables - viper.SetEnvPrefix("KF") - replacer := strings.NewReplacer("-", "_", ".", "_DOT_") - viper.SetEnvKeyReplacer(replacer) - viper.AutomaticEnv() - - if err := viper.ReadInConfig(); err == nil { - RootCmd.Printf("Using config file: %s", viper.ConfigFileUsed()) - } -} diff --git a/cmd/flags.go b/cmd/flags.go new file mode 100644 index 0000000..651b5d4 --- /dev/null +++ b/cmd/flags.go @@ -0,0 +1,85 @@ +package cmd + +import ( + "log" + + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var ( + cfgFile string + apiServer string + kubeConf string + dryRun bool + dumpMode bool + logLevel string + logOutput string + logServer string + filter string + localDir string + gitURL string + healthP int + resync int + exclkind []string + exclobj []string +) + +func bindPFlag(key string, cmd string) { + if err := viper.BindPFlag(key, RootCmd.PersistentFlags().Lookup(cmd)); err != nil { + log.Fatal("Failed to bind cli argument:", err) + } +} + +func init() { + cobra.OnInitialize(loadConfigFile) + RootCmd.AddCommand(versionCmd) + + defaultCfg := "/etc/katafygio/" + appName + ".yaml" + RootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", defaultCfg, "Configuration file") + + RootCmd.PersistentFlags().StringVarP(&apiServer, "api-server", "s", "", "Kubernetes api-server url") + bindPFlag("api-server", "api-server") + + RootCmd.PersistentFlags().StringVarP(&kubeConf, "kube-config", "k", "", "Kubernetes config path") + bindPFlag("kube-config", "kube-config") + if err := viper.BindEnv("kube-config", "KUBECONFIG"); err != nil { + log.Fatal("Failed to bind cli argument:", err) + } + + RootCmd.PersistentFlags().BoolVarP(&dryRun, "dry-run", "d", false, "Dry-run mode: don't store anything") + bindPFlag("dry-run", "dry-run") + + RootCmd.PersistentFlags().BoolVarP(&dumpMode, "dump-only", "m", false, "Dump mode: dump everything once and exit") + bindPFlag("dump-only", "dump-only") + + RootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "v", "info", "Log level") + bindPFlag("log.level", "log-level") + + RootCmd.PersistentFlags().StringVarP(&logOutput, "log-output", "o", "stderr", "Log output") + bindPFlag("log.output", "log-output") + + RootCmd.PersistentFlags().StringVarP(&logServer, "log-server", "r", "", "Log server (if using syslog)") + bindPFlag("log.server", "log-server") + + RootCmd.PersistentFlags().StringVarP(&localDir, "local-dir", "e", "./kubernetes-backup", "Where to dump yaml files") + bindPFlag("local-dir", "local-dir") + + RootCmd.PersistentFlags().StringVarP(&gitURL, "git-url", "g", "", "Git repository URL") + bindPFlag("git-url", "git-url") + + RootCmd.PersistentFlags().StringSliceVarP(&exclkind, "exclude-kind", "x", nil, "Ressource kind to exclude. Eg. 'deployment'") + bindPFlag("exclude-kind", "exclude-kind") + + RootCmd.PersistentFlags().StringSliceVarP(&exclobj, "exclude-object", "y", nil, "Object to exclude. Eg. 'configmap:kube-system/kube-dns'") + bindPFlag("exclude-object", "exclude-object") + + RootCmd.PersistentFlags().StringVarP(&filter, "filter", "l", "", "Label filter. Select only objects matching the label.") + bindPFlag("filter", "filter") + + RootCmd.PersistentFlags().IntVarP(&healthP, "healthcheck-port", "p", 0, "Port for answering healthchecks on /health url") + bindPFlag("healthcheck-port", "healthcheck-port") + + RootCmd.PersistentFlags().IntVarP(&resync, "resync-interval", "i", 900, "Full resync interval in seconds (0 to disable)") + bindPFlag("resync-interval", "resync-interval") +} diff --git a/cmd/version.go b/cmd/version.go new file mode 100644 index 0000000..3a115ed --- /dev/null +++ b/cmd/version.go @@ -0,0 +1,15 @@ +package cmd + +import "github.com/spf13/cobra" + +var ( + version = "0.3.0" + + versionCmd = &cobra.Command{ + Use: "version", + Short: "Print the version number", + Run: func(cmd *cobra.Command, args []string) { + RootCmd.Printf("%s version %s\n", appName, version) + }, + } +) diff --git a/main.go b/main.go index a4e3022..42b4541 100644 --- a/main.go +++ b/main.go @@ -7,16 +7,16 @@ import ( "github.com/bpineau/katafygio/cmd" ) -//var privateExitHandler func(code int) = os.Exit var privateExitHandler = os.Exit -// ExitWrapper allow unit tests on main() exit values +// ExitWrapper allow tests on main() exit values func ExitWrapper(exit int) { privateExitHandler(exit) } func main() { - if err := cmd.Execute(); err != nil { + err := cmd.Execute() + if err != nil { fmt.Printf("%+v", err) ExitWrapper(1) }