Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[amtool] - Add new command to update silence #1123

Merged
merged 1 commit into from
Dec 11, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cli/silence.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ func init() {
silenceCmd.AddCommand(expireCmd)
silenceCmd.AddCommand(queryCmd)
silenceCmd.AddCommand(importCmd)
silenceCmd.AddCommand(updateCmd)
}
147 changes: 147 additions & 0 deletions cli/silence_update.go
Original file line number Diff line number Diff line change
@@ -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 <id> ...",
Aliases: []string{"extend"},
Args: cobra.MinimumNArgs(1),
Short: "Update silences",
Long: `Extend or update existing silence in Alertmanager.`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you update this with a longer description, with a usage example? It would also be good to note that expires-on will override expires.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'dd add that in once we decided on the parameter names :)

Run: CommandWrapper(update),
}

func init() {
updateCmd.Flags().StringP("expires", "e", "", "Duration of silence (100h)")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking we should rename these. Maybe expires should become duration, with d as the short flag?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just following the parameter names in silence add command here. duration does make more sense than expires though, but if we are going to change we need to update add command as well. That will be a breaking change though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point

updateCmd.Flags().String("expire-on", "", "Expire at a certain time (Overwrites expires) RFC3339 format 2006-01-02T15:04:05Z07:00")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In alertmanager, you can also update the starting time when editing a silence.

How about adding that as well, and naming the flags date.start and date.end?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm, thinking more about the name .. not sure i like date. @grobie , naming guru, any recommendation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we just follow the field names in Alertmanager API, startsAt and endsAt?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hrm, those are more json-idiomatic names. for a CLI I think something different would be more appropriate.

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
}