forked from prometheus/alertmanager
/
alert.go
174 lines (143 loc) · 4.97 KB
/
alert.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package cli
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"path"
"strings"
"time"
"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 {
Status string `json:"status"`
Data []*alertGroup `json:"data,omitempty"`
ErrorType string `json:"errorType,omitempty"`
Error string `json:"error,omitempty"`
}
type alertGroup struct {
Labels model.LabelSet `json:"labels"`
GroupKey string `json:"groupKey"`
Blocks []*alertBlock `json:"blocks"`
}
type alertBlock struct {
RouteOpts interface{} `json:"routeOpts"`
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:
amtool alert query alertname=foo node=bar
This query will match all alerts with the alertname=foo and node=bar label
value pairs set.
amtool alert query 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.
amtool alert query 'alertname=~foo.*'
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),
}
var alertQueryCmd = &cobra.Command{
Use: "query",
Short: "View and search through current alerts",
Long: alertCmd.Long,
RunE: queryAlerts,
}
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"))
}
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.RawQuery = "filter=" + url.QueryEscape(filter)
res, err := http.Get(u.String())
if err != nil {
return []*dispatch.APIAlert{}, err
}
defer res.Body.Close()
err = json.NewDecoder(res.Body).Decode(&alertResponse)
if err != nil {
return []*dispatch.APIAlert{}, fmt.Errorf("Unable to decode json response: %s", err)
}
if alertResponse.Status != "success" {
return []*dispatch.APIAlert{}, fmt.Errorf("[%s] %s", alertResponse.ErrorType, alertResponse.Error)
}
return flattenAlertOverview(alertResponse.Data), nil
}
func flattenAlertOverview(overview []*alertGroup) []*dispatch.APIAlert {
alerts := []*dispatch.APIAlert{}
for _, group := range overview {
for _, block := range group.Blocks {
alerts = append(alerts, block.Alerts...)
}
}
return alerts
}
func queryAlerts(cmd *cobra.Command, args []string) error {
expired := viper.GetBool("expired")
showSilenced := viper.GetBool("silenced")
var filterString = ""
if len(args) == 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])
if err != nil {
filterString = fmt.Sprintf("{alertname=%s}", args[0])
} else {
filterString = fmt.Sprintf("{%s}", strings.Join(args, ","))
}
} else if len(args) > 1 {
filterString = fmt.Sprintf("{%s}", strings.Join(args, ","))
}
fetchedAlerts, err := fetchAlerts(filterString)
if err != nil {
return err
}
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 !alert.EndsAt.IsZero() && alert.EndsAt.Before(time.Now()) {
continue
}
}
if !showSilenced {
// If any silence mutes this alert don't show it
if alert.Status.State == types.AlertStateSuppressed && len(alert.Status.SilencedBy) > 0 {
continue
}
}
displayAlerts = append(displayAlerts, alert)
}
formatter, found := format.Formatters[viper.GetString("output")]
if !found {
return errors.New("Unknown output formatter")
}
return formatter.FormatAlerts(displayAlerts)
}