Skip to content

Commit eee0c02

Browse files
authored
feat: Support list checks with alerts (#408)
* feat: Add ListCheckWithAlerts method to API client * refactor: Move list checks to separate method This allows having separate methods to list checks and list checks with alerts based on the `include-alerts` flag. * feat: Add support for check list include-alerts flag Supports include-alerts flag for check list command which will retrieve and output the list of checks along with their alerts.
1 parent 37e5650 commit eee0c02

File tree

4 files changed

+139
-0
lines changed

4 files changed

+139
-0
lines changed

cli/check.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"time"
1111

1212
sm "github.com/grafana/synthetic-monitoring-agent/pkg/pb/synthetic_monitoring"
13+
smapi "github.com/grafana/synthetic-monitoring-api-go-client"
1314
"github.com/urfave/cli/v2"
1415
)
1516

@@ -59,6 +60,14 @@ func GetCheckCommands(cc ChecksClient) cli.Commands {
5960
Name: "list",
6061
Usage: "list Synthetic Monitoring checks",
6162
Action: cc.checkList,
63+
Flags: []cli.Flag{
64+
&cli.BoolFlag{
65+
Name: "include-alerts",
66+
Usage: "include alerts in the output",
67+
Required: false,
68+
Value: false,
69+
},
70+
},
6271
},
6372
&cli.Command{
6473
Name: "get",
@@ -298,6 +307,14 @@ func (c ChecksClient) checkList(ctx *cli.Context) error {
298307
}
299308
defer func() { _ = cleanup(ctx.Context) }()
300309

310+
if includeAlerts := ctx.Bool("include-alerts"); includeAlerts {
311+
return c.listAndPrintChecksWithAlerts(ctx, smClient)
312+
}
313+
314+
return c.listAndPrintChecks(ctx, smClient)
315+
}
316+
317+
func (c ChecksClient) listAndPrintChecks(ctx *cli.Context, smClient *smapi.Client) error {
301318
checks, err := smClient.ListChecks(ctx.Context)
302319
if err != nil {
303320
return fmt.Errorf("listing checks: %w", err)
@@ -331,6 +348,46 @@ func (c ChecksClient) checkList(ctx *cli.Context) error {
331348
return nil
332349
}
333350

351+
func (c ChecksClient) listAndPrintChecksWithAlerts(ctx *cli.Context, smClient *smapi.Client) error {
352+
checks, err := smClient.ListChecksWithAlerts(ctx.Context)
353+
if err != nil {
354+
return fmt.Errorf("listing checks: %w", err)
355+
}
356+
357+
jsonWriter := c.JsonWriterBuilder(ctx)
358+
359+
if done, err := jsonWriter(checks, "marshaling checks"); err != nil || done {
360+
return err
361+
}
362+
363+
w := c.TabWriterBuilder(ctx)
364+
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", "id", "type", "job", "target", "enabled", "frequency", "timeout")
365+
for _, check := range checks {
366+
fmt.Fprintf(
367+
w,
368+
"%d\t%s\t%s\t%s\t%t\t%s\t%s\n",
369+
check.Id,
370+
check.Type(),
371+
check.Job,
372+
check.Target,
373+
check.Enabled,
374+
time.Duration(check.Frequency)*time.Millisecond,
375+
time.Duration(check.Timeout)*time.Millisecond,
376+
)
377+
for i, alert := range check.Alerts {
378+
if i == 0 {
379+
fmt.Fprintf(w, "\t%s\t%s\t%s\t%s\t%s\t%s\n", "alert name", "alert threshold", "alert period", "alert runbook", "alert status", "alert error")
380+
}
381+
fmt.Fprintf(w, "\t%s\t%.2f\t%s\t%s\t%s\t%s\n", alert.Name, alert.Threshold, alert.Period, alert.RunbookUrl, alert.Status, alert.Error)
382+
}
383+
}
384+
if err := w.Flush(); err != nil {
385+
return fmt.Errorf("flushing output: %w", err)
386+
}
387+
388+
return nil
389+
}
390+
334391
func (c ChecksClient) checkGet(ctx *cli.Context) error {
335392
smClient, cleanup, err := c.ClientBuilder(ctx)
336393
if err != nil {

model/model.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ type CheckAlertWithStatus struct {
100100
Error string `json:"error,omitempty"`
101101
}
102102

103+
type CheckWithAlerts struct {
104+
synthetic_monitoring.Check
105+
Alerts []CheckAlertWithStatus `json:"alerts"`
106+
}
107+
103108
func (e *ResponseError) Error() string {
104109
switch {
105110
case e == nil:

smapi.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,27 @@ func (h *Client) ListChecks(ctx context.Context) ([]synthetic_monitoring.Check,
505505
return result, nil
506506
}
507507

508+
// ListChecksWithAlerts returns the list of Synthetic Monitoring checks for the
509+
// authenticated tenant including check alerts.
510+
func (h *Client) ListChecksWithAlerts(ctx context.Context) ([]model.CheckWithAlerts, error) {
511+
if err := h.requireAuthToken(); err != nil {
512+
return nil, err
513+
}
514+
515+
resp, err := h.Get(ctx, "/check/list?includeAlerts=true", true, nil)
516+
if err != nil {
517+
return nil, fmt.Errorf("sending check list request: %w", err)
518+
}
519+
520+
var result []model.CheckWithAlerts
521+
522+
if err := ValidateResponse("check list request", resp, &result); err != nil {
523+
return nil, err
524+
}
525+
526+
return result, nil
527+
}
528+
508529
// QueryCheck returns a Synthetic Monitoring check for the
509530
// authenticated tenant that matches the job and target passed in.
510531
// Job and Target must be set to non empty strings.

smapi_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1294,6 +1294,62 @@ func TestListChecks(t *testing.T) {
12941294
require.ElementsMatch(t, checks, actualChecks)
12951295
}
12961296

1297+
func TestListChecksWithAlerts(t *testing.T) {
1298+
orgs := orgs()
1299+
testTenant := orgs.findTenantByOrg(1000)
1300+
testTenantID := testTenant.id
1301+
checksWithAlerts := []model.CheckWithAlerts{
1302+
{
1303+
Check: synthetic_monitoring.Check{
1304+
Id: 42,
1305+
TenantId: testTenantID,
1306+
},
1307+
Alerts: []model.CheckAlertWithStatus{
1308+
{
1309+
CheckAlert: model.CheckAlert{
1310+
Name: "Alert1",
1311+
Threshold: 100,
1312+
Period: "1m",
1313+
RunbookUrl: "https://example.com/runbook/alert1",
1314+
},
1315+
Status: "OK",
1316+
},
1317+
},
1318+
},
1319+
}
1320+
1321+
url, mux, cleanup := newTestServer(t)
1322+
defer cleanup()
1323+
mux.Handle("/api/v1/check/list", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1324+
if err := requireMethod(w, r, http.MethodGet); err != nil {
1325+
return
1326+
}
1327+
1328+
if r.URL.Query().Get("includeAlerts") != "true" {
1329+
w.WriteHeader(http.StatusBadRequest)
1330+
return
1331+
}
1332+
1333+
if _, err := requireAuth(orgs, w, r, testTenantID); err != nil {
1334+
return
1335+
}
1336+
1337+
resp := checksWithAlerts
1338+
1339+
writeResponse(w, http.StatusOK, &resp)
1340+
}))
1341+
1342+
c := NewClient(url, testTenant.token, http.DefaultClient)
1343+
1344+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
1345+
defer cancel()
1346+
1347+
actualChecks, err := c.ListChecksWithAlerts(ctx)
1348+
require.NoError(t, err)
1349+
require.NotNil(t, actualChecks)
1350+
require.ElementsMatch(t, checksWithAlerts, actualChecks)
1351+
}
1352+
12971353
func TestQueryChecks(t *testing.T) {
12981354
orgs := orgs()
12991355
testTenant := orgs.findTenantByOrg(1000)

0 commit comments

Comments
 (0)