From a4850153ce5cf2abc7266e10407e830440ca974f Mon Sep 17 00:00:00 2001 From: Stuart Nelson Date: Wed, 11 Apr 2018 15:45:20 +0200 Subject: [PATCH 1/4] Move amtool to modular structure Signed-off-by: Stuart Nelson --- cli/alert.go | 43 +++++----- cli/check_config.go | 23 +++--- cli/commands.go | 24 ++++++ cli/config.go | 8 +- cli/config_resolver.go | 57 +++++++++++++ cli/root.go | 180 ----------------------------------------- cli/silence.go | 14 +++- cli/silence_add.go | 59 ++++++++------ cli/silence_expire.go | 27 ++++--- cli/silence_import.go | 37 +++++---- cli/silence_query.go | 55 +++++++------ cli/silence_update.go | 61 ++++++++------ cli/utils.go | 4 +- cmd/amtool/main.go | 120 ++++++++++++++++++++++++++- 14 files changed, 394 insertions(+), 318 deletions(-) create mode 100644 cli/commands.go create mode 100644 cli/config_resolver.go delete mode 100644 cli/root.go diff --git a/cli/alert.go b/cli/alert.go index 92ce51ac9d..044be414ba 100644 --- a/cli/alert.go +++ b/cli/alert.go @@ -14,16 +14,21 @@ import ( "github.com/prometheus/alertmanager/pkg/parse" ) -var ( - alertCmd = app.Command("alert", "View and search through current alerts") - alertQueryCmd = alertCmd.Command("query", "View and search through current alerts").Default() - expired = alertQueryCmd.Flag("expired", "Show expired alerts as well as active").Bool() - silenced = alertQueryCmd.Flag("silenced", "Show silenced alerts").Short('s').Bool() - alertQuery = alertQueryCmd.Arg("matcher-groups", "Query filter").Strings() -) +type alertQueryCmd struct { + expired, silenced bool + matcherGroups []string +} -func init() { - alertQueryCmd.Action(queryAlerts) +func configureAlertCmd(app *kingpin.Application, longHelpText map[string]string) { + var ( + a = &alertQueryCmd{} + alertCmd = app.Command("alert", "View and search through current alerts") + alertQueryCmd = alertCmd.Command("query", "View and search through current alerts").Default() + ) + alertQueryCmd.Flag("expired", "Show expired alerts as well as active").BoolVar(&a.expired) + alertQueryCmd.Flag("silenced", "Show silenced alerts").Short('s').BoolVar(&a.silenced) + alertQueryCmd.Arg("matcher-groups", "Query filter").StringsVar(&a.matcherGroups) + alertQueryCmd.Action(a.queryAlerts) longHelpText["alert"] = `View and search through current alerts. Amtool has a simplified prometheus query syntax, but contains robust support for @@ -49,33 +54,33 @@ amtool alert query 'alertname=~foo.*' longHelpText["alert query"] = longHelpText["alert"] } -func queryAlerts(element *kingpin.ParseElement, ctx *kingpin.ParseContext) error { +func (a *alertQueryCmd) queryAlerts(element *kingpin.ParseElement, ctx *kingpin.ParseContext) error { var filterString = "" - if len(*alertQuery) == 1 { + if len(a.matcherGroups) == 1 { // If the parser fails then we likely don't have a (=|=~|!=|!~) so lets // assume that the user wants alertname= and prepend `alertname=` // to the front. - _, err := parse.Matcher((*alertQuery)[0]) + _, err := parse.Matcher(a.matcherGroups[0]) if err != nil { - filterString = fmt.Sprintf("{alertname=%s}", (*alertQuery)[0]) + filterString = fmt.Sprintf("{alertname=%s}", a.matcherGroups[0]) } else { - filterString = fmt.Sprintf("{%s}", strings.Join(*alertQuery, ",")) + filterString = fmt.Sprintf("{%s}", strings.Join(a.matcherGroups, ",")) } - } else if len(*alertQuery) > 1 { - filterString = fmt.Sprintf("{%s}", strings.Join(*alertQuery, ",")) + } else if len(a.matcherGroups) > 1 { + filterString = fmt.Sprintf("{%s}", strings.Join(a.matcherGroups, ",")) } - c, err := api.NewClient(api.Config{Address: (*alertmanagerUrl).String()}) + c, err := api.NewClient(api.Config{Address: alertmanagerURL.String()}) if err != nil { return err } alertAPI := client.NewAlertAPI(c) - fetchedAlerts, err := alertAPI.List(context.Background(), filterString, *expired, *silenced) + fetchedAlerts, err := alertAPI.List(context.Background(), filterString, a.expired, a.silenced) if err != nil { return err } - formatter, found := format.Formatters[*output] + formatter, found := format.Formatters[output] if !found { return errors.New("unknown output formatter") } diff --git a/cli/check_config.go b/cli/check_config.go index 27c869303d..cbc1340e9f 100644 --- a/cli/check_config.go +++ b/cli/check_config.go @@ -8,14 +8,19 @@ import ( "github.com/prometheus/alertmanager/template" ) -// alertCmd represents the alert command -var ( - checkConfigCmd = app.Command("check-config", "Validate alertmanager config files") - checkFiles = checkConfigCmd.Arg("check-files", "Files to be validated").ExistingFiles() -) +// TODO: This can just be a type that is []string, doesn't have to be a struct +type checkConfigCmd struct { + files []string +} + +func configureCheckConfigCmd(app *kingpin.Application, longHelpText map[string]string) { + var ( + c = &checkConfigCmd{} + checkConfigCmd = app.Command("check-config", "Validate alertmanager config files") + ) + checkConfigCmd.Arg("check-files", "Files to be validated").ExistingFilesVar(&c.files) -func init() { - checkConfigCmd.Action(checkConfig) + checkConfigCmd.Action(c.checkConfig) longHelpText["check-config"] = `Validate alertmanager config files Will validate the syntax and schema for alertmanager config file @@ -23,8 +28,8 @@ and associated templates. Non existing templates will not trigger errors` } -func checkConfig(element *kingpin.ParseElement, ctx *kingpin.ParseContext) error { - return CheckConfig(*checkFiles) +func (c *checkConfigCmd) checkConfig(element *kingpin.ParseElement, ctx *kingpin.ParseContext) error { + return CheckConfig(c.files) } func CheckConfig(args []string) error { diff --git a/cli/commands.go b/cli/commands.go new file mode 100644 index 0000000000..c063149a46 --- /dev/null +++ b/cli/commands.go @@ -0,0 +1,24 @@ +package cli + +import ( + "net/url" + + "github.com/alecthomas/kingpin" +) + +var ( + verbose bool + alertmanagerURL *url.URL + output string +) + +func ConfigureCommands(app *kingpin.Application, longHelpText map[string]string) { + app.Flag("verbose", "Verbose running information").Short('v').BoolVar(&verbose) + app.Flag("alertmanager.url", "Alertmanager to talk to").Required().URLVar(&alertmanagerURL) + app.Flag("output", "Output formatter (simple, extended, json)").Short('o').Default("simple").EnumVar(&output, "simple", "extended", "json") + + configureAlertCmd(app, longHelpText) + configureSilenceCmd(app, longHelpText) + configureCheckConfigCmd(app, longHelpText) + configureConfigCmd(app, longHelpText) +} diff --git a/cli/config.go b/cli/config.go index f00a697519..b020089303 100644 --- a/cli/config.go +++ b/cli/config.go @@ -12,9 +12,9 @@ import ( ) // configCmd represents the config command -var configCmd = app.Command("config", "View the running config").Action(queryConfig) +func configureConfigCmd(app *kingpin.Application, longHelpText map[string]string) { + app.Command("config", "View the running config").Action(queryConfig) -func init() { longHelpText["config"] = `View current config The amount of output is controlled by the output selection flag: - Simple: Print just the running config @@ -23,7 +23,7 @@ The amount of output is controlled by the output selection flag: } func queryConfig(element *kingpin.ParseElement, ctx *kingpin.ParseContext) error { - c, err := api.NewClient(api.Config{Address: (*alertmanagerUrl).String()}) + c, err := api.NewClient(api.Config{Address: alertmanagerURL.String()}) if err != nil { return err } @@ -33,7 +33,7 @@ func queryConfig(element *kingpin.ParseElement, ctx *kingpin.ParseContext) error return err } - formatter, found := format.Formatters[*output] + formatter, found := format.Formatters[output] if !found { return errors.New("unknown output formatter") } diff --git a/cli/config_resolver.go b/cli/config_resolver.go new file mode 100644 index 0000000000..d8a3161432 --- /dev/null +++ b/cli/config_resolver.go @@ -0,0 +1,57 @@ +package cli + +import ( + "io/ioutil" + "os" + + "github.com/alecthomas/kingpin" + "gopkg.in/yaml.v2" +) + +type ConfigResolver []map[string]string + +func NewConfigResolver() (ConfigResolver, error) { + files := []string{ + os.ExpandEnv("$HOME/.config/amtool/config.yml"), + "/etc/amtool/config.yml", + } + + resolver := ConfigResolver{} + for _, f := range files { + b, err := ioutil.ReadFile(f) + if err != nil { + if os.IsNotExist(err) { + continue + } + return nil, err + } + + var m map[string]string + err = yaml.Unmarshal(b, &m) + if err != nil { + return nil, err + } + resolver = append(resolver, m) + } + + return resolver, nil +} + +func (r ConfigResolver) Resolve(key string, context *kingpin.ParseContext) ([]string, error) { + for _, c := range r { + if v, ok := c[key]; ok { + return []string{v}, nil + } + } + return nil, nil +} + +// This function maps things which have previously had different names in the +// config file to their new names, so old configurations keep working +func BackwardsCompatibilityResolver(key string) string { + switch key { + case "require-comment": + return "comment_required" + } + return key +} diff --git a/cli/root.go b/cli/root.go deleted file mode 100644 index 9c4c48cad3..0000000000 --- a/cli/root.go +++ /dev/null @@ -1,180 +0,0 @@ -package cli - -import ( - "io/ioutil" - "os" - - "github.com/alecthomas/kingpin" - "github.com/prometheus/alertmanager/cli/format" - "github.com/prometheus/common/version" - "gopkg.in/yaml.v2" -) - -var ( - app = kingpin.New("amtool", "Alertmanager CLI").DefaultEnvars() - verbose = app.Flag("verbose", "Verbose running information").Short('v').Bool() - alertmanagerUrl = app.Flag("alertmanager.url", "Alertmanager to talk to").Required().URL() - output = app.Flag("output", "Output formatter (simple, extended, json)").Short('o').Default("simple").Enum("simple", "extended", "json") - - // This contains a mapping from command path to the long-format help string - // Separate subcommands with spaces, eg longHelpText["silence query"] - longHelpText = make(map[string]string) -) - -type amtoolConfigResolver struct { - configData []map[string]string -} - -func newConfigResolver() (amtoolConfigResolver, error) { - files := []string{ - os.ExpandEnv("$HOME/.config/amtool/config.yml"), - "/etc/amtool/config.yml", - } - - resolver := amtoolConfigResolver{ - configData: make([]map[string]string, 0), - } - for _, f := range files { - b, err := ioutil.ReadFile(f) - if err != nil { - if os.IsNotExist(err) { - continue - } - return resolver, err - } - var config map[string]string - err = yaml.Unmarshal(b, &config) - if err != nil { - return resolver, err - } - resolver.configData = append(resolver.configData, config) - } - - return resolver, nil -} - -func (r amtoolConfigResolver) Resolve(key string, context *kingpin.ParseContext) ([]string, error) { - for _, c := range r.configData { - if v, ok := c[key]; ok { - return []string{v}, nil - } - } - return nil, nil -} - -// This function maps things which have previously had different names in the -// config file to their new names, so old configurations keep working -func backwardsCompatibilityResolver(key string) string { - switch key { - case "require-comment": - return "comment_required" - } - return key -} - -func init() { - longHelpText["root"] = `View and modify the current Alertmanager state. - -Config File: -The alertmanager tool will read a config file in YAML format from one of two -default config locations: $HOME/.config/amtool/config.yml or -/etc/amtool/config.yml - -All flags can be given in the config file, but the following are the suited for -static configuration: - - alertmanager.url - Set a default alertmanager url for each request - - author - Set a default author value for new silences. If this argument is not - specified then the username will be used - - require-comment - Bool, whether to require a comment on silence creation. Defaults to true - - output - Set a default output type. Options are (simple, extended, json) - - date.format - Sets the output format for dates. Defaults to "2006-01-02 15:04:05 MST" -` -} - -// Execute parses the arguments and executes the corresponding command, it is -// called by cmd/amtool/main.main(). -func Execute() { - format.InitFormatFlags(app) - - app.Version(version.Print("amtool")) - app.GetFlag("help").Short('h') - app.UsageTemplate(kingpin.CompactUsageTemplate) - app.Flag("help-long", "Give more detailed help output").UsageAction(&kingpin.UsageContext{ - Template: longHelpTemplate, - Vars: map[string]interface{}{"LongHelp": longHelpText}, - }).Bool() - - configResolver, err := newConfigResolver() - if err != nil { - kingpin.Fatalf("could not load config file: %v\n", err) - } - // Use the same resolver twice, first for checking backwards compatibility, - // then again for the new names. This order ensures that the newest wins, if - // both old and new are present - app.Resolver( - kingpin.RenamingResolver(configResolver, backwardsCompatibilityResolver), - configResolver, - ) - - _, err = app.Parse(os.Args[1:]) - if err != nil { - kingpin.Fatalf("%v\n", err) - } -} - -const longHelpTemplate = `{{define "FormatCommands" -}} -{{range .FlattenedCommands -}} -{{if not .Hidden}} - {{.CmdSummary}} -{{.Help|Wrap 4}} -{{if .Flags -}} -{{with .Flags|FlagsToTwoColumns}}{{FormatTwoColumnsWithIndent . 4 2}}{{end}} -{{end -}} -{{end -}} -{{end -}} -{{end -}} - -{{define "FormatUsage" -}} -{{.AppSummary}} -{{if .Help}} -{{.Help|Wrap 0 -}} -{{end -}} - -{{end -}} - -{{if .Context.SelectedCommand -}} -{{T "usage:"}} {{.App.Name}} {{.App.FlagSummary}} {{.Context.SelectedCommand.CmdSummary}} - -{{index .LongHelp .Context.SelectedCommand.FullCommand}} -{{else}} -{{T "usage:"}} {{template "FormatUsage" .App}} -{{index .LongHelp "root"}} -{{end}} -{{if .Context.Flags -}} -{{T "Flags:"}} -{{.Context.Flags|FlagsToTwoColumns|FormatTwoColumns}} -{{end -}} -{{if .Context.Args -}} -{{T "Args:"}} -{{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}} -{{end -}} -{{if .Context.SelectedCommand -}} -{{if len .Context.SelectedCommand.Commands -}} -{{T "Subcommands:"}} -{{template "FormatCommands" .Context.SelectedCommand}} -{{end -}} -{{else if .App.Commands -}} -{{T "Commands:" -}} -{{template "FormatCommands" .App}} -{{end -}} -` diff --git a/cli/silence.go b/cli/silence.go index 698d965f29..0023898be5 100644 --- a/cli/silence.go +++ b/cli/silence.go @@ -1,7 +1,13 @@ package cli +import "github.com/alecthomas/kingpin" + // silenceCmd represents the silence command -var ( - silenceCmd = app.Command("silence", "Add, expire or view silences. For more information and additional flags see query help") - silenceQuiet = silenceCmd.Flag("quiet", "Only show silence ids").Short('q').Bool() -) +func configureSilenceCmd(app *kingpin.Application, longHelpText map[string]string) { + silenceCmd := app.Command("silence", "Add, expire or view silences. For more information and additional flags see query help") + configureSilenceAddCmd(silenceCmd, longHelpText) + configureSilenceExpireCmd(silenceCmd, longHelpText) + configureSilenceImportCmd(silenceCmd, longHelpText) + configureSilenceQueryCmd(silenceCmd, longHelpText) + configureSilenceUpdateCmd(silenceCmd, longHelpText) +} diff --git a/cli/silence_add.go b/cli/silence_add.go index d254b270a2..5246a3caee 100644 --- a/cli/silence_add.go +++ b/cli/silence_add.go @@ -23,19 +23,30 @@ func username() string { return user.Username } -var ( - addCmd = silenceCmd.Command("add", "Add a new alertmanager silence") - author = addCmd.Flag("author", "Username for CreatedBy field").Short('a').Default(username()).String() - requireComment = addCmd.Flag("require-comment", "Require comment to be set").Hidden().Default("true").Bool() - duration = addCmd.Flag("duration", "Duration of silence").Short('d').Default("1h").String() - addStart = addCmd.Flag("start", "Set when the silence should start. RFC3339 format 2006-01-02T15:04:05Z07:00").String() - addEnd = addCmd.Flag("end", "Set when the silence should end (overwrites duration). RFC3339 format 2006-01-02T15:04:05Z07:00").String() - comment = addCmd.Flag("comment", "A comment to help describe the silence").Short('c').String() - addArgs = addCmd.Arg("matcher-groups", "Query filter").Strings() -) +type silenceAddCmd struct { + author string + requireComment bool + duration string + start string + end string + comment string + matchers []string +} + +func configureSilenceAddCmd(cc *kingpin.CmdClause, longHelpText map[string]string) { + var ( + c = &silenceAddCmd{} + addCmd = cc.Command("add", "Add a new alertmanager silence") + ) + addCmd.Flag("author", "Username for CreatedBy field").Short('a').Default(username()).StringVar(&c.author) + addCmd.Flag("require-comment", "Require comment to be set").Hidden().Default("true").BoolVar(&c.requireComment) + addCmd.Flag("duration", "Duration of silence").Short('d').Default("1h").StringVar(&c.duration) + addCmd.Flag("start", "Set when the silence should start. RFC3339 format 2006-01-02T15:04:05Z07:00").StringVar(&c.start) + addCmd.Flag("end", "Set when the silence should end (overwrites duration). RFC3339 format 2006-01-02T15:04:05Z07:00").StringVar(&c.end) + addCmd.Flag("comment", "A comment to help describe the silence").Short('c').StringVar(&c.comment) + addCmd.Arg("matcher-groups", "Query filter").StringsVar(&c.matchers) + addCmd.Action(c.add) -func init() { - addCmd.Action(add) longHelpText["silence add"] = `Add a new alertmanager silence Amtool uses a simplified prometheus syntax to represent silences. The @@ -61,10 +72,10 @@ func init() { ` } -func add(element *kingpin.ParseElement, ctx *kingpin.ParseContext) error { +func (c *silenceAddCmd) add(element *kingpin.ParseElement, ctx *kingpin.ParseContext) error { var err error - matchers, err := parseMatchers(*addArgs) + matchers, err := parseMatchers(c.matchers) if err != nil { return err } @@ -74,13 +85,13 @@ func add(element *kingpin.ParseElement, ctx *kingpin.ParseContext) error { } var endsAt time.Time - if *addEnd != "" { - endsAt, err = time.Parse(time.RFC3339, *addEnd) + if c.end != "" { + endsAt, err = time.Parse(time.RFC3339, c.end) if err != nil { return err } } else { - d, err := model.ParseDuration(*duration) + d, err := model.ParseDuration(c.duration) if err != nil { return err } @@ -90,13 +101,13 @@ func add(element *kingpin.ParseElement, ctx *kingpin.ParseContext) error { endsAt = time.Now().UTC().Add(time.Duration(d)) } - if *requireComment && *comment == "" { + if c.requireComment && c.comment == "" { return errors.New("comment required by config") } var startsAt time.Time - if *addStart != "" { - startsAt, err = time.Parse(time.RFC3339, *addStart) + if c.start != "" { + startsAt, err = time.Parse(time.RFC3339, c.start) if err != nil { return err } @@ -118,15 +129,15 @@ func add(element *kingpin.ParseElement, ctx *kingpin.ParseContext) error { Matchers: typeMatchers, StartsAt: startsAt, EndsAt: endsAt, - CreatedBy: *author, - Comment: *comment, + CreatedBy: c.author, + Comment: c.comment, } - c, err := api.NewClient(api.Config{Address: (*alertmanagerUrl).String()}) + apiClient, err := api.NewClient(api.Config{Address: alertmanagerURL.String()}) if err != nil { return err } - silenceAPI := client.NewSilenceAPI(c) + silenceAPI := client.NewSilenceAPI(apiClient) silenceID, err := silenceAPI.Set(context.Background(), silence) if err != nil { return err diff --git a/cli/silence_expire.go b/cli/silence_expire.go index f7daaf341c..b3e26f9c18 100644 --- a/cli/silence_expire.go +++ b/cli/silence_expire.go @@ -10,28 +10,33 @@ import ( "github.com/prometheus/alertmanager/client" ) -var ( - expireCmd = silenceCmd.Command("expire", "expire an alertmanager silence") - expireIds = expireCmd.Arg("silence-ids", "Ids of silences to expire").Strings() -) +type silenceExpireCmd struct { + ids []string +} + +func configureSilenceExpireCmd(cc *kingpin.CmdClause, longHelpText map[string]string) { + var ( + c = &silenceExpireCmd{} + expireCmd = cc.Command("expire", "expire an alertmanager silence") + ) + expireCmd.Arg("silence-ids", "Ids of silences to expire").StringsVar(&c.ids) -func init() { - expireCmd.Action(expire) + expireCmd.Action(c.expire) longHelpText["silence expire"] = `Expire an alertmanager silence` } -func expire(element *kingpin.ParseElement, ctx *kingpin.ParseContext) error { - if len(*expireIds) < 1 { +func (c *silenceExpireCmd) expire(element *kingpin.ParseElement, ctx *kingpin.ParseContext) error { + if len(c.ids) < 1 { return errors.New("no silence IDs specified") } - c, err := api.NewClient(api.Config{Address: (*alertmanagerUrl).String()}) + apiClient, err := api.NewClient(api.Config{Address: alertmanagerURL.String()}) if err != nil { return err } - silenceAPI := client.NewSilenceAPI(c) + silenceAPI := client.NewSilenceAPI(apiClient) - for _, id := range *expireIds { + for _, id := range c.ids { err := silenceAPI.Expire(context.Background(), id) if err != nil { return err diff --git a/cli/silence_import.go b/cli/silence_import.go index 6cdea5d794..02c3bce4e4 100644 --- a/cli/silence_import.go +++ b/cli/silence_import.go @@ -16,15 +16,22 @@ import ( "github.com/prometheus/alertmanager/types" ) -var ( - importCmd = silenceCmd.Command("import", "Import silences") - force = importCmd.Flag("force", "Force adding new silences even if it already exists").Short('f').Bool() - workers = importCmd.Flag("worker", "Number of concurrent workers to use for import").Short('w').Default("8").Int() - importFile = importCmd.Arg("input-file", "JSON file with silences").ExistingFile() -) +type silenceImportCmd struct { + force bool + workers int + file string +} + +func configureSilenceImportCmd(cc *kingpin.CmdClause, longHelpText map[string]string) { + var ( + c = &silenceImportCmd{} + importCmd = cc.Command("import", "Import silences") + ) -func init() { - importCmd.Action(bulkImport) + importCmd.Flag("force", "Force adding new silences even if it already exists").Short('f').BoolVar(&c.force) + importCmd.Flag("worker", "Number of concurrent workers to use for import").Short('w').Default("8").IntVar(&c.workers) + importCmd.Arg("input-file", "JSON file with silences").ExistingFileVar(&c.file) + importCmd.Action(c.bulkImport) longHelpText["silence import"] = `Import alertmanager silences from JSON file or stdin This command can be used to bulk import silences from a JSON file @@ -55,11 +62,11 @@ func addSilenceWorker(sclient client.SilenceAPI, silencec <-chan *types.Silence, } } -func bulkImport(element *kingpin.ParseElement, ctx *kingpin.ParseContext) error { +func (c *silenceImportCmd) bulkImport(element *kingpin.ParseElement, ctx *kingpin.ParseContext) error { input := os.Stdin var err error - if *importFile != "" { - input, err = os.Open(*importFile) + if c.file != "" { + input, err = os.Open(c.file) if err != nil { return err } @@ -73,15 +80,15 @@ func bulkImport(element *kingpin.ParseElement, ctx *kingpin.ParseContext) error return errors.Wrap(err, "couldn't unmarshal input data, is it JSON?") } - c, err := api.NewClient(api.Config{Address: (*alertmanagerUrl).String()}) + apiClient, err := api.NewClient(api.Config{Address: alertmanagerURL.String()}) if err != nil { return err } - silenceAPI := client.NewSilenceAPI(c) + silenceAPI := client.NewSilenceAPI(apiClient) silencec := make(chan *types.Silence, 100) errc := make(chan error, 100) var wg sync.WaitGroup - for w := 0; w < *workers; w++ { + for w := 0; w < c.workers; w++ { wg.Add(1) go func() { addSilenceWorker(silenceAPI, silencec, errc) @@ -106,7 +113,7 @@ func bulkImport(element *kingpin.ParseElement, ctx *kingpin.ParseContext) error return errors.Wrap(err, "couldn't unmarshal input data, is it JSON?") } - if *force { + if c.force { // reset the silence ID so Alertmanager will always create new silence s.ID = "" } diff --git a/cli/silence_query.go b/cli/silence_query.go index 1bbe5a445b..7107a56ebf 100644 --- a/cli/silence_query.go +++ b/cli/silence_query.go @@ -16,15 +16,24 @@ import ( "github.com/prometheus/alertmanager/types" ) -var ( - queryCmd = silenceCmd.Command("query", "Query Alertmanager silences.").Default() - queryExpired = queryCmd.Flag("expired", "Show expired silences instead of active").Bool() - silenceQuery = queryCmd.Arg("matcher-groups", "Query filter").Strings() - queryWithin = queryCmd.Flag("within", "Show silences that will expire or have expired within a duration").Duration() -) +type silenceQueryCmd struct { + expired bool + quiet bool + matchers []string + within time.Duration +} -func init() { - queryCmd.Action(query) +func configureSilenceQueryCmd(cc *kingpin.CmdClause, longHelpText map[string]string) { + var ( + c = &silenceQueryCmd{} + queryCmd = cc.Command("query", "Query Alertmanager silences.").Default() + ) + + queryCmd.Flag("expired", "Show expired silences instead of active").BoolVar(&c.expired) + queryCmd.Flag("quiet", "Only show silence ids").Short('q').BoolVar(&c.quiet) + queryCmd.Arg("matcher-groups", "Query filter").StringsVar(&c.matchers) + queryCmd.Flag("within", "Show silences that will expire or have expired within a duration").DurationVar(&c.within) + queryCmd.Action(c.query) longHelpText["silence query"] = `Query Alertmanager silences. Amtool has a simplified prometheus query syntax, but contains robust support for @@ -67,27 +76,27 @@ amtool silence query --within 2h --expired returns all silences that expired within the preceeding 2 hours.` } -func query(element *kingpin.ParseElement, ctx *kingpin.ParseContext) error { +func (c *silenceQueryCmd) query(element *kingpin.ParseElement, ctx *kingpin.ParseContext) error { var filterString = "" - if len(*silenceQuery) == 1 { + if len(c.matchers) == 1 { // If the parser fails then we likely don't have a (=|=~|!=|!~) so lets // assume that the user wants alertname= and prepend `alertname=` // to the front. - _, err := parse.Matcher((*silenceQuery)[0]) + _, err := parse.Matcher(c.matchers[0]) if err != nil { - filterString = fmt.Sprintf("{alertname=%s}", (*silenceQuery)[0]) + filterString = fmt.Sprintf("{alertname=%s}", c.matchers[0]) } else { - filterString = fmt.Sprintf("{%s}", strings.Join(*silenceQuery, ",")) + filterString = fmt.Sprintf("{%s}", strings.Join(c.matchers, ",")) } - } else if len(*silenceQuery) > 1 { - filterString = fmt.Sprintf("{%s}", strings.Join(*silenceQuery, ",")) + } else if len(c.matchers) > 1 { + filterString = fmt.Sprintf("{%s}", strings.Join(c.matchers, ",")) } - c, err := api.NewClient(api.Config{Address: (*alertmanagerUrl).String()}) + apiClient, err := api.NewClient(api.Config{Address: alertmanagerURL.String()}) if err != nil { return err } - silenceAPI := client.NewSilenceAPI(c) + silenceAPI := client.NewSilenceAPI(apiClient) fetchedSilences, err := silenceAPI.List(context.Background(), filterString) if err != nil { return err @@ -96,31 +105,31 @@ func query(element *kingpin.ParseElement, ctx *kingpin.ParseContext) error { displaySilences := []types.Silence{} for _, silence := range fetchedSilences { // skip expired silences if --expired is not set - if !*queryExpired && silence.EndsAt.Before(time.Now()) { + if !c.expired && silence.EndsAt.Before(time.Now()) { continue } // skip active silences if --expired is set - if *queryExpired && silence.EndsAt.After(time.Now()) { + if c.expired && silence.EndsAt.After(time.Now()) { continue } // skip active silences expiring after "--within" - if !*queryExpired && int64(*queryWithin) > 0 && silence.EndsAt.After(time.Now().UTC().Add(*queryWithin)) { + if !c.expired && int64(c.within) > 0 && silence.EndsAt.After(time.Now().UTC().Add(c.within)) { continue } // skip silences that expired before "--within" - if *queryExpired && int64(*queryWithin) > 0 && silence.EndsAt.Before(time.Now().UTC().Add(-*queryWithin)) { + if c.expired && int64(c.within) > 0 && silence.EndsAt.Before(time.Now().UTC().Add(-c.within)) { continue } displaySilences = append(displaySilences, *silence) } - if *silenceQuiet { + if c.quiet { for _, silence := range displaySilences { fmt.Println(silence.ID) } } else { - formatter, found := format.Formatters[*output] + formatter, found := format.Formatters[output] if !found { return errors.New("unknown output formatter") } diff --git a/cli/silence_update.go b/cli/silence_update.go index 839ff0f079..598830bf0a 100644 --- a/cli/silence_update.go +++ b/cli/silence_update.go @@ -15,51 +15,62 @@ import ( "github.com/prometheus/common/model" ) -var ( - updateCmd = silenceCmd.Command("update", "Update silences") - updateDuration = updateCmd.Flag("duration", "Duration of silence").Short('d').String() - updateStart = updateCmd.Flag("start", "Set when the silence should start. RFC3339 format 2006-01-02T15:04:05Z07:00").String() - updateEnd = updateCmd.Flag("end", "Set when the silence should end (overwrites duration). RFC3339 format 2006-01-02T15:04:05Z07:00").String() - updateComment = updateCmd.Flag("comment", "A comment to help describe the silence").Short('c').String() - updateIds = updateCmd.Arg("update-ids", "Silence IDs to update").Strings() -) +type silenceUpdateCmd struct { + quiet bool + duration string + start string + end string + comment string + ids []string +} + +func configureSilenceUpdateCmd(cc *kingpin.CmdClause, longHelpText map[string]string) { + var ( + c = &silenceUpdateCmd{} + updateCmd = cc.Command("update", "Update silences") + ) + updateCmd.Flag("quiet", "Only show silence ids").Short('q').BoolVar(&c.quiet) + updateCmd.Flag("duration", "Duration of silence").Short('d').StringVar(&c.duration) + updateCmd.Flag("start", "Set when the silence should start. RFC3339 format 2006-01-02T15:04:05Z07:00").StringVar(&c.start) + updateCmd.Flag("end", "Set when the silence should end (overwrites duration). RFC3339 format 2006-01-02T15:04:05Z07:00").StringVar(&c.end) + updateCmd.Flag("comment", "A comment to help describe the silence").Short('c').StringVar(&c.comment) + updateCmd.Arg("update-ids", "Silence IDs to update").StringsVar(&c.ids) -func init() { - updateCmd.Action(update) + updateCmd.Action(c.update) longHelpText["silence update"] = `Extend or update existing silence in Alertmanager.` } -func update(element *kingpin.ParseElement, ctx *kingpin.ParseContext) error { - if len(*updateIds) < 1 { +func (c *silenceUpdateCmd) update(element *kingpin.ParseElement, ctx *kingpin.ParseContext) error { + if len(c.ids) < 1 { return fmt.Errorf("no silence IDs specified") } - c, err := api.NewClient(api.Config{Address: (*alertmanagerUrl).String()}) + apiClient, err := api.NewClient(api.Config{Address: alertmanagerURL.String()}) if err != nil { return err } - silenceAPI := client.NewSilenceAPI(c) + silenceAPI := client.NewSilenceAPI(apiClient) var updatedSilences []types.Silence - for _, silenceID := range *updateIds { + for _, silenceID := range c.ids { silence, err := silenceAPI.Get(context.Background(), silenceID) if err != nil { return err } - if *updateStart != "" { - silence.StartsAt, err = time.Parse(time.RFC3339, *updateStart) + if c.start != "" { + silence.StartsAt, err = time.Parse(time.RFC3339, c.start) if err != nil { return err } } - if *updateEnd != "" { - silence.EndsAt, err = time.Parse(time.RFC3339, *updateEnd) + if c.end != "" { + silence.EndsAt, err = time.Parse(time.RFC3339, c.end) if err != nil { return err } - } else if *updateDuration != "" { - d, err := model.ParseDuration(*updateDuration) + } else if c.duration != "" { + d, err := model.ParseDuration(c.duration) if err != nil { return err } @@ -73,8 +84,8 @@ func update(element *kingpin.ParseElement, ctx *kingpin.ParseContext) error { return errors.New("silence cannot start after it ends") } - if *updateComment != "" { - silence.Comment = *updateComment + if c.comment != "" { + silence.Comment = c.comment } newID, err := silenceAPI.Set(context.Background(), *silence) @@ -86,12 +97,12 @@ func update(element *kingpin.ParseElement, ctx *kingpin.ParseContext) error { updatedSilences = append(updatedSilences, *silence) } - if *silenceQuiet { + if c.quiet { for _, silence := range updatedSilences { fmt.Println(silence.ID) } } else { - formatter, found := format.Formatters[*output] + formatter, found := format.Formatters[output] if !found { return fmt.Errorf("unknown output formatter") } diff --git a/cli/utils.go b/cli/utils.go index 082837380e..b4d3264055 100644 --- a/cli/utils.go +++ b/cli/utils.go @@ -27,8 +27,8 @@ func (s ByAlphabetical) Less(i, j int) bool { } func GetAlertmanagerURL(p string) url.URL { - amURL := **alertmanagerUrl - amURL.Path = path.Join((*alertmanagerUrl).Path, p) + amURL := *alertmanagerURL + amURL.Path = path.Join(alertmanagerURL.Path, p) return amURL } diff --git a/cmd/amtool/main.go b/cmd/amtool/main.go index 18f2687078..b258e21326 100644 --- a/cmd/amtool/main.go +++ b/cmd/amtool/main.go @@ -1,7 +1,123 @@ package main -import "github.com/prometheus/alertmanager/cli" +import ( + "os" + + "github.com/alecthomas/kingpin" + "github.com/prometheus/alertmanager/cli" + "github.com/prometheus/alertmanager/cli/format" + "github.com/prometheus/common/version" +) func main() { - cli.Execute() + var ( + longHelpText = map[string]string{} + app = kingpin.New("amtool", "Alertmanager CLI").DefaultEnvars() + ) + + longHelpText["root"] = longHelpTextRoot + + format.InitFormatFlags(app) + + app.Version(version.Print("amtool")) + app.GetFlag("help").Short('h') + app.UsageTemplate(kingpin.CompactUsageTemplate) + app.Flag("help-long", "Give more detailed help output").UsageAction(&kingpin.UsageContext{ + Template: longHelpTemplate, + Vars: map[string]interface{}{"LongHelp": longHelpText}, + }).Bool() + + configResolver, err := cli.NewConfigResolver() + if err != nil { + kingpin.Fatalf("could not load config file: %v\n", err) + } + // Use the same resolver twice, first for checking backwards compatibility, + // then again for the new names. This order ensures that the newest wins, if + // both old and new are present + app.Resolver( + kingpin.RenamingResolver(configResolver, cli.BackwardsCompatibilityResolver), + configResolver, + ) + + cli.ConfigureCommands(app, longHelpText) + + _, err = app.Parse(os.Args[1:]) + if err != nil { + kingpin.Fatalf("%v\n", err) + } } + +const ( + longHelpTextRoot = `View and modify the current Alertmanager state. + +Config File: +The alertmanager tool will read a config file in YAML format from one of two +default config locations: $HOME/.config/amtool/config.yml or +/etc/amtool/config.yml + +All flags can be given in the config file, but the following are the suited for +static configuration: + + alertmanager.url + Set a default alertmanager url for each request + + author + Set a default author value for new silences. If this argument is not + specified then the username will be used + + require-comment + Bool, whether to require a comment on silence creation. Defaults to true + + output + Set a default output type. Options are (simple, extended, json) + + date.format + Sets the output format for dates. Defaults to "2006-01-02 15:04:05 MST" +` + longHelpTemplate = `{{define "FormatCommands" -}} +{{range .FlattenedCommands -}} +{{if not .Hidden}} + {{.CmdSummary}} +{{.Help|Wrap 4}} +{{if .Flags -}} +{{with .Flags|FlagsToTwoColumns}}{{FormatTwoColumnsWithIndent . 4 2}}{{end}} +{{end -}} +{{end -}} +{{end -}} +{{end -}} + +{{define "FormatUsage" -}} +{{.AppSummary}} +{{if .Help}} +{{.Help|Wrap 0 -}} +{{end -}} + +{{end -}} + +{{if .Context.SelectedCommand -}} +{{T "usage:"}} {{.App.Name}} {{.App.FlagSummary}} {{.Context.SelectedCommand.CmdSummary}} + +{{index .LongHelp .Context.SelectedCommand.FullCommand}} +{{else}} +{{T "usage:"}} {{template "FormatUsage" .App}} +{{index .LongHelp "root"}} +{{end}} +{{if .Context.Flags -}} +{{T "Flags:"}} +{{.Context.Flags|FlagsToTwoColumns|FormatTwoColumns}} +{{end -}} +{{if .Context.Args -}} +{{T "Args:"}} +{{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}} +{{end -}} +{{if .Context.SelectedCommand -}} +{{if len .Context.SelectedCommand.Commands -}} +{{T "Subcommands:"}} +{{template "FormatCommands" .Context.SelectedCommand}} +{{end -}} +{{else if .App.Commands -}} +{{T "Commands:" -}} +{{template "FormatCommands" .App}} +{{end -}} +` +) From f00458d18921ff2b7253a17a827fc3c6afab5992 Mon Sep 17 00:00:00 2001 From: Stuart Nelson Date: Wed, 11 Apr 2018 16:32:23 +0200 Subject: [PATCH 2/4] Move toplevel setup back into root.go Signed-off-by: Stuart Nelson --- cli/commands.go | 24 -------- cli/config_resolver.go | 10 +-- cli/root.go | 136 +++++++++++++++++++++++++++++++++++++++++ cmd/amtool/main.go | 120 +----------------------------------- 4 files changed, 143 insertions(+), 147 deletions(-) delete mode 100644 cli/commands.go create mode 100644 cli/root.go diff --git a/cli/commands.go b/cli/commands.go deleted file mode 100644 index c063149a46..0000000000 --- a/cli/commands.go +++ /dev/null @@ -1,24 +0,0 @@ -package cli - -import ( - "net/url" - - "github.com/alecthomas/kingpin" -) - -var ( - verbose bool - alertmanagerURL *url.URL - output string -) - -func ConfigureCommands(app *kingpin.Application, longHelpText map[string]string) { - app.Flag("verbose", "Verbose running information").Short('v').BoolVar(&verbose) - app.Flag("alertmanager.url", "Alertmanager to talk to").Required().URLVar(&alertmanagerURL) - app.Flag("output", "Output formatter (simple, extended, json)").Short('o').Default("simple").EnumVar(&output, "simple", "extended", "json") - - configureAlertCmd(app, longHelpText) - configureSilenceCmd(app, longHelpText) - configureCheckConfigCmd(app, longHelpText) - configureConfigCmd(app, longHelpText) -} diff --git a/cli/config_resolver.go b/cli/config_resolver.go index d8a3161432..3abdd6571b 100644 --- a/cli/config_resolver.go +++ b/cli/config_resolver.go @@ -8,15 +8,15 @@ import ( "gopkg.in/yaml.v2" ) -type ConfigResolver []map[string]string +type configResolver []map[string]string -func NewConfigResolver() (ConfigResolver, error) { +func newConfigResolver() (configResolver, error) { files := []string{ os.ExpandEnv("$HOME/.config/amtool/config.yml"), "/etc/amtool/config.yml", } - resolver := ConfigResolver{} + resolver := configResolver{} for _, f := range files { b, err := ioutil.ReadFile(f) if err != nil { @@ -37,7 +37,7 @@ func NewConfigResolver() (ConfigResolver, error) { return resolver, nil } -func (r ConfigResolver) Resolve(key string, context *kingpin.ParseContext) ([]string, error) { +func (r configResolver) Resolve(key string, context *kingpin.ParseContext) ([]string, error) { for _, c := range r { if v, ok := c[key]; ok { return []string{v}, nil @@ -48,7 +48,7 @@ func (r ConfigResolver) Resolve(key string, context *kingpin.ParseContext) ([]st // This function maps things which have previously had different names in the // config file to their new names, so old configurations keep working -func BackwardsCompatibilityResolver(key string) string { +func backwardsCompatibilityResolver(key string) string { switch key { case "require-comment": return "comment_required" diff --git a/cli/root.go b/cli/root.go new file mode 100644 index 0000000000..0ec0b1d022 --- /dev/null +++ b/cli/root.go @@ -0,0 +1,136 @@ +package cli + +import ( + "net/url" + "os" + + "github.com/alecthomas/kingpin" + "github.com/prometheus/alertmanager/cli/format" + "github.com/prometheus/common/version" +) + +var ( + verbose bool + alertmanagerURL *url.URL + output string +) + +func Execute() { + var ( + longHelpText = map[string]string{} + app = kingpin.New("amtool", "Alertmanager CLI").DefaultEnvars() + ) + + longHelpText["root"] = longHelpTextRoot + + format.InitFormatFlags(app) + + app.Flag("verbose", "Verbose running information").Short('v').BoolVar(&verbose) + app.Flag("alertmanager.url", "Alertmanager to talk to").Required().URLVar(&alertmanagerURL) + app.Flag("output", "Output formatter (simple, extended, json)").Short('o').Default("simple").EnumVar(&output, "simple", "extended", "json") + app.Version(version.Print("amtool")) + app.GetFlag("help").Short('h') + app.UsageTemplate(kingpin.CompactUsageTemplate) + app.Flag("help-long", "Give more detailed help output").UsageAction(&kingpin.UsageContext{ + Template: longHelpTemplate, + Vars: map[string]interface{}{"LongHelp": longHelpText}, + }).Bool() + + configResolver, err := newConfigResolver() + if err != nil { + kingpin.Fatalf("could not load config file: %v\n", err) + } + // Use the same resolver twice, first for checking backwards compatibility, + // then again for the new names. This order ensures that the newest wins, if + // both old and new are present + app.Resolver( + kingpin.RenamingResolver(configResolver, backwardsCompatibilityResolver), + configResolver, + ) + + configureAlertCmd(app, longHelpText) + configureSilenceCmd(app, longHelpText) + configureCheckConfigCmd(app, longHelpText) + configureConfigCmd(app, longHelpText) + + _, err = app.Parse(os.Args[1:]) + if err != nil { + kingpin.Fatalf("%v\n", err) + } + +} + +const ( + longHelpTextRoot = `View and modify the current Alertmanager state. + +Config File: +The alertmanager tool will read a config file in YAML format from one of two +default config locations: $HOME/.config/amtool/config.yml or +/etc/amtool/config.yml + +All flags can be given in the config file, but the following are the suited for +static configuration: + + alertmanager.url + Set a default alertmanager url for each request + + author + Set a default author value for new silences. If this argument is not + specified then the username will be used + + require-comment + Bool, whether to require a comment on silence creation. Defaults to true + + output + Set a default output type. Options are (simple, extended, json) + + date.format + Sets the output format for dates. Defaults to "2006-01-02 15:04:05 MST" +` + longHelpTemplate = `{{define "FormatCommands" -}} +{{range .FlattenedCommands -}} +{{if not .Hidden}} + {{.CmdSummary}} +{{.Help|Wrap 4}} +{{if .Flags -}} +{{with .Flags|FlagsToTwoColumns}}{{FormatTwoColumnsWithIndent . 4 2}}{{end}} +{{end -}} +{{end -}} +{{end -}} +{{end -}} + +{{define "FormatUsage" -}} +{{.AppSummary}} +{{if .Help}} +{{.Help|Wrap 0 -}} +{{end -}} + +{{end -}} + +{{if .Context.SelectedCommand -}} +{{T "usage:"}} {{.App.Name}} {{.App.FlagSummary}} {{.Context.SelectedCommand.CmdSummary}} + +{{index .LongHelp .Context.SelectedCommand.FullCommand}} +{{else}} +{{T "usage:"}} {{template "FormatUsage" .App}} +{{index .LongHelp "root"}} +{{end}} +{{if .Context.Flags -}} +{{T "Flags:"}} +{{.Context.Flags|FlagsToTwoColumns|FormatTwoColumns}} +{{end -}} +{{if .Context.Args -}} +{{T "Args:"}} +{{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}} +{{end -}} +{{if .Context.SelectedCommand -}} +{{if len .Context.SelectedCommand.Commands -}} +{{T "Subcommands:"}} +{{template "FormatCommands" .Context.SelectedCommand}} +{{end -}} +{{else if .App.Commands -}} +{{T "Commands:" -}} +{{template "FormatCommands" .App}} +{{end -}} +` +) diff --git a/cmd/amtool/main.go b/cmd/amtool/main.go index b258e21326..18f2687078 100644 --- a/cmd/amtool/main.go +++ b/cmd/amtool/main.go @@ -1,123 +1,7 @@ package main -import ( - "os" - - "github.com/alecthomas/kingpin" - "github.com/prometheus/alertmanager/cli" - "github.com/prometheus/alertmanager/cli/format" - "github.com/prometheus/common/version" -) +import "github.com/prometheus/alertmanager/cli" func main() { - var ( - longHelpText = map[string]string{} - app = kingpin.New("amtool", "Alertmanager CLI").DefaultEnvars() - ) - - longHelpText["root"] = longHelpTextRoot - - format.InitFormatFlags(app) - - app.Version(version.Print("amtool")) - app.GetFlag("help").Short('h') - app.UsageTemplate(kingpin.CompactUsageTemplate) - app.Flag("help-long", "Give more detailed help output").UsageAction(&kingpin.UsageContext{ - Template: longHelpTemplate, - Vars: map[string]interface{}{"LongHelp": longHelpText}, - }).Bool() - - configResolver, err := cli.NewConfigResolver() - if err != nil { - kingpin.Fatalf("could not load config file: %v\n", err) - } - // Use the same resolver twice, first for checking backwards compatibility, - // then again for the new names. This order ensures that the newest wins, if - // both old and new are present - app.Resolver( - kingpin.RenamingResolver(configResolver, cli.BackwardsCompatibilityResolver), - configResolver, - ) - - cli.ConfigureCommands(app, longHelpText) - - _, err = app.Parse(os.Args[1:]) - if err != nil { - kingpin.Fatalf("%v\n", err) - } + cli.Execute() } - -const ( - longHelpTextRoot = `View and modify the current Alertmanager state. - -Config File: -The alertmanager tool will read a config file in YAML format from one of two -default config locations: $HOME/.config/amtool/config.yml or -/etc/amtool/config.yml - -All flags can be given in the config file, but the following are the suited for -static configuration: - - alertmanager.url - Set a default alertmanager url for each request - - author - Set a default author value for new silences. If this argument is not - specified then the username will be used - - require-comment - Bool, whether to require a comment on silence creation. Defaults to true - - output - Set a default output type. Options are (simple, extended, json) - - date.format - Sets the output format for dates. Defaults to "2006-01-02 15:04:05 MST" -` - longHelpTemplate = `{{define "FormatCommands" -}} -{{range .FlattenedCommands -}} -{{if not .Hidden}} - {{.CmdSummary}} -{{.Help|Wrap 4}} -{{if .Flags -}} -{{with .Flags|FlagsToTwoColumns}}{{FormatTwoColumnsWithIndent . 4 2}}{{end}} -{{end -}} -{{end -}} -{{end -}} -{{end -}} - -{{define "FormatUsage" -}} -{{.AppSummary}} -{{if .Help}} -{{.Help|Wrap 0 -}} -{{end -}} - -{{end -}} - -{{if .Context.SelectedCommand -}} -{{T "usage:"}} {{.App.Name}} {{.App.FlagSummary}} {{.Context.SelectedCommand.CmdSummary}} - -{{index .LongHelp .Context.SelectedCommand.FullCommand}} -{{else}} -{{T "usage:"}} {{template "FormatUsage" .App}} -{{index .LongHelp "root"}} -{{end}} -{{if .Context.Flags -}} -{{T "Flags:"}} -{{.Context.Flags|FlagsToTwoColumns|FormatTwoColumns}} -{{end -}} -{{if .Context.Args -}} -{{T "Args:"}} -{{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}} -{{end -}} -{{if .Context.SelectedCommand -}} -{{if len .Context.SelectedCommand.Commands -}} -{{T "Subcommands:"}} -{{template "FormatCommands" .Context.SelectedCommand}} -{{end -}} -{{else if .App.Commands -}} -{{T "Commands:" -}} -{{template "FormatCommands" .App}} -{{end -}} -` -) From 0b407873d5d3d6b3c4354ece87122e8702d12366 Mon Sep 17 00:00:00 2001 From: Stuart Nelson Date: Wed, 11 Apr 2018 18:23:53 +0200 Subject: [PATCH 3/4] Remove confusing alert struct name overwriting A local variable within the alert subcommand was using the name of the struct within that file. Signed-off-by: Stuart Nelson --- cli/alert.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cli/alert.go b/cli/alert.go index 044be414ba..cd70dbab4d 100644 --- a/cli/alert.go +++ b/cli/alert.go @@ -21,14 +21,14 @@ type alertQueryCmd struct { func configureAlertCmd(app *kingpin.Application, longHelpText map[string]string) { var ( - a = &alertQueryCmd{} - alertCmd = app.Command("alert", "View and search through current alerts") - alertQueryCmd = alertCmd.Command("query", "View and search through current alerts").Default() + a = &alertQueryCmd{} + alertCmd = app.Command("alert", "View and search through current alerts") + queryCmd = alertCmd.Command("query", "View and search through current alerts").Default() ) - alertQueryCmd.Flag("expired", "Show expired alerts as well as active").BoolVar(&a.expired) - alertQueryCmd.Flag("silenced", "Show silenced alerts").Short('s').BoolVar(&a.silenced) - alertQueryCmd.Arg("matcher-groups", "Query filter").StringsVar(&a.matcherGroups) - alertQueryCmd.Action(a.queryAlerts) + queryCmd.Flag("expired", "Show expired alerts as well as active").BoolVar(&a.expired) + queryCmd.Flag("silenced", "Show silenced alerts").Short('s').BoolVar(&a.silenced) + queryCmd.Arg("matcher-groups", "Query filter").StringsVar(&a.matcherGroups) + queryCmd.Action(a.queryAlerts) longHelpText["alert"] = `View and search through current alerts. Amtool has a simplified prometheus query syntax, but contains robust support for From 01e7258a9923676682bfc17067675b1206f3e196 Mon Sep 17 00:00:00 2001 From: Stuart Nelson Date: Fri, 13 Apr 2018 10:15:32 +0200 Subject: [PATCH 4/4] change local var name shadowing struct name Signed-off-by: Stuart Nelson --- cli/check_config.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cli/check_config.go b/cli/check_config.go index cbc1340e9f..9dd755f44c 100644 --- a/cli/check_config.go +++ b/cli/check_config.go @@ -15,12 +15,12 @@ type checkConfigCmd struct { func configureCheckConfigCmd(app *kingpin.Application, longHelpText map[string]string) { var ( - c = &checkConfigCmd{} - checkConfigCmd = app.Command("check-config", "Validate alertmanager config files") + c = &checkConfigCmd{} + checkCmd = app.Command("check-config", "Validate alertmanager config files") ) - checkConfigCmd.Arg("check-files", "Files to be validated").ExistingFilesVar(&c.files) + checkCmd.Arg("check-files", "Files to be validated").ExistingFilesVar(&c.files) - checkConfigCmd.Action(c.checkConfig) + checkCmd.Action(c.checkConfig) longHelpText["check-config"] = `Validate alertmanager config files Will validate the syntax and schema for alertmanager config file