From bb9c9cc1e8add31474f72571f2ed14605501edd8 Mon Sep 17 00:00:00 2001 From: Victor Castell Date: Wed, 22 Aug 2018 23:00:52 +0200 Subject: [PATCH] Use cobra for flags --- builder/files/dkron.yml | 20 +-- cmd/agent.go | 188 +++++++++++++++++++++++- cmd/dkron.go | 53 +++---- dkron/config.go | 123 ++++++---------- website/content/basics/configuration.md | 22 +-- 5 files changed, 271 insertions(+), 135 deletions(-) diff --git a/builder/files/dkron.yml b/builder/files/dkron.yml index c05da1cad..e350b4931 100644 --- a/builder/files/dkron.yml +++ b/builder/files/dkron.yml @@ -2,7 +2,7 @@ # backend: etcd # backend_machine: 127.0.0.1:2379 # server: false -# log_level: debug +# log-level: debug # tags: # role: web # datacenter: east @@ -12,12 +12,12 @@ # - 10.0.0.1 # - 10.0.0.2 # - 10.0.0.3 -# webhook_url: https://hooks.slack.com/services/XXXXXX/XXXXXXX/XXXXXXXXXXXXXXXXXXXX -# webhook_payload: "payload={\"text\": \"{{.Report}}\", \"channel\": \"#foo\"}" -# webhook_headers: Content-Type:application/x-www-form-urlencoded -# mail_host: email-smtp.eu-west-1.amazonaws.com -# mail_port: 25 -# mail_username": mailuser -# mail_password": mailpassword -# mail_from": cron@example.com -# mail_subject_prefix: [Dkron] +# webhook-url: https://hooks.slack.com/services/XXXXXX/XXXXXXX/XXXXXXXXXXXXXXXXXXXX +# webhook-payload: "payload={\"text\": \"{{.Report}}\", \"channel\": \"#foo\"}" +# webhook-headers: Content-Type:application/x-www-form-urlencoded +# mail-host: email-smtp.eu-west-1.amazonaws.com +# mail-port: 25 +# mail-username": mailuser +# mail-password": mailpassword +# mail-from": cron@example.com +# mail-subject-prefix: [Dkron] diff --git a/cmd/agent.go b/cmd/agent.go index dcb0389a2..f01c3c526 100644 --- a/cmd/agent.go +++ b/cmd/agent.go @@ -34,6 +34,8 @@ It also runs a web UI.`, // The returned value is the exit code. // protoc -I proto/ proto/executor.proto --go_out=plugins=grpc:dkron/ RunE: func(cmd *cobra.Command, args []string) error { + legacyConfig() + // Make sure we clean up any managed plugins at the end of this p := &Plugins{} if err := p.DiscoverPlugins(); err != nil { @@ -61,7 +63,7 @@ It also runs a web UI.`, func init() { dkronCmd.AddCommand(agentCmd) - agentCmd.Flags().AddGoFlagSet(dkron.ConfigFlagSet()) + agentCmd.Flags().AddFlagSet(dkron.ConfigFlagSet()) viper.BindPFlags(agentCmd.Flags()) } @@ -134,3 +136,187 @@ func handleReload() { } //Config reloading will also reload Notification settings } + +// Suport legacy config files for some time +func legacyConfig() { + s := viper.GetString("node_name") + if s != "" && viper.GetString("node-name") == "" { + log.WithField("param", "node_name").Warn("Deprecation warning: Config param name is deprecated and will be removed in future versions.") + config.NodeName = s + } + + s = viper.GetString("bind_addr") + if s != "" && viper.GetString("bind-addr") == "" { + log.WithField("param", "bind_addr").Warn("Deprecation warning: Config param name is deprecated and will be removed in future versions.") + config.BindAddr = s + } + + s = viper.GetString("http_addr") + if s != "" && viper.GetString("http-addr") == "" { + log.WithField("param", "http_addr").Warn("Deprecation warning: Config param name is deprecated and will be removed in future versions.") + config.HTTPAddr = s + } + + ss := viper.GetStringSlice("backend_machine") + if ss != nil && viper.GetStringSlice("backend_machine") == nil { + log.WithField("param", "backend_machine").Warn("Deprecation warning: Config param name is deprecated and will be removed in future versions.") + config.BackendMachines = ss + } + + s = viper.GetString("advertise_addr") + if s != "" && viper.GetString("advertise-addr") == "" { + log.WithField("param", "advertise_addr").Warn("Deprecation warning: Config param name is deprecated and will be removed in future versions.") + config.AdvertiseAddr = s + } + + s = viper.GetString("snapshot_path") + if s != "" && viper.GetString("snapshot-path") == "" { + log.WithField("param", "snapshot_path").Warn("Deprecation warning: Config param name is deprecated and will be removed in future versions.") + config.SnapshotPath = s + } + + d := viper.GetDuration("reconnect_interval") + if d != 0 && viper.GetDuration("reconnect-interval") == 0 { + log.WithField("param", "reconnect_interval").Warn("Deprecation warning: Config param name is deprecated and will be removed in future versions.") + config.ReconnectInterval = d + } + + d = viper.GetDuration("reconnect_timeout") + if d != 0 && viper.GetDuration("reconnect-timeout") == 0 { + log.WithField("param", "reconnect_timeout").Warn("Deprecation warning: Config param name is deprecated and will be removed in future versions.") + config.ReconnectTimeout = d + } + + d = viper.GetDuration("tombstone_timeout") + if d != 0 && viper.GetDuration("tombstone-timeout") == 0 { + log.WithField("param", "tombstone_timeout").Warn("Deprecation warning: Config param name is deprecated and will be removed in future versions.") + config.TombstoneTimeout = d + } + + b := viper.GetBool("disable_name_resolution") + if b != false && viper.GetBool("disable-name-resolution") == false { + log.WithField("param", "disable_name_resolution").Warn("Deprecation warning: Config param name is deprecated and will be removed in future versions.") + config.DisableNameResolution = b + } + + s = viper.GetString("keyring_file") + if s != "" && viper.GetString("keyring-file") == "" { + log.WithField("param", "keyring_file").Warn("Deprecation warning: Config param name is deprecated and will be removed in future versions.") + config.KeyringFile = s + } + + b = viper.GetBool("rejoin_after_leave") + if b != false && viper.GetBool("rejoin-after-leave") == false { + log.WithField("param", "rejoin_after_leave").Warn("Deprecation warning: Config param name is deprecated and will be removed in future versions.") + config.RejoinAfterLeave = b + } + + s = viper.GetString("encrypt_key") + if s != "" && viper.GetString("encrypt-key") == "" { + log.WithField("param", "encrypt_key").Warn("Deprecation warning: Config param name is deprecated and will be removed in future versions.") + config.EncryptKey = s + } + + ss = viper.GetStringSlice("start_join") + if ss != nil && viper.GetStringSlice("start-join") == nil { + log.WithField("param", "start_join").Warn("Deprecation warning: Config param name is deprecated and will be removed in future versions.") + config.StartJoin = ss + } + + i := viper.GetInt("rpc_port") + if i != 0 && viper.GetInt("rpc-port") == 0 { + log.WithField("param", "rpc_port").Warn("Deprecation warning: Config param name is deprecated and will be removed in future versions.") + config.RPCPort = i + } + + i = viper.GetInt("advertise_rpc_port") + if i != 0 && viper.GetInt("advertise-rpc-port") == 0 { + log.WithField("param", "advertise_rpc_port").Warn("Deprecation warning: Config param name is deprecated and will be removed in future versions.") + config.AdvertiseRPCPort = i + } + + s = viper.GetString("log_level") + if s != "" && viper.GetString("log-level") == "" { + log.WithField("param", "log_level").Warn("Deprecation warning: Config param name is deprecated and will be removed in future versions.") + config.LogLevel = s + } + + s = viper.GetString("mail_host") + if s != "" && viper.GetString("mail-host") == "" { + log.WithField("param", "mail_host").Warn("Deprecation warning: Config param name is deprecated and will be removed in future versions.") + config.MailHost = s + } + + i = viper.GetInt("mail_port") + if i != 0 && viper.GetInt("mail-port") == 0 { + log.WithField("param", "mail_port").Warn("Deprecation warning: Config param name is deprecated and will be removed in future versions.") + config.MailPort = uint16(i) + } + + s = viper.GetString("mail_username") + if s != "" && viper.GetString("mail-username") == "" { + log.WithField("param", "mail_username").Warn("Deprecation warning: Config param name is deprecated and will be removed in future versions.") + config.MailUsername = s + } + + s = viper.GetString("mail_password") + if s != "" && viper.GetString("mail-password") == "" { + log.WithField("param", "mail_password").Warn("Deprecation warning: Config param name is deprecated and will be removed in future versions.") + config.MailPassword = s + } + + s = viper.GetString("mail_from") + if s != "" && viper.GetString("mail-from") == "" { + log.WithField("param", "mail_from").Warn("Deprecation warning: Config param name is deprecated and will be removed in future versions.") + config.MailFrom = s + } + + s = viper.GetString("mail_payload") + if s != "" && viper.GetString("mail-payload") == "" { + log.WithField("param", "mail_payload").Warn("Deprecation warning: Config param name is deprecated and will be removed in future versions.") + config.MailPayload = s + } + + s = viper.GetString("mail_subject_prefix") + if s != "" && viper.GetString("mail-subject-prefix") == "" { + log.WithField("param", "mail_subject_prefix").Warn("Deprecation warning: Config param name is deprecated and will be removed in future versions.") + config.MailSubjectPrefix = s + } + + s = viper.GetString("webhook_url") + if s != "" && viper.GetString("webhook-url") == "" { + log.WithField("param", "webhook_url").Warn("Deprecation warning: Config param name is deprecated and will be removed in future versions.") + config.WebhookURL = s + } + + s = viper.GetString("webhook_payload") + if s != "" && viper.GetString("webhook-payload") == "" { + log.WithField("param", "webhook_payload").Warn("Deprecation warning: Config param name is deprecated and will be removed in future versions.") + config.WebhookPayload = s + } + + ss = viper.GetStringSlice("webhook_headers") + if ss != nil && viper.GetStringSlice("webhook-headers") == nil { + log.WithField("param", "webhook_headers").Warn("Deprecation warning: Config param name is deprecated and will be removed in future versions.") + config.WebhookHeaders = ss + } + + s = viper.GetString("dog_statsd_addr") + if s != "" && viper.GetString("dog-statsd-addr") == "" { + log.WithField("param", "dog_statsd_add").Warn("Deprecation warning: Config param name is deprecated and will be removed in future versions.") + config.DogStatsdAddr = s + } + + ss = viper.GetStringSlice("dog_statsd_tags") + if ss != nil && viper.GetStringSlice("dog-statsd-tags") == nil { + log.WithField("param", "dog_statsd_tags").Warn("Deprecation warning: Config param name is deprecated and will be removed in future versions.") + config.DogStatsdTags = ss + } + + s = viper.GetString("statsd_addr") + if s != "" && viper.GetString("statsd-addr") == "" { + log.WithField("param", "statsd_addr").Warn("Deprecation warning: Config param name is deprecated and will be removed in future versions.") + config.StatsdAddr = s + } + +} diff --git a/cmd/dkron.go b/cmd/dkron.go index e428c5061..5f23b24cc 100644 --- a/cmd/dkron.go +++ b/cmd/dkron.go @@ -3,12 +3,11 @@ package cmd import ( "fmt" "os" + "strconv" "strings" - "github.com/davecgh/go-spew/spew" "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "github.com/spf13/pflag" "github.com/spf13/viper" "github.com/victorcoder/dkron/dkron" ) @@ -37,23 +36,33 @@ func Execute() { func init() { cobra.OnInitialize(initConfig) - dkronCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is /etc/dkron/dkron.yml)") + dkronCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file path") } // initConfig reads in config file and ENV variables if set. func initConfig() { - viper.SetConfigName("dkron") // name of config file (without extension) - viper.AddConfigPath("/etc/dkron") // call multiple times to add many search paths - viper.AddConfigPath("$HOME/.dkron") // call multiple times to add many search paths - viper.AddConfigPath("./config") // call multiple times to add many search paths - viper.SetEnvPrefix("dkron") // will be uppercased automatically - viper.AutomaticEnv() + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + viper.SetConfigName("dkron") // name of config file (without extension) + viper.AddConfigPath("/etc/dkron") // call multiple times to add many search paths + viper.AddConfigPath("$HOME/.dkron") // call multiple times to add many search paths + viper.AddConfigPath("./config") // call multiple times to add many search paths + } + + viper.SetEnvPrefix("dkron") + replacer := strings.NewReplacer("-", "_") + viper.SetEnvKeyReplacer(replacer) + viper.AutomaticEnv() // read in environment variables that match err := viper.ReadInConfig() // Find and read the config file if err != nil { // Handle errors reading the config file logrus.WithError(err).Info("No valid config found: Applying default values.") } + viper.Unmarshal(config) + cliTags := viper.GetStringSlice("tag") var tags map[string]string @@ -64,32 +73,14 @@ func initConfig() { } } else { tags = viper.GetStringMapString("tags") + fmt.Println(tags) } - - server := viper.GetBool("server") - nodeName := viper.GetString("node_name") - - if server { - tags["dkron_server"] = "true" - } else { - tags["dkron_server"] = "false" - } + tags["dkron_server"] = strconv.FormatBool(config.Server) tags["dkron_version"] = dkron.Version - dkron.InitLogger(viper.GetString("log_level"), nodeName) + config.Tags = tags - dkronCmd.Flags().VisitAll(func(f *pflag.Flag) { - fmt.Println(f.Value.String()) - v := strings.Replace(f.Name, "-", "_", -1) - if f.Value.String() != f.DefValue { - viper.Set(v, f.Value.String()) - } else { - viper.SetDefault(v, f.Value.String()) - } - }) - - viper.Unmarshal(config) - spew.Dump(config) + dkron.InitLogger(viper.GetString("log_level"), config.NodeName) } // unmarshalTags is a utility function which takes a slice of strings in diff --git a/dkron/config.go b/dkron/config.go index 73bb704b7..1f270c0bf 100644 --- a/dkron/config.go +++ b/dkron/config.go @@ -2,102 +2,65 @@ package dkron import ( "encoding/base64" - "flag" "fmt" "net" "os" - "strings" "time" - "github.com/spf13/viper" + flag "github.com/spf13/pflag" ) // Config stores all configuration options for the dkron package. type Config struct { - NodeName string - BindAddr string - HTTPAddr string + NodeName string `mapstructure:"node-name"` + BindAddr string `mapstructure:"bind-addr"` + HTTPAddr string `mapstructure:"http-addr"` Discover string Backend string - BackendMachines []string + BackendMachines []string `mapstructure:"backend-machine"` Profile string Interface string - AdvertiseAddr string + AdvertiseAddr string `mapstructure:"advertise-addr"` Tags map[string]string - SnapshotPath string - ReconnectInterval time.Duration - ReconnectTimeout time.Duration - TombstoneTimeout time.Duration - DisableNameResolution bool - KeyringFile string - RejoinAfterLeave bool + SnapshotPath string `mapstructure:"snapshot-path"` + ReconnectInterval time.Duration `mapstructure:"reconnect-interval"` + ReconnectTimeout time.Duration `mapstructure:"reconnect-timeout"` + TombstoneTimeout time.Duration `mapstructure:"tombstone-timeout"` + DisableNameResolution bool `mapstructure:"disable-name-resolution"` + KeyringFile string `mapstructure:"keyring-file"` + RejoinAfterLeave bool `mapstructure:"rejoin-after-leave"` Server bool - EncryptKey string - StartJoin AppendSliceValue + EncryptKey string `mapstructure:"encrypt-key"` + StartJoin AppendSliceValue `mapstructure:"start-join"` Keyspace string - UIDir string - RPCPort int - AdvertiseRPCPort int - LogLevel string - - MailHost string - MailPort uint16 `mapstructure:"mail_port"` - MailUsername string - MailPassword string - MailFrom string - MailPayload string - MailSubjectPrefix string - - WebhookURL string - WebhookPayload string - WebhookHeaders []string + RPCPort int `mapstructure:"rpc-port"` + AdvertiseRPCPort int `mapstructure:"advertise-rpc-port"` + LogLevel string `mapstructure:"log-level"` + + MailHost string `mapstructure:"mail-host"` + MailPort uint16 `mapstructure:"mail-port"` + MailUsername string `mapstructure:"mail-username"` + MailPassword string `mapstructure:"mail-password"` + MailFrom string `mapstructure:"mail-from"` + MailPayload string `mapstructure:"mail-payload"` + MailSubjectPrefix string `mapstructure:"mail-subject-prefix"` + + WebhookURL string `mapstructure:"webhook-url"` + WebhookPayload string `mapstructure:"webhook-payload"` + WebhookHeaders []string `mapstructure:"webhook-headers"` // DogStatsdAddr is the address of a dogstatsd instance. If provided, // metrics will be sent to that instance - DogStatsdAddr string + DogStatsdAddr string `mapstructure:"dog-statsd-addr"` // DogStatsdTags are the global tags that should be sent with each packet to dogstatsd // It is a list of strings, where each string looks like "my_tag_name:my_tag_value" - DogStatsdTags []string - StatsdAddr string + DogStatsdTags []string `mapstructure:"dog-statsd-tags"` + StatsdAddr string `mapstructure:"statsd-addr"` } // DefaultBindPort is the default port that dkron will use for Serf communication const DefaultBindPort int = 8946 -func init() { - viper.SetConfigName("dkron") // name of config file (without extension) - viper.AddConfigPath("/etc/dkron") // call multiple times to add many search paths - viper.AddConfigPath("$HOME/.dkron") // call multiple times to add many search paths - viper.AddConfigPath("./config") // call multiple times to add many search paths - viper.SetEnvPrefix("dkron") // will be uppercased automatically - viper.AutomaticEnv() -} - -// NewConfig creates a Config object and will set up the dkron configuration using -// the command line and any file configs. -func NewConfig(args []string) *Config { - cmdFlags := ConfigFlagSet() - - if err := cmdFlags.Parse(args); err != nil { - log.WithError(err).Error("agent: Error parsing flags") - } - - cmdFlags.VisitAll(func(f *flag.Flag) { - v := strings.Replace(f.Name, "-", "_", -1) - if f.Value.String() != f.DefValue { - if sliceValue, ok := f.Value.(*AppendSliceValue); ok { - viper.Set(v, ([]string)(*sliceValue)) - } else { - viper.Set(v, f.Value.String()) - } - } else { - viper.SetDefault(v, f.Value.String()) - } - }) - - return &Config{} -} - // configFlagSet creates all of our configuration flags. func ConfigFlagSet() *flag.FlagSet { hostname, err := os.Hostname() @@ -105,24 +68,20 @@ func ConfigFlagSet() *flag.FlagSet { log.Panic(err) } - cmdFlags := flag.NewFlagSet("dkron agent [options]", flag.ContinueOnError) + cmdFlags := flag.NewFlagSet("agent flagset", flag.ContinueOnError) cmdFlags.Bool("server", false, "start dkron server") - cmdFlags.String("node", hostname, "[Deprecated use node-name]") cmdFlags.String("node-name", hostname, "node name") cmdFlags.String("bind", fmt.Sprintf("0.0.0.0:%d", DefaultBindPort), "[Deprecated use bind-addr]") cmdFlags.String("bind-addr", fmt.Sprintf("0.0.0.0:%d", DefaultBindPort), "address to bind listeners to") - cmdFlags.String("advertise", "", "[Deprecated use advertise-addr]") cmdFlags.String("advertise-addr", "", "address to advertise to other nodes") cmdFlags.String("http-addr", ":8080", "HTTP address") cmdFlags.String("discover", "dkron", "mDNS discovery name") cmdFlags.String("backend", "etcd", "store backend") - cmdFlags.String("backend-machine", "127.0.0.1:2379", "store backend machines addresses") + cmdFlags.StringSlice("backend-machine", []string{"127.0.0.1:2379"}, "store backend machines addresses") cmdFlags.String("profile", "lan", "timing profile to use (lan, wan, local)") - var join []string - cmdFlags.Var((*AppendSliceValue)(&join), "join", "address of agent to join on startup") - var tag []string - cmdFlags.Var((*AppendSliceValue)(&tag), "tag", "tag pair, specified as key=value") + cmdFlags.StringSlice("join", []string{}, "address of agent to join on startup") + cmdFlags.StringSlice("tag", []string{}, "tag pair, specified as key=value") cmdFlags.String("keyspace", "dkron", "key namespace to use") cmdFlags.String("encrypt", "", "encryption key") cmdFlags.String("log-level", "info", "Log level (debug, info, warn, error, fatal, panic), defaults to info") @@ -140,14 +99,14 @@ func ConfigFlagSet() *flag.FlagSet { cmdFlags.String("webhook-url", "", "notification webhook url") cmdFlags.String("webhook-payload", "", "notification webhook payload") - webhookHeaders := &AppendSliceValue{} - cmdFlags.Var(webhookHeaders, "webhook-header", "notification webhook additional header") + cmdFlags.StringSlice("webhook-header", []string{}, "notification webhook additional header") cmdFlags.String("dog-statsd-addr", "", "DataDog Agent address") - var dogStatsdTags []string - cmdFlags.Var((*AppendSliceValue)(&dogStatsdTags), "dog-statsd-tags", "Datadog tags, specified as key:value") + cmdFlags.StringSlice("dog-statsd-tags", []string{}, "Datadog tags, specified as key:value") cmdFlags.String("statsd-addr", "", "Statsd Address") + cmdFlags.StringToString("tags", map[string]string{}, "fufifa") + return cmdFlags } diff --git a/website/content/basics/configuration.md b/website/content/basics/configuration.md index 0b2b35611..dab30b987 100644 --- a/website/content/basics/configuration.md +++ b/website/content/basics/configuration.md @@ -55,9 +55,9 @@ Settings for dkron can be specified in three ways: Using a `config/dkron.json` c ```yaml # Dkron example configuration file # backend: etcd -# backend_machine: 127.0.0.1:2379 +# backend-machine: 127.0.0.1:2379 # server: false -# log_level: debug +# log-level: debug # tags: # role: web # datacenter: east @@ -67,13 +67,13 @@ Settings for dkron can be specified in three ways: Using a `config/dkron.json` c # - 10.0.0.1 # - 10.0.0.2 # - 10.0.0.3 -# webhook_url: https://hooks.slack.com/services/XXXXXX/XXXXXXX/XXXXXXXXXXXXXXXXXXXX -# webhook_payload: "payload={\"text\": \"{{.Report}}\", \"channel\": \"#foo\"}" -# webhook_headers: Content-Type:application/x-www-form-urlencoded -# mail_host: email-smtp.eu-west-1.amazonaws.com -# mail_port: 25 -# mail_username": mailuser -# mail_password": mailpassword -# mail_from": cron@example.com -# mail_subject_prefix: [Dkron] +# webhook-url: https://hooks.slack.com/services/XXXXXX/XXXXXXX/XXXXXXXXXXXXXXXXXXXX +# webhook-payload: "payload={\"text\": \"{{.Report}}\", \"channel\": \"#foo\"}" +# webhook-headers: Content-Type:application/x-www-form-urlencoded +# mail-host: email-smtp.eu-west-1.amazonaws.com +# mail-port: 25 +# mail-username": mailuser +# mail-password": mailpassword +# mail-from": cron@example.com +# mail-subject_prefix: [Dkron] ```