package cli import ( "encoding/json" "errors" "fmt" "net/http" "net/url" "path" "strings" "time" "github.com/prometheus/alertmanager/cli/format" "github.com/prometheus/common/model" "github.com/prometheus/alertmanager/pkg/parse" "github.com/prometheus/alertmanager/types" "github.com/spf13/cobra" flag "github.com/spf13/pflag" "github.com/spf13/viper" ) var queryFlags *flag.FlagSet var queryCmd = &cobra.Command{ Use: "query", Short: "Query silences", Long: `Query Alertmanager silences. Amtool has a simplified prometheus query syntax, but contains robust support for bash variable expansions. The non-option section of arguments constructs a list of "Matcher Groups" that will be used to filter your query. The following examples will attempt to show this behaviour in action: amtool silence query alertname=foo node=bar This query will match all silences with the alertname=foo and node=bar label value pairs set. amtool silence query foo node=bar If alertname is ommited and the first argument does not contain a '=' or a '=~' then it will be assumed to be the value of the alertname pair. amtool silence query 'alertname=~foo.*' As well as direct equality, regex matching is also supported. The '=~' syntax (similar to prometheus) is used to represent a regex match. Regex matching can be used in combination with a direct match. In addition to filtering by silence labels, one can also query for silences that are due to expire soon with the "--within" parameter. In the event that you want to preemptively act upon expiring silences by either fixing them or extending them. For example: amtool silence query --within 8h gives all the silences due to expire within the next 8 hours. This syntax can also be combined with the label based filtering above for more flexibility. `, Run: CommandWrapper(query), } func init() { queryCmd.Flags().Bool("expired", false, "Show expired silences as well as active") queryCmd.Flags().String("within", "", "Show silences that will expire within a duration") queryFlags = queryCmd.Flags() } func fetchSilences(filter string) ([]types.Silence, error) { silenceResponse := alertmanagerSilenceResponse{} u, err := GetAlertmanagerURL() if err != nil { return []types.Silence{}, err } u.Path = path.Join(u.Path, "/api/v1/silences") u.RawQuery = "filter=" + url.QueryEscape(filter) res, err := http.Get(u.String()) if err != nil { return []types.Silence{}, err } defer res.Body.Close() err = json.NewDecoder(res.Body).Decode(&silenceResponse) if err != nil { return []types.Silence{}, err } if silenceResponse.Status != "success" { return []types.Silence{}, fmt.Errorf("[%s] %s", silenceResponse.ErrorType, silenceResponse.Error) } return silenceResponse.Data, nil } func query(cmd *cobra.Command, args []string) error { expired, err := queryFlags.GetBool("expired") if err != nil { return err } within, err := queryFlags.GetString("within") if err != nil { return err } var duration model.Duration if within != "" { duration, err = model.ParseDuration(within) if err != nil { return err } } quiet := viper.GetBool("quiet") var filterString = "" if len(args) == 1 { // If we only have one argument then it's possible that the user wants me to assume alertname= // Attempt to use the parser to pare the argument // If the parser fails then we likely don't have a (=|=~|!=|!~) so lets prepend `alertname=` to the front _, err := parse.Matcher(args[0]) if err != nil { filterString = fmt.Sprintf("{alertname=%s}", args[0]) } else { filterString = fmt.Sprintf("{%s}", strings.Join(args, ",")) } } else if len(args) > 1 { filterString = fmt.Sprintf("{%s}", strings.Join(args, ",")) } fetchedSilences, err := fetchSilences(filterString) if err != nil { return err } displaySilences := []types.Silence{} for _, silence := range fetchedSilences { // If we are only returning current silences and this one has already expired skip it if !expired && silence.EndsAt.Before(time.Now()) { continue } if int64(duration) > 0 && silence.EndsAt.After(time.Now().UTC().Add(time.Duration(duration))) { continue } displaySilences = append(displaySilences, silence) } if quiet { for _, silence := range displaySilences { fmt.Println(silence.ID) } } else { formatter, found := format.Formatters[viper.GetString("output")] if !found { return errors.New("unknown output formatter") } formatter.FormatSilences(displaySilences) } return nil }