package cli import ( "encoding/json" "errors" "fmt" "io/ioutil" "net/http" "net/url" "path" "time" "github.com/alecthomas/kingpin" "github.com/prometheus/alertmanager/cli/format" "github.com/prometheus/alertmanager/types" "github.com/prometheus/common/model" ) type getResponse struct { Status string `json:"status"` Data types.Silence `json:"data,omitempty"` ErrorType string `json:"errorType,omitempty"` Error string `json:"error,omitempty"` } 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() ) func init() { updateCmd.Action(update) longHelpText["silence update"] = `Extend or update existing silence in Alertmanager.` } func update(element *kingpin.ParseElement, ctx *kingpin.ParseContext) error { if len(*updateIds) < 1 { return fmt.Errorf("no silence IDs specified") } alertmanagerUrl := GetAlertmanagerURL("/api/v1/silence") var updatedSilences []types.Silence for _, silenceId := range *updateIds { silence, err := getSilenceById(silenceId, alertmanagerUrl) if err != nil { return err } silence, err = updateSilence(silence) if err != nil { return err } updatedSilences = append(updatedSilences, *silence) } if *silenceQuiet { for _, silence := range updatedSilences { fmt.Println(silence.ID) } } else { formatter, found := format.Formatters[*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, 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) if err != nil { return nil, fmt.Errorf("couldn't decode response body: %v", err) } return &response.Data, nil } func updateSilence(silence *types.Silence) (*types.Silence, error) { var err error if *updateStart != "" { silence.StartsAt, err = time.Parse(time.RFC3339, *updateStart) if err != nil { return nil, err } } if *updateEnd != "" { silence.EndsAt, err = time.Parse(time.RFC3339, *updateEnd) if err != nil { return nil, err } } else if *updateDuration != "" { d, err := model.ParseDuration(*updateDuration) if err != nil { return nil, err } if d == 0 { return nil, fmt.Errorf("silence duration must be greater than 0") } silence.EndsAt = silence.StartsAt.UTC().Add(time.Duration(d)) } if silence.StartsAt.After(silence.EndsAt) { return nil, errors.New("silence cannot start after it ends") } if *updateComment != "" { silence.Comment = *updateComment } // addSilence can also be used to update an existing silence newID, err := addSilence(silence) if err != nil { return nil, err } silence.ID = newID return silence, nil }