Skip to content
This repository was archived by the owner on Jan 15, 2024. It is now read-only.

Commit c6904d3

Browse files
authored
Merge branch 'master' into alexweav/mute-timings
2 parents 7ed0cb9 + e527c99 commit c6904d3

File tree

4 files changed

+379
-0
lines changed

4 files changed

+379
-0
lines changed

alerting_alert_rule.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package gapi
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"time"
8+
)
9+
10+
// AlertRule represents a Grafana Alert Rule.
11+
type AlertRule struct {
12+
Annotations map[string]string `json:"annotations,omitempty"`
13+
Condition string `json:"condition"`
14+
Data []*AlertQuery `json:"data"`
15+
ExecErrState ExecErrState `json:"execErrState"`
16+
FolderUID string `json:"folderUid"`
17+
ID int64 `json:"id,omitempty"`
18+
Labels map[string]string `json:"labels,omitempty"`
19+
NoDataState NoDataState `json:"noDataState"`
20+
OrgID int64 `json:"orgId"`
21+
RuleGroup string `json:"ruleGroup"`
22+
Title string `json:"title"`
23+
UID string `json:"uid,omitempty"`
24+
Updated time.Time `json:"updated"`
25+
ForDuration time.Duration `json:"for"`
26+
Provenance string `json:"provenance"`
27+
}
28+
29+
// AlertQuery represents a single query stage associated with an alert definition.
30+
type AlertQuery struct {
31+
DatasourceUID string `json:"datasourceUid,omitempty"`
32+
Model interface{} `json:"model"`
33+
QueryType string `json:"queryType,omitempty"`
34+
RefID string `json:"refId,omitempty"`
35+
RelativeTimeRange RelativeTimeRange `json:"relativeTimeRange"`
36+
}
37+
38+
type ExecErrState string
39+
type NoDataState string
40+
41+
const (
42+
ErrOK ExecErrState = "OK"
43+
ErrError ExecErrState = "Error"
44+
ErrAlerting ExecErrState = "Alerting"
45+
NoDataOk NoDataState = "OK"
46+
NoData NoDataState = "NoData"
47+
NoDataAlerting NoDataState = "Alerting"
48+
)
49+
50+
// RelativeTimeRange represents the time range for an alert query.
51+
type RelativeTimeRange struct {
52+
From time.Duration `json:"from"`
53+
To time.Duration `json:"to"`
54+
}
55+
56+
// AlertRule fetches a single alert rule, identified by its UID.
57+
func (c *Client) AlertRule(uid string) (AlertRule, error) {
58+
path := fmt.Sprintf("/api/v1/provisioning/alert-rules/%s", uid)
59+
result := AlertRule{}
60+
err := c.request("GET", path, nil, nil, &result)
61+
if err != nil {
62+
return AlertRule{}, err
63+
}
64+
return result, err
65+
}
66+
67+
// NewAlertRule creates a new alert rule and returns its UID.
68+
func (c *Client) NewAlertRule(ar *AlertRule) (string, error) {
69+
req, err := json.Marshal(ar)
70+
if err != nil {
71+
return "", err
72+
}
73+
result := AlertRule{}
74+
err = c.request("POST", "/api/v1/provisioning/alert-rules", nil, bytes.NewBuffer(req), &result)
75+
if err != nil {
76+
return "", err
77+
}
78+
return result.UID, nil
79+
}
80+
81+
// UpdateAlertRule replaces an alert rule, identified by the alert rule's UID.
82+
func (c *Client) UpdateAlertRule(ar *AlertRule) error {
83+
uri := fmt.Sprintf("/api/v1/provisioning/alert-rules/%s", ar.UID)
84+
req, err := json.Marshal(ar)
85+
if err != nil {
86+
return err
87+
}
88+
89+
return c.request("PUT", uri, nil, bytes.NewBuffer(req), nil)
90+
}
91+
92+
// DeleteAlertRule deletes a alert rule, identified by the alert rule's UID.
93+
func (c *Client) DeleteAlertRule(uid string) error {
94+
uri := fmt.Sprintf("/api/v1/provisioning/alert-rules/%s", uid)
95+
return c.request("DELETE", uri, nil, nil, nil)
96+
}

alerting_alert_rule_test.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package gapi
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
7+
"github.com/gobs/pretty"
8+
)
9+
10+
func TestAlertRules(t *testing.T) {
11+
t.Run("get alert rule succeeds", func(t *testing.T) {
12+
server, client := gapiTestTools(t, 200, getAlertRuleJSON)
13+
defer server.Close()
14+
15+
alertRule, err := client.AlertRule("123abcd")
16+
17+
if err != nil {
18+
t.Error(err)
19+
}
20+
if alertRule.UID != "123abcd" {
21+
t.Errorf("incorrect UID - expected %s got %#v", "123abcd", alertRule.UID)
22+
}
23+
})
24+
25+
t.Run("get non-existent alert rule fails", func(t *testing.T) {
26+
server, client := gapiTestTools(t, 404, "")
27+
defer server.Close()
28+
29+
alertRule, err := client.AlertRule("does not exist")
30+
31+
if err == nil {
32+
t.Errorf("expected error but got nil")
33+
t.Log(pretty.PrettyFormat(alertRule))
34+
}
35+
})
36+
37+
t.Run("create alert rule succeeds", func(t *testing.T) {
38+
server, client := gapiTestTools(t, 201, writeAlertRuleJSON)
39+
defer server.Close()
40+
alertRule := createAlertRule()
41+
uid, err := client.NewAlertRule(&alertRule)
42+
43+
if err != nil {
44+
t.Error(err)
45+
}
46+
if uid != "123abcd" {
47+
t.Errorf("unexpected UID returned, got %s", uid)
48+
}
49+
})
50+
51+
t.Run("update alert rule succeeds", func(t *testing.T) {
52+
server, client := gapiTestTools(t, 200, writeAlertRuleJSON)
53+
defer server.Close()
54+
alertRule := createAlertRule()
55+
alertRule.UID = "foobar"
56+
57+
err := client.UpdateAlertRule(&alertRule)
58+
59+
if err != nil {
60+
t.Error(err)
61+
}
62+
})
63+
64+
t.Run("delete alert rule succeeds", func(t *testing.T) {
65+
server, client := gapiTestTools(t, 204, "")
66+
defer server.Close()
67+
68+
err := client.DeleteAlertRule("123abcd")
69+
70+
if err != nil {
71+
t.Error(err)
72+
}
73+
})
74+
}
75+
76+
func createAlertRule() AlertRule {
77+
return AlertRule{
78+
Condition: "A",
79+
Data: createAlertQueries(),
80+
ExecErrState: ErrOK,
81+
FolderUID: "project_test",
82+
NoDataState: NoDataOk,
83+
OrgID: 1,
84+
RuleGroup: "eval_group_1",
85+
Title: "Always in alarm",
86+
ForDuration: 0,
87+
}
88+
}
89+
90+
func createAlertQueries() []*AlertQuery {
91+
alertQueries := make([]*AlertQuery, 1)
92+
alertQueries[0] = &AlertQuery{
93+
DatasourceUID: "-100",
94+
Model: json.RawMessage(`{"datasourceUid":"-100","model":{"conditions":[{"evaluator":{"params":[0,0],"type":"gt"},"operator":{"type":"and"},"query":{"params":[]},"reducer":{"params":[],"type":"avg"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1 == 1","hide":false,"intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"},"queryType":"","refId":"A","relativeTimeRange":{"from":0,"to":0}}`),
95+
QueryType: "",
96+
RefID: "A",
97+
RelativeTimeRange: RelativeTimeRange{From: 0, To: 0},
98+
}
99+
return alertQueries
100+
}
101+
102+
const writeAlertRuleJSON = `
103+
{
104+
"conditions": "A",
105+
"data": [{"datasourceUid":"-100","model":{"conditions":[{"evaluator":{"params":[0,0],"type":"gt"},"operator":{"type":"and"},"query":{"params":[]},"reducer":{"params":[],"type":"avg"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1 == 1","hide":false,"intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"},"queryType":"","refId":"A","relativeTimeRange":{"from":0,"to":0}}],
106+
"uid": "123abcd",
107+
"execErrState": "OK",
108+
"folderUID": "project_test",
109+
"noDataState": "OK",
110+
"orgId": 1,
111+
"ruleGroup": "eval_group_1",
112+
"title": "Always in alarm",
113+
"for": 0
114+
}
115+
`
116+
117+
const getAlertRuleJSON = `
118+
{
119+
"conditions": "A",
120+
"data": [{"datasourceUid":"-100","model":{"conditions":[{"evaluator":{"params":[0,0],"type":"gt"},"operator":{"type":"and"},"query":{"params":[]},"reducer":{"params":[],"type":"avg"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1 == 1","hide":false,"intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"},"queryType":"","refId":"A","relativeTimeRange":{"from":0,"to":0}}],
121+
"execErrState": "OK",
122+
"folderUID": "project_test",
123+
"noDataState": "OK",
124+
"orgId": 1,
125+
"uid": "123abcd",
126+
"ruleGroup": "eval_group_1",
127+
"title": "Always in alarm",
128+
"for": 0
129+
}
130+
`

alerting_message_template.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package gapi
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
)
8+
9+
// AlertingMessageTemplate is a re-usable template for Grafana Alerting messages.
10+
type AlertingMessageTemplate struct {
11+
Name string `json:"name"`
12+
Template string `json:"template"`
13+
}
14+
15+
// MessageTemplates fetches all message templates.
16+
func (c *Client) MessageTemplates() ([]AlertingMessageTemplate, error) {
17+
ts := make([]AlertingMessageTemplate, 0)
18+
err := c.request("GET", "/api/v1/provisioning/templates", nil, nil, &ts)
19+
if err != nil {
20+
return nil, err
21+
}
22+
return ts, nil
23+
}
24+
25+
// MessageTemplate fetches a single message template, identified by its name.
26+
func (c *Client) MessageTemplate(name string) (*AlertingMessageTemplate, error) {
27+
t := AlertingMessageTemplate{}
28+
uri := fmt.Sprintf("/api/v1/provisioning/templates/%s", name)
29+
err := c.request("GET", uri, nil, nil, &t)
30+
if err != nil {
31+
return nil, err
32+
}
33+
return &t, err
34+
}
35+
36+
// SetMessageTemplate creates or updates a message template.
37+
func (c *Client) SetMessageTemplate(name, content string) error {
38+
req := struct {
39+
Template string `json:"template"`
40+
}{Template: content}
41+
body, err := json.Marshal(req)
42+
if err != nil {
43+
return err
44+
}
45+
46+
uri := fmt.Sprintf("/api/v1/provisioning/templates/%s", name)
47+
return c.request("PUT", uri, nil, bytes.NewBuffer(body), nil)
48+
}
49+
50+
// DeleteMessageTemplate deletes a message template.
51+
func (c *Client) DeleteMessageTemplate(name string) error {
52+
uri := fmt.Sprintf("/api/v1/provisioning/templates/%s", name)
53+
return c.request("DELETE", uri, nil, nil, nil)
54+
}

alerting_message_template_test.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package gapi
2+
3+
import (
4+
"testing"
5+
6+
"github.com/gobs/pretty"
7+
)
8+
9+
func TestMessageTemplates(t *testing.T) {
10+
t.Run("get message templates succeeds", func(t *testing.T) {
11+
server, client := gapiTestTools(t, 200, getMessageTemplatesJSON)
12+
defer server.Close()
13+
14+
ts, err := client.MessageTemplates()
15+
16+
if err != nil {
17+
t.Error(err)
18+
}
19+
t.Log(pretty.PrettyFormat(ts))
20+
if len(ts) != 2 {
21+
t.Errorf("wrong number of templates returned, got %#v", ts)
22+
}
23+
if ts[0].Name != "template-one" {
24+
t.Errorf("incorrect name - expected %s on element %d, got %#v", "template-one", 0, ts)
25+
}
26+
if ts[1].Name != "template-two" {
27+
t.Errorf("incorrect name - expected %s on element %d, got %#v", "template-two", 0, ts)
28+
}
29+
})
30+
31+
t.Run("get message template succeeds", func(t *testing.T) {
32+
server, client := gapiTestTools(t, 200, messageTemplateJSON)
33+
defer server.Close()
34+
35+
tmpl, err := client.MessageTemplate("template-one")
36+
37+
if err != nil {
38+
t.Error(err)
39+
}
40+
t.Log(pretty.PrettyFormat(tmpl))
41+
if tmpl.Name != "template-one" {
42+
t.Errorf("incorrect name - expected %s, got %#v", "template-one", tmpl)
43+
}
44+
})
45+
46+
t.Run("get non-existent message template fails", func(t *testing.T) {
47+
server, client := gapiTestTools(t, 404, ``)
48+
defer server.Close()
49+
50+
tmpl, err := client.MessageTemplate("does not exist")
51+
52+
if err == nil {
53+
t.Errorf("expected error but got nil")
54+
t.Log(pretty.PrettyFormat(tmpl))
55+
}
56+
})
57+
58+
t.Run("put message template succeeds", func(t *testing.T) {
59+
server, client := gapiTestTools(t, 202, messageTemplateJSON)
60+
defer server.Close()
61+
62+
err := client.SetMessageTemplate("template-three", "{{define \"template-one\" }}\n content three\n{{ end }}")
63+
64+
if err != nil {
65+
t.Error(err)
66+
}
67+
})
68+
69+
t.Run("delete message template succeeds", func(t *testing.T) {
70+
server, client := gapiTestTools(t, 204, ``)
71+
defer server.Close()
72+
73+
err := client.DeleteMessageTemplate("template-three")
74+
75+
if err != nil {
76+
t.Error(err)
77+
}
78+
})
79+
}
80+
81+
const getMessageTemplatesJSON = `
82+
[
83+
{
84+
"name": "template-one",
85+
"template": "{{define \"template-one\" }}\n content one\n{{ end }}"
86+
},
87+
{
88+
"name": "template-two",
89+
"template": "{{define \"template-one\" }}\n content two\n{{ end }}"
90+
}
91+
]
92+
`
93+
94+
const messageTemplateJSON = `
95+
{
96+
"name": "template-one",
97+
"template": "{{define \"template-one\" }}\n content one\n{{ end }}"
98+
}
99+
`

0 commit comments

Comments
 (0)