// parseFlags is a helper function for parsing command line flags using Go's // Flag library. This is extracted into a helper to keep the main function // small, but it also makes writing tests for parsing command line arguments // much easier and cleaner. func (cli *CLI) parseFlags(args []string) (*Config, bool, bool, bool, error) { var dry, once, version bool config := DefaultConfig() // Parse the flags and options flags := flag.NewFlagSet(Name, flag.ContinueOnError) flags.SetOutput(cli.errStream) flags.Usage = func() { fmt.Fprintf(cli.errStream, usage, Name) } flags.Var((funcVar)(func(s string) error { config.Consul = s config.set("consul") return nil }), "consul", "") flags.Var((funcVar)(func(s string) error { config.Token = s config.set("token") return nil }), "token", "") flags.Var((funcVar)(func(s string) error { config.Auth.Enabled = true config.set("auth.enabled") if strings.Contains(s, ":") { split := strings.SplitN(s, ":", 2) config.Auth.Username = split[0] config.set("auth.username") config.Auth.Password = split[1] config.set("auth.password") } else { config.Auth.Username = s config.set("auth.username") } return nil }), "auth", "") flags.Var((funcBoolVar)(func(b bool) error { config.SSL.Enabled = b config.set("ssl") config.set("ssl.enabled") return nil }), "ssl", "") flags.Var((funcBoolVar)(func(b bool) error { config.SSL.Verify = b config.set("ssl") config.set("ssl.verify") return nil }), "ssl-verify", "") flags.Var((funcVar)(func(s string) error { config.SSL.Cert = s config.set("ssl") config.set("ssl.cert") return nil }), "ssl-cert", "") flags.Var((funcVar)(func(s string) error { config.SSL.Key = s config.set("ssl") config.set("ssl.key") return nil }), "ssl-key", "") flags.Var((funcVar)(func(s string) error { config.SSL.CaCert = s config.set("ssl") config.set("ssl.ca_cert") return nil }), "ssl-ca-cert", "") flags.Var((funcDurationVar)(func(d time.Duration) error { config.MaxStale = d config.set("max_stale") return nil }), "max-stale", "") flags.Var((funcVar)(func(s string) error { t, err := ParseConfigTemplate(s) if err != nil { return err } if config.ConfigTemplates == nil { config.ConfigTemplates = make([]*ConfigTemplate, 0, 1) } config.ConfigTemplates = append(config.ConfigTemplates, t) return nil }), "template", "") flags.Var((funcBoolVar)(func(b bool) error { config.Syslog.Enabled = b config.set("syslog") config.set("syslog.enabled") return nil }), "syslog", "") flags.Var((funcVar)(func(s string) error { config.Syslog.Facility = s config.set("syslog.facility") return nil }), "syslog-facility", "") flags.Var((funcBoolVar)(func(b bool) error { config.Deduplicate.Enabled = b config.set("deduplicate") config.set("deduplicate.enabled") return nil }), "dedup", "") flags.Var((funcVar)(func(s string) error { w, err := watch.ParseWait(s) if err != nil { return err } config.Wait.Min = w.Min config.Wait.Max = w.Max config.set("wait") return nil }), "wait", "") flags.Var((funcDurationVar)(func(d time.Duration) error { config.Retry = d config.set("retry") return nil }), "retry", "") flags.Var((funcVar)(func(s string) error { config.Path = s config.set("path") return nil }), "config", "") flags.Var((funcVar)(func(s string) error { config.PidFile = s config.set("pid_file") return nil }), "pid-file", "") flags.Var((funcVar)(func(s string) error { config.LogLevel = s config.set("log_level") return nil }), "log-level", "") flags.BoolVar(&once, "once", false, "") flags.BoolVar(&dry, "dry", false, "") flags.BoolVar(&version, "v", false, "") flags.BoolVar(&version, "version", false, "") // If there was a parser error, stop if err := flags.Parse(args); err != nil { return nil, false, false, false, err } // Error if extra arguments are present args = flags.Args() if len(args) > 0 { return nil, false, false, false, fmt.Errorf("cli: extra argument(s): %q", args) } return config, once, dry, version, nil }
// parseFlags is a helper function for parsing command line flags using Go's // Flag library. This is extracted into a helper to keep the main function // small, but it also makes writing tests for parsing command line arguments // much easier and cleaner. func (cli *CLI) parseFlags(args []string) (*Config, []string, bool, bool, error) { var once, version bool var config = DefaultConfig() // Parse the flags and options flags := flag.NewFlagSet(Name, flag.ContinueOnError) flags.SetOutput(cli.errStream) flags.Usage = func() { fmt.Fprintf(cli.errStream, usage, Name) } flags.Var((funcVar)(func(s string) error { config.Consul = s config.set("consul") return nil }), "consul", "") flags.Var((funcVar)(func(s string) error { config.Token = s config.set("token") return nil }), "token", "") flags.Var((funcVar)(func(s string) error { s = strings.TrimPrefix(s, "/") p, err := dep.ParseStoreKeyPrefix(s) if err != nil { return err } if config.Prefixes == nil { config.Prefixes = make([]*dep.StoreKeyPrefix, 0, 1) } config.Prefixes = append(config.Prefixes, p) return nil }), "prefix", "") flags.Var((funcVar)(func(s string) error { config.Auth.Enabled = true config.set("auth.enabled") if strings.Contains(s, ":") { split := strings.SplitN(s, ":", 2) config.Auth.Username = split[0] config.set("auth.username") config.Auth.Password = split[1] config.set("auth.password") } else { config.Auth.Username = s config.set("auth.username") } return nil }), "auth", "") flags.Var((funcBoolVar)(func(b bool) error { config.SSL.Enabled = b config.set("ssl") config.set("ssl.enabled") return nil }), "ssl", "") flags.Var((funcBoolVar)(func(b bool) error { config.SSL.Verify = b config.set("ssl.verify") return nil }), "ssl-verify", "") flags.Var((funcVar)(func(s string) error { config.SSL.Cert = s config.set("ssl.cert") return nil }), "ssl-cert", "") flags.Var((funcVar)(func(s string) error { config.SSL.CaCert = s config.set("ssl.ca_cert") return nil }), "ssl-ca-cert", "") flags.Var((funcDurationVar)(func(d time.Duration) error { config.MaxStale = d config.set("max_stale") return nil }), "max-stale", "") flags.Var((funcBoolVar)(func(b bool) error { config.Syslog.Enabled = b config.set("syslog.enabled") return nil }), "syslog", "") flags.Var((funcVar)(func(s string) error { config.Syslog.Facility = s config.set("syslog.facility") return nil }), "syslog-facility", "") flags.Var((funcVar)(func(s string) error { w, err := watch.ParseWait(s) if err != nil { return err } config.Wait.Min = w.Min config.Wait.Max = w.Max config.set("wait") return nil }), "wait", "") flags.Var((funcDurationVar)(func(d time.Duration) error { config.Retry = d config.set("retry") return nil }), "retry", "") flags.Var((funcBoolVar)(func(b bool) error { config.Sanitize = b config.set("sanitize") return nil }), "sanitize", "") flags.Var((funcBoolVar)(func(b bool) error { config.Upcase = b config.set("upcase") return nil }), "upcase", "") flags.Var((funcVar)(func(s string) error { config.Path = s config.set("path") return nil }), "config", "") flags.Var((funcVar)(func(s string) error { config.KillSignal = s config.set("kill_signal") return nil }), "kill-signal", "") flags.Var((funcVar)(func(s string) error { config.LogLevel = s config.set("log_level") return nil }), "log-level", "") flags.Var((funcBoolVar)(func(b bool) error { config.Pristine = b config.set("pristine") return nil }), "pristine", "") flags.BoolVar(&once, "once", false, "") flags.BoolVar(&version, "v", false, "") flags.BoolVar(&version, "version", false, "") // If there was a parser error, stop if err := flags.Parse(args); err != nil { return nil, nil, false, false, err } return config, flags.Args(), once, version, nil }
// ParseConfig reads the configuration file at the given path and returns a new // Config struct with the data populated. func ParseConfig(path string) (*Config, error) { var errs *multierror.Error // Read the contents of the file contents, err := ioutil.ReadFile(path) if err != nil { return nil, fmt.Errorf("error reading config at %q: %s", path, err) } // Parse the file (could be HCL or JSON) var shadow interface{} if err := hcl.Decode(&shadow, string(contents)); err != nil { return nil, fmt.Errorf("error decoding config at %q: %s", path, err) } // Convert to a map and flatten the keys we want to flatten parsed, ok := shadow.(map[string]interface{}) if !ok { return nil, fmt.Errorf("error converting config at %q", path) } flattenKeys(parsed, []string{"auth", "ssl", "syslog", "vault"}) // if raw, ok := parsed["max_stale"]; ok { if typed, ok := raw.(string); !ok { err = fmt.Errorf("error converting max_stale to string at %q", path) errs = multierror.Append(errs, err) delete(parsed, "max_stale") } else { if stale, err := time.ParseDuration(typed); err != nil { err = fmt.Errorf("error parsing max_stale at %q: %s", path, err) errs = multierror.Append(errs, err) delete(parsed, "max_stale") } else { parsed["max_stale"] = stale } } } if raw, ok := parsed["retry"]; ok { if typed, ok := raw.(string); !ok { err = fmt.Errorf("error converting retry to string at %q", path) errs = multierror.Append(errs, err) delete(parsed, "retry") } else { if stale, err := time.ParseDuration(typed); err != nil { err = fmt.Errorf("error parsing retry at %q: %s", path, err) errs = multierror.Append(errs, err) delete(parsed, "retry") } else { parsed["retry"] = stale } } } if raw, ok := parsed["wait"]; ok { if typed, ok := raw.(string); !ok { err = fmt.Errorf("error converting wait to string at %q", path) errs = multierror.Append(errs, err) delete(parsed, "wait") } else { if wait, err := watch.ParseWait(typed); err != nil { err = fmt.Errorf("error parsing wait at %q: %s", path, err) errs = multierror.Append(errs, err) delete(parsed, "wait") } else { parsed["wait"] = map[string]time.Duration{ "min": wait.Min, "max": wait.Max, } } } } // Create a new, empty config config := new(Config) // Use mapstructure to populate the basic config fields metadata := new(mapstructure.Metadata) decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ ErrorUnused: true, Metadata: metadata, Result: config, }) if err != nil { errs = multierror.Append(errs, err) return nil, errs.ErrorOrNil() } if err := decoder.Decode(parsed); err != nil { errs = multierror.Append(errs, err) return nil, errs.ErrorOrNil() } // Store a reference to the path where this config was read from config.Path = path // Update the list of set keys if config.setKeys == nil { config.setKeys = make(map[string]struct{}) } for _, key := range metadata.Keys { if _, ok := config.setKeys[key]; !ok { config.setKeys[key] = struct{}{} } } config.setKeys["path"] = struct{}{} d := DefaultConfig() d.Merge(config) config = d return config, errs.ErrorOrNil() }
// ParseConfig reads the configuration file at the given path and returns a new // Config struct with the data populated. func ParseConfig(path string) (*Config, error) { var errs *multierror.Error // Read the contents of the file contents, err := ioutil.ReadFile(path) if err != nil { return nil, fmt.Errorf("error reading config at %q: %s", path, err) } // Parse the file (could be HCL or JSON) var shadow interface{} if err := hcl.Decode(&shadow, string(contents)); err != nil { return nil, fmt.Errorf("error decoding config at %q: %s", path, err) } // Convert to a map and flatten the keys we want to flatten parsed, ok := shadow.(map[string]interface{}) if !ok { return nil, fmt.Errorf("error converting config at %q", path) } flattenKeys(parsed, []string{"auth", "ssl", "syslog"}) // Parse the prefixes if raw, ok := parsed["prefixes"]; ok { if typed, ok := raw.([]interface{}); !ok { err = fmt.Errorf("error converting prefixes to []interface{} at %q, was %T", path, raw) errs = multierror.Append(errs, err) delete(parsed, "prefixes") } else { prefixes := make([]*dep.StoreKeyPrefix, 0, len(typed)) for _, p := range typed { if s, ok := p.(string); ok { if prefix, err := dep.ParseStoreKeyPrefix(s); err != nil { err = fmt.Errorf("error parsing prefix %q at %q: %s", p, path, err) errs = multierror.Append(errs, err) } else { prefixes = append(prefixes, prefix) } } else { err = fmt.Errorf("error converting %T to string", p) errs = multierror.Append(errs, err) delete(parsed, "prefixes") } } parsed["prefixes"] = prefixes } } // Parse the wait component if raw, ok := parsed["wait"]; ok { if typed, ok := raw.(string); !ok { err = fmt.Errorf("error converting wait to string at %q", path) errs = multierror.Append(errs, err) delete(parsed, "wait") } else { if wait, err := watch.ParseWait(typed); err != nil { err = fmt.Errorf("error parsing wait at %q: %s", path, err) errs = multierror.Append(errs, err) delete(parsed, "wait") } else { parsed["wait"] = map[string]time.Duration{ "min": wait.Min, "max": wait.Max, } } } } // Create a new, empty config config := new(Config) // Use mapstructure to populate the basic config fields metadata := new(mapstructure.Metadata) decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ DecodeHook: mapstructure.ComposeDecodeHookFunc( mapstructure.StringToSliceHookFunc(","), mapstructure.StringToTimeDurationHookFunc(), ), ErrorUnused: true, Metadata: metadata, Result: config, }) if err != nil { errs = multierror.Append(errs, err) return nil, errs.ErrorOrNil() } if err := decoder.Decode(parsed); err != nil { errs = multierror.Append(errs, err) return nil, errs.ErrorOrNil() } // Store a reference to the path where this config was read from config.Path = path // Update the list of set keys if config.setKeys == nil { config.setKeys = make(map[string]struct{}) } for _, key := range metadata.Keys { if _, ok := config.setKeys[key]; !ok { config.setKeys[key] = struct{}{} } } config.setKeys["path"] = struct{}{} d := DefaultConfig() d.Merge(config) config = d return config, errs.ErrorOrNil() }
// ParseConfig reads the configuration file at the given path and returns a new // Config struct with the data populated. func ParseConfig(path string) (*Config, error) { var errs *multierror.Error // Read the contents of the file contents, err := ioutil.ReadFile(path) if err != nil { errs = multierror.Append(errs, err) return nil, errs.ErrorOrNil() } // Parse the file (could be HCL or JSON) var parsed interface{} if err := hcl.Decode(&parsed, string(contents)); err != nil { errs = multierror.Append(errs, err) return nil, errs.ErrorOrNil() } // Create a new, empty config config := &Config{} // Use mapstructure to populate the basic config fields decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ ErrorUnused: true, Metadata: nil, Result: config, }) if err != nil { errs = multierror.Append(errs, err) return nil, errs.ErrorOrNil() } if err := decoder.Decode(parsed); err != nil { errs = multierror.Append(errs, err) return nil, errs.ErrorOrNil() } // Store a reference to the path where this config was read from config.Path = path // Parse the prefix sources for _, prefix := range config.Prefixes { parsed, err := dep.ParseStoreKeyPrefix(prefix.SourceRaw) if err != nil { errs = multierror.Append(errs, err) continue } prefix.Source = parsed // If no destination was given, default to the prefix if prefix.Destination == "" { prefix.Destination = parsed.Prefix } } // Parse the MaxStale component if raw := config.MaxStaleRaw; raw != "" { stale, err := time.ParseDuration(raw) if err == nil { config.MaxStale = stale } else { errs = multierror.Append(errs, fmt.Errorf("max_stale invalid: %v", err)) } } // Extract the last Auth block if len(config.AuthRaw) > 0 { config.Auth = config.AuthRaw[len(config.AuthRaw)-1] } // Extract the last SSL block if len(config.SSLRaw) > 0 { config.SSL = config.SSLRaw[len(config.SSLRaw)-1] } // Extract the last Syslog block if len(config.SyslogRaw) > 0 { config.Syslog = config.SyslogRaw[len(config.SyslogRaw)-1] } // Parse the Retry component if raw := config.RetryRaw; raw != "" { retry, err := time.ParseDuration(raw) if err == nil { config.Retry = retry } else { errs = multierror.Append(errs, fmt.Errorf("retry invalid: %v", err)) } } // Parse the Wait component if raw := config.WaitRaw; raw != "" { wait, err := watch.ParseWait(raw) if err == nil { config.Wait = wait } else { errs = multierror.Append(errs, fmt.Errorf("wait invalid: %v", err)) } } return config, errs.ErrorOrNil() }