diff --git a/cli/silence.go b/cli/silence.go index 494126f3..c0b16e45 100644 --- a/cli/silence.go +++ b/cli/silence.go @@ -32,4 +32,5 @@ func init() { silenceCmd.AddCommand(expireCmd) silenceCmd.AddCommand(queryCmd) silenceCmd.AddCommand(importCmd) + silenceCmd.AddCommand(updateCmd) } diff --git a/cli/silence_update.go b/cli/silence_update.go new file mode 100644 index 00000000..46c765a6 --- /dev/null +++ b/cli/silence_update.go @@ -0,0 +1,147 @@ +package cli + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "path" + "time" + + "github.com/prometheus/alertmanager/cli/format" + "github.com/prometheus/alertmanager/types" + "github.com/spf13/cobra" + flag "github.com/spf13/pflag" + "github.com/spf13/viper" +) + +type getResponse struct { + Status string `json:"status"` + Data types.Silence `json:"data,omitempty"` + ErrorType string `json:"errorType,omitempty"` + Error string `json:"error,omitempty"` +} + +var updateFlags *flag.FlagSet +var updateCmd = &cobra.Command{ + Use: "update ...", + Aliases: []string{"extend"}, + Args: cobra.MinimumNArgs(1), + Short: "Update silences", + Long: `Extend or update existing silence in Alertmanager.`, + Run: CommandWrapper(update), +} + +func init() { + updateCmd.Flags().StringP("expires", "e", "", "Duration of silence (100h)") + updateCmd.Flags().String("expire-on", "", "Expire at a certain time (Overwrites expires) RFC3339 format 2006-01-02T15:04:05Z07:00") + updateCmd.Flags().StringP("comment", "c", "", "A comment to help describe the silence") + updateFlags = updateCmd.Flags() +} + +func update(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return fmt.Errorf("no silence IDs specified") + } + + alertmanagerUrl, err := GetAlertmanagerURL() + if err != nil { + return err + } + + var updatedSilences []types.Silence + for _, silenceId := range args { + silence, err := getSilenceById(silenceId, *alertmanagerUrl) + if err != nil { + return err + } + silence, err = updateSilence(silence) + if err != nil { + return err + } + updatedSilences = append(updatedSilences, *silence) + } + + quiet := viper.GetBool("quiet") + if quiet { + for _, silence := range updatedSilences { + fmt.Println(silence.ID) + } + } else { + formatter, found := format.Formatters[viper.GetString("output")] + if !found { + return fmt.Errorf("unknown output formatter") + } + formatter.FormatSilences(updatedSilences) + } + return nil +} + +// This takes an url.URL and not a pointer as we will modify it for our API call. +func getSilenceById(silenceId string, baseUrl url.URL) (*types.Silence, error) { + baseUrl.Path = path.Join(baseUrl.Path, "/api/v1/silence", silenceId) + res, err := http.Get(baseUrl.String()) + if err != nil { + return nil, err + } + + defer res.Body.Close() + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, fmt.Errorf("couldn't read response body: %v", err) + } + + if res.StatusCode == 404 { + return nil, fmt.Errorf("no silence found with id: %v", silenceId) + } + if res.StatusCode != 200 { + return nil, fmt.Errorf("received %d response from Alertmanager: %v", res.StatusCode, body) + } + + var response getResponse + err = json.Unmarshal(body, &response) + return &response.Data, nil +} + +func updateSilence(silence *types.Silence) (*types.Silence, error) { + if updateFlags.Changed("expires") { + expires, err := updateFlags.GetString("expires") + if err != nil { + return nil, err + } + duration, err := time.ParseDuration(expires) + if err != nil { + return nil, err + } + silence.EndsAt = time.Now().UTC().Add(duration) + } + + // expire-on will override expires value if both are specified + if updateFlags.Changed("expire-on") { + expireOn, err := updateFlags.GetString("expire-on") + if err != nil { + return nil, err + } + endsAt, err := time.Parse(time.RFC3339, expireOn) + if err != nil { + return nil, err + } + silence.EndsAt = endsAt + } + + if updateFlags.Changed("comment") { + comment, err := updateFlags.GetString("comment") + if err != nil { + return nil, err + } + silence.Comment = comment + } + + // addSilence can also be used to update an existing silence + _, err := addSilence(silence) + if err != nil { + return nil, err + } + return silence, nil +}