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