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

Switch amtool to kingpin #976

Merged
merged 8 commits into from
Dec 22, 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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
17 changes: 0 additions & 17 deletions artifacts/generate_amtool_artifacts.go

This file was deleted.

95 changes: 37 additions & 58 deletions cli/alert.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,15 @@ import (
"fmt"
"net/http"
"net/url"
"path"
"strings"
"time"

"github.com/alecthomas/kingpin"
Copy link
Contributor

Choose a reason for hiding this comment

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

We should use gopkg.in/alecthomas/kingpin.v2, like in prometheus (and as recommended in the repo)

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually, no :) We need to use the v3-branch, since the pull request I made to kingpin to add support for config files is only present on that branch.

"github.com/prometheus/alertmanager/cli/format"
"github.com/prometheus/alertmanager/dispatch"
"github.com/prometheus/alertmanager/pkg/parse"
"github.com/prometheus/alertmanager/types"
"github.com/prometheus/common/model"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

type alertmanagerAlertResponse struct {
Expand All @@ -37,61 +35,45 @@ type alertBlock struct {
Alerts []*dispatch.APIAlert `json:"alerts"`
}

// alertCmd represents the alert command
var alertCmd = &cobra.Command{
Use: "alert",
Short: "View and search through current alerts",
Long: `View and search through current alerts.

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:
var (
alertCmd = app.Command("alert", "View and search through current alerts")
alertQueryCmd = alertCmd.Command("query", "View and search through current alerts").Default()
expired = alertQueryCmd.Flag("expired", "Show expired alerts as well as active").Bool()
showSilenced = alertQueryCmd.Flag("silenced", "Show silenced alerts").Short('s').Bool()
alertQuery = alertQueryCmd.Arg("matcher-groups", "Query filter").Strings()
)

amtool alert query alertname=foo node=bar
func init() {
alertQueryCmd.Action(queryAlerts)
longHelpText["alert"] = `View and search through current alerts.

This query will match all alerts with the alertname=foo and node=bar label
value pairs set.
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 alert query foo node=bar
amtool alert query alertname=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.
This query will match all alerts with the alertname=foo and node=bar label
value pairs set.

amtool alert query 'alertname=~foo.*'
amtool alert query foo node=bar

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.
`,
Run: CommandWrapper(queryAlerts),
}
If alertname is omitted and the first argument does not contain a '=' or a
'=~' then it will be assumed to be the value of the alertname pair.

var alertQueryCmd = &cobra.Command{
Use: "query",
Short: "View and search through current alerts",
Long: alertCmd.Long,
RunE: queryAlerts,
}
amtool alert query 'alertname=~foo.*'

func init() {
RootCmd.AddCommand(alertCmd)
alertCmd.AddCommand(alertQueryCmd)
alertQueryCmd.Flags().Bool("expired", false, "Show expired alerts as well as active")
alertQueryCmd.Flags().BoolP("silenced", "s", false, "Show silenced alerts")
viper.BindPFlag("expired", alertQueryCmd.Flags().Lookup("expired"))
viper.BindPFlag("silenced", alertQueryCmd.Flags().Lookup("silenced"))
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.`
longHelpText["alert query"] = longHelpText["alert"]
}

func fetchAlerts(filter string) ([]*dispatch.APIAlert, error) {
alertResponse := alertmanagerAlertResponse{}

u, err := GetAlertmanagerURL()
if err != nil {
return []*dispatch.APIAlert{}, err
}

u.Path = path.Join(u.Path, "/api/v1/alerts/groups")
u := GetAlertmanagerURL("/api/v1/alerts/groups")
u.RawQuery = "filter=" + url.QueryEscape(filter)

res, err := http.Get(u.String())
Expand Down Expand Up @@ -123,23 +105,20 @@ func flattenAlertOverview(overview []*alertGroup) []*dispatch.APIAlert {
return alerts
}

func queryAlerts(cmd *cobra.Command, args []string) error {
expired := viper.GetBool("expired")
showSilenced := viper.GetBool("silenced")

func queryAlerts(element *kingpin.ParseElement, ctx *kingpin.ParseContext) error {
var filterString = ""
if len(args) == 1 {
if len(*alertQuery) == 1 {
// If we only have one argument then it's possible that the user wants me to assume alertname=<arg>
// 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])
_, err := parse.Matcher((*alertQuery)[0])
if err != nil {
filterString = fmt.Sprintf("{alertname=%s}", args[0])
filterString = fmt.Sprintf("{alertname=%s}", (*alertQuery)[0])
} else {
filterString = fmt.Sprintf("{%s}", strings.Join(args, ","))
filterString = fmt.Sprintf("{%s}", strings.Join(*alertQuery, ","))
}
} else if len(args) > 1 {
filterString = fmt.Sprintf("{%s}", strings.Join(args, ","))
} else if len(*alertQuery) > 1 {
filterString = fmt.Sprintf("{%s}", strings.Join(*alertQuery, ","))
}

fetchedAlerts, err := fetchAlerts(filterString)
Expand All @@ -150,13 +129,13 @@ func queryAlerts(cmd *cobra.Command, args []string) error {
displayAlerts := []*dispatch.APIAlert{}
for _, alert := range fetchedAlerts {
// If we are only returning current alerts and this one has already expired skip it
if !expired {
if !*expired {
if !alert.EndsAt.IsZero() && alert.EndsAt.Before(time.Now()) {
continue
}
}

if !showSilenced {
if !*showSilenced {
// If any silence mutes this alert don't show it
if alert.Status.State == types.AlertStateSuppressed && len(alert.Status.SilencedBy) > 0 {
continue
Expand All @@ -166,7 +145,7 @@ func queryAlerts(cmd *cobra.Command, args []string) error {
displayAlerts = append(displayAlerts, alert)
}

formatter, found := format.Formatters[viper.GetString("output")]
formatter, found := format.Formatters[*output]
if !found {
return errors.New("unknown output formatter")
}
Expand Down
28 changes: 12 additions & 16 deletions cli/check_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,28 @@ package cli
import (
"fmt"

"github.com/alecthomas/kingpin"
"github.com/prometheus/alertmanager/config"
"github.com/prometheus/alertmanager/template"
"github.com/spf13/cobra"
)

// alertCmd represents the alert command
var checkConfigCmd = &cobra.Command{
Use: "check-config file1 [file2] ...",
Args: cobra.MinimumNArgs(1),
Short: "Validate alertmanager config files",
Long: `Validate alertmanager config files
var (
checkConfigCmd = app.Command("check-config", "Validate alertmanager config files")
checkFiles = checkConfigCmd.Arg("check-files", "Files to be validated").ExistingFiles()
)

func init() {
checkConfigCmd.Action(checkConfig)
longHelpText["check-config"] = `Validate alertmanager config files

Will validate the syntax and schema for alertmanager config file
and associated templates. Non existing templates will not trigger
errors`,
RunE: checkConfig,
}

func init() {
RootCmd.AddCommand(checkConfigCmd)
checkConfigCmd.Flags()
errors`
}

func checkConfig(cmd *cobra.Command, args []string) error {
cmd.SilenceUsage = true
return CheckConfig(args)
func checkConfig(element *kingpin.ParseElement, ctx *kingpin.ParseContext) error {
return CheckConfig(*checkFiles)
}

func CheckConfig(args []string) error {
Expand Down
30 changes: 9 additions & 21 deletions cli/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@ import (
"errors"
"fmt"
"net/http"
"path"
"time"

"github.com/alecthomas/kingpin"
Copy link
Contributor

Choose a reason for hiding this comment

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

same import issue as above

"github.com/prometheus/alertmanager/cli/format"
"github.com/prometheus/alertmanager/config"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

// Config is the response type of alertmanager config endpoint
Expand Down Expand Up @@ -43,31 +41,21 @@ type alertmanagerStatusResponse struct {
Error string `json:"error,omitempty"`
}

// alertCmd represents the alert command
var configCmd = &cobra.Command{
Use: "config",
Short: "View the running config",
Long: `View current config
// configCmd represents the config command
var configCmd = app.Command("config", "View the running config").Action(queryConfig)

func init() {
longHelpText["config"] = `View current config
The amount of output is controlled by the output selection flag:
- Simple: Print just the running config
- Extended: Print the running config as well as uptime and all version info
- Json: Print entire config object as json`,
RunE: queryConfig,
}

func init() {
RootCmd.AddCommand(configCmd)
- Json: Print entire config object as json`
}

func fetchConfig() (Config, error) {
configResponse := alertmanagerStatusResponse{}
u, err := GetAlertmanagerURL()
if err != nil {
return Config{}, err
}

u.Path = path.Join(u.Path, "/api/v1/status")
u := GetAlertmanagerURL("/api/v1/status")
res, err := http.Get(u.String())
if err != nil {
return Config{}, err
Expand All @@ -87,13 +75,13 @@ func fetchConfig() (Config, error) {
return configResponse.Data, nil
}

func queryConfig(cmd *cobra.Command, args []string) error {
func queryConfig(element *kingpin.ParseElement, ctx *kingpin.ParseContext) error {
config, err := fetchConfig()
if err != nil {
return err
}

formatter, found := format.Formatters[viper.GetString("output")]
formatter, found := format.Formatters[*output]
if !found {
return errors.New("unknown output formatter")
}
Expand Down
13 changes: 10 additions & 3 deletions cli/format/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,22 @@ import (
"io"
"time"

"github.com/alecthomas/kingpin"
Copy link
Contributor

Choose a reason for hiding this comment

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

import

"github.com/prometheus/alertmanager/config"
"github.com/prometheus/alertmanager/dispatch"
"github.com/prometheus/alertmanager/types"
"github.com/spf13/viper"
)

const DefaultDateFormat = "2006-01-02 15:04:05 MST"

var (
dateFormat *string
)

func InitFormatFlags(app *kingpin.Application) {
dateFormat = app.Flag("date.format", "Format of date output").Default(DefaultDateFormat).String()
}

// Config representation
// Need to get this moved to the prometheus/common/model repo having is duplicated here is smelly
type Config struct {
Expand Down Expand Up @@ -46,6 +54,5 @@ type Formatter interface {
var Formatters = map[string]Formatter{}

func FormatDate(input time.Time) string {
dateformat := viper.GetString("date.format")
return input.Format(dateformat)
return input.Format(*dateFormat)
}
Loading