Skip to content

Commit 9813367

Browse files
authored
feat: add support for alerts v2 (#218)
1 parent 64589e3 commit 9813367

18 files changed

+2366
-129
lines changed

CODEOWNERS

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
*benchmark* @haresh-suresh @nkraemer-sysdig
66

77
# monitor
8-
*monitor*alert* @arturodilecce
8+
*monitor*alert* @arturodilecce @dbonf
99
*monitor*dashboard* @brokenpip3
1010

1111
# policies/rules
12-
*secure*policy* @jacklongsd @kmvachhani @rbaderts
12+
*secure*policy* @jacklongsd @kmvachhani @rbaderts

sysdig/internal/client/monitor/alerts_v2.go

Lines changed: 311 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ package monitor
22

33
import (
44
"context"
5+
"encoding/json"
56
"fmt"
67
"io"
8+
"log"
79
"net/http"
10+
"sync"
811
)
912

1013
func (c *sysdigMonitorClient) alertsV2URL() string {
@@ -15,21 +18,71 @@ func (c *sysdigMonitorClient) alertV2URL(alertID int) string {
1518
return fmt.Sprintf("%s/api/v2/alerts/%d", c.URL, alertID)
1619
}
1720

21+
func (c *sysdigMonitorClient) labelsDescriptorsV3URL(label string) string {
22+
return fmt.Sprintf("%s/api/v3/labels/descriptors/%s", c.URL, label)
23+
}
24+
25+
func (c *sysdigMonitorClient) labelsV3URL() string {
26+
return fmt.Sprintf("%s/api/v3/labels/?limit=6000", c.URL) //6000 is the maximum number of labels a customer can have
27+
}
28+
29+
func (c *sysdigMonitorClient) addNotificationChannelType(ctx context.Context, notificationChannelConfigList []NotificationChannelConfigV2) error {
30+
// on put/posts the api wants the type of the channel even if it can be inferred
31+
for i, n := range notificationChannelConfigList {
32+
nc, err := c.GetNotificationChannelById(ctx, n.ChannelID)
33+
if err != nil {
34+
return fmt.Errorf("error getting info for notification channel %d: %w", n.ChannelID, err)
35+
}
36+
notificationChannelConfigList[i].Type = nc.Type
37+
}
38+
return nil
39+
}
40+
41+
func (c *sysdigMonitorClient) translateScopeSegmentLabels(ctx context.Context, scopedSegmentedConfig *ScopedSegmentedConfig) error {
42+
// the operand of the scope must be in dot notation
43+
if scopedSegmentedConfig.Scope != nil {
44+
for i, e := range scopedSegmentedConfig.Scope.Expressions {
45+
labelDescriptorV3, err := c.GetLabelDescriptor(ctx, e.Operand)
46+
if err != nil {
47+
return fmt.Errorf("error getting descriptor for label %s: %w", e.Operand, err)
48+
}
49+
scopedSegmentedConfig.Scope.Expressions[i].Operand = labelDescriptorV3.ID
50+
}
51+
}
52+
53+
// the label descriptor id must be in dot notation
54+
for i, d := range scopedSegmentedConfig.SegmentBy {
55+
labelDescriptorV3, err := c.GetLabelDescriptor(ctx, d.ID)
56+
if err != nil {
57+
return fmt.Errorf("error getting descriptor for label %s: %w", d.ID, err)
58+
}
59+
scopedSegmentedConfig.SegmentBy[i].ID = labelDescriptorV3.ID
60+
}
61+
62+
return nil
63+
}
64+
65+
// prometheus
66+
1867
func (c *sysdigMonitorClient) CreateAlertV2Prometheus(ctx context.Context, alert AlertV2Prometheus) (createdAlert AlertV2Prometheus, err error) {
68+
if err = c.addNotificationChannelType(ctx, alert.NotificationChannelConfigList); err != nil {
69+
return
70+
}
71+
1972
body, err := c.createAlertV2(ctx, alert.ToJSON())
2073
if err != nil {
2174
return
2275
}
23-
createdAlert = AlertV2PrometheusFromJSON(body)
2476

25-
// this fixes the APIs bug of not setting the default group on the response of the create method
26-
if createdAlert.Group == "" {
27-
createdAlert.Group = "default"
28-
}
77+
createdAlert = AlertV2PrometheusFromJSON(body)
2978
return
3079
}
3180

3281
func (c *sysdigMonitorClient) UpdateAlertV2Prometheus(ctx context.Context, alert AlertV2Prometheus) (updatedAlert AlertV2Prometheus, err error) {
82+
if err = c.addNotificationChannelType(ctx, alert.NotificationChannelConfigList); err != nil {
83+
return
84+
}
85+
3386
body, err := c.updateAlertV2(ctx, alert.ID, alert.ToJSON())
3487
if err != nil {
3588
return
@@ -53,10 +106,166 @@ func (c *sysdigMonitorClient) DeleteAlertV2Prometheus(ctx context.Context, alert
53106
return c.deleteAlertV2(ctx, alertID)
54107
}
55108

109+
// event
110+
111+
func (c *sysdigMonitorClient) CreateAlertV2Event(ctx context.Context, alert AlertV2Event) (createdAlert AlertV2Event, err error) {
112+
if err = c.addNotificationChannelType(ctx, alert.NotificationChannelConfigList); err != nil {
113+
return
114+
}
115+
116+
if err = c.translateScopeSegmentLabels(ctx, &alert.Config.ScopedSegmentedConfig); err != nil {
117+
return
118+
}
119+
120+
body, err := c.createAlertV2(ctx, alert.ToJSON())
121+
if err != nil {
122+
return
123+
}
124+
125+
createdAlert = AlertV2EventFromJSON(body)
126+
return
127+
}
128+
129+
func (c *sysdigMonitorClient) UpdateAlertV2Event(ctx context.Context, alert AlertV2Event) (updatedAlert AlertV2Event, err error) {
130+
if err = c.addNotificationChannelType(ctx, alert.NotificationChannelConfigList); err != nil {
131+
return
132+
}
133+
134+
if err = c.translateScopeSegmentLabels(ctx, &alert.Config.ScopedSegmentedConfig); err != nil {
135+
return
136+
}
137+
138+
body, err := c.updateAlertV2(ctx, alert.ID, alert.ToJSON())
139+
if err != nil {
140+
return
141+
}
142+
143+
updatedAlert = AlertV2EventFromJSON(body)
144+
return
145+
}
146+
147+
func (c *sysdigMonitorClient) GetAlertV2EventById(ctx context.Context, alertID int) (alert AlertV2Event, err error) {
148+
body, err := c.getAlertV2ById(ctx, alertID)
149+
if err != nil {
150+
return
151+
}
152+
153+
alert = AlertV2EventFromJSON(body)
154+
return
155+
}
156+
157+
func (c *sysdigMonitorClient) DeleteAlertV2Event(ctx context.Context, alertID int) (err error) {
158+
return c.deleteAlertV2(ctx, alertID)
159+
}
160+
161+
// metric
162+
163+
func (c *sysdigMonitorClient) CreateAlertV2Metric(ctx context.Context, alert AlertV2Metric) (createdAlert AlertV2Metric, err error) {
164+
if err = c.addNotificationChannelType(ctx, alert.NotificationChannelConfigList); err != nil {
165+
return
166+
}
167+
168+
if err = c.translateScopeSegmentLabels(ctx, &alert.Config.ScopedSegmentedConfig); err != nil {
169+
return
170+
}
171+
172+
body, err := c.createAlertV2(ctx, alert.ToJSON())
173+
if err != nil {
174+
return
175+
}
176+
177+
createdAlert = AlertV2MetricFromJSON(body)
178+
return
179+
}
180+
181+
func (c *sysdigMonitorClient) UpdateAlertV2Metric(ctx context.Context, alert AlertV2Metric) (updatedAlert AlertV2Metric, err error) {
182+
if err = c.addNotificationChannelType(ctx, alert.NotificationChannelConfigList); err != nil {
183+
return
184+
}
185+
186+
if err = c.translateScopeSegmentLabels(ctx, &alert.Config.ScopedSegmentedConfig); err != nil {
187+
return
188+
}
189+
190+
body, err := c.updateAlertV2(ctx, alert.ID, alert.ToJSON())
191+
if err != nil {
192+
return
193+
}
194+
195+
updatedAlert = AlertV2MetricFromJSON(body)
196+
return
197+
}
198+
199+
func (c *sysdigMonitorClient) GetAlertV2MetricById(ctx context.Context, alertID int) (alert AlertV2Metric, err error) {
200+
body, err := c.getAlertV2ById(ctx, alertID)
201+
if err != nil {
202+
return
203+
}
204+
205+
alert = AlertV2MetricFromJSON(body)
206+
return
207+
}
208+
209+
func (c *sysdigMonitorClient) DeleteAlertV2Metric(ctx context.Context, alertID int) (err error) {
210+
return c.deleteAlertV2(ctx, alertID)
211+
}
212+
213+
// downtime
214+
215+
func (c *sysdigMonitorClient) CreateAlertV2Downtime(ctx context.Context, alert AlertV2Downtime) (createdAlert AlertV2Downtime, err error) {
216+
if err = c.addNotificationChannelType(ctx, alert.NotificationChannelConfigList); err != nil {
217+
return
218+
}
219+
220+
if err = c.translateScopeSegmentLabels(ctx, &alert.Config.ScopedSegmentedConfig); err != nil {
221+
return
222+
}
223+
224+
body, err := c.createAlertV2(ctx, alert.ToJSON())
225+
if err != nil {
226+
return
227+
}
228+
229+
createdAlert = AlertV2DowntimeFromJSON(body)
230+
return
231+
}
232+
233+
func (c *sysdigMonitorClient) UpdateAlertV2Downtime(ctx context.Context, alert AlertV2Downtime) (updatedAlert AlertV2Downtime, err error) {
234+
if err = c.addNotificationChannelType(ctx, alert.NotificationChannelConfigList); err != nil {
235+
return
236+
}
237+
238+
if err = c.translateScopeSegmentLabels(ctx, &alert.Config.ScopedSegmentedConfig); err != nil {
239+
return
240+
}
241+
242+
body, err := c.updateAlertV2(ctx, alert.ID, alert.ToJSON())
243+
if err != nil {
244+
return
245+
}
246+
247+
updatedAlert = AlertV2DowntimeFromJSON(body)
248+
return
249+
}
250+
251+
func (c *sysdigMonitorClient) GetAlertV2DowntimeById(ctx context.Context, alertID int) (alert AlertV2Downtime, err error) {
252+
body, err := c.getAlertV2ById(ctx, alertID)
253+
if err != nil {
254+
return
255+
}
256+
257+
alert = AlertV2DowntimeFromJSON(body)
258+
return
259+
}
260+
261+
func (c *sysdigMonitorClient) DeleteAlertV2Downtime(ctx context.Context, alertID int) (err error) {
262+
return c.deleteAlertV2(ctx, alertID)
263+
}
264+
56265
// helpers
57266

58267
func (c *sysdigMonitorClient) createAlertV2(ctx context.Context, alertJson io.Reader) (responseBody []byte, err error) {
59-
response, err := c.doSysdigMonitorRequest(ctx, http.MethodPost, fmt.Sprintf("%s/create", c.alertsV2URL()), alertJson)
268+
response, err := c.doSysdigMonitorRequest(ctx, http.MethodPost, c.alertsV2URL(), alertJson)
60269
if err != nil {
61270
return
62271
}
@@ -116,3 +325,99 @@ func (c *sysdigMonitorClient) getAlertV2ById(ctx context.Context, alertID int) (
116325
body, err := io.ReadAll(response.Body)
117326
return body, err
118327
}
328+
329+
// buildLabelDescriptor gets the descriptor of a label in public notation from the v3/labels/descriptors api
330+
// this is not a general solution to get the descriptor for a public notation label since custom labels will not be properly translated
331+
// e.g. the public notation cloud_provider_tag_k8s_io_role_master will not be translated to the correct cloudProvider.tag.k8s.io/role/master id
332+
func (c *sysdigMonitorClient) buildLabelDescriptor(ctx context.Context, label string) (LabelDescriptorV3, error) {
333+
// always returns 200, even if the label does not exist for the customer
334+
response, err := c.doSysdigMonitorRequest(ctx, http.MethodGet, c.labelsDescriptorsV3URL(label), nil)
335+
if err != nil {
336+
return LabelDescriptorV3{}, err
337+
}
338+
defer response.Body.Close()
339+
340+
if response.StatusCode != http.StatusOK {
341+
err = errorFromResponse(response)
342+
return LabelDescriptorV3{}, err
343+
}
344+
345+
body, err := io.ReadAll(response.Body)
346+
if err != nil {
347+
return LabelDescriptorV3{}, err
348+
}
349+
350+
var labelsDescriptorsV3result struct {
351+
LabelDescriptorV3 `json:"labelDescriptor"`
352+
}
353+
354+
err = json.Unmarshal(body, &labelsDescriptorsV3result)
355+
if err != nil {
356+
return LabelDescriptorV3{}, err
357+
}
358+
359+
return labelsDescriptorsV3result.LabelDescriptorV3, nil
360+
}
361+
362+
func (c *sysdigMonitorClient) getLabels(ctx context.Context, label string) ([]LabelDescriptorV3, error) {
363+
364+
var labelsResp struct {
365+
AllLabels []LabelDescriptorV3 `json:"allLabels"`
366+
}
367+
368+
response, err := c.doSysdigMonitorRequest(ctx, http.MethodGet, c.labelsV3URL(), nil)
369+
if err != nil {
370+
return nil, err
371+
}
372+
defer response.Body.Close()
373+
374+
if response.StatusCode != http.StatusOK {
375+
err = errorFromResponse(response)
376+
return nil, err
377+
}
378+
379+
body, err := io.ReadAll(response.Body)
380+
if err != nil {
381+
return nil, err
382+
}
383+
384+
err = json.Unmarshal(body, &labelsResp)
385+
return labelsResp.AllLabels, err
386+
387+
}
388+
389+
var labelCache struct {
390+
sync.Mutex
391+
392+
labels []LabelDescriptorV3
393+
}
394+
395+
// GetLabel gets the descriptor from a label in public notation
396+
func (c *sysdigMonitorClient) GetLabelDescriptor(ctx context.Context, label string) (LabelDescriptorV3, error) {
397+
var alertDescriptior LabelDescriptorV3
398+
399+
labelCache.Lock()
400+
defer labelCache.Unlock()
401+
402+
if len(labelCache.labels) == 0 {
403+
log.Printf("[DEBUG] GetLabel for %s: fetching all labels", label)
404+
labelDescriptors, err := c.getLabels(ctx, label)
405+
if err != nil {
406+
return alertDescriptior, err
407+
}
408+
labelCache.labels = labelDescriptors
409+
} else {
410+
log.Printf("[DEBUG] GetLabel for %s: using cached labels", label)
411+
}
412+
413+
for _, l := range labelCache.labels {
414+
if l.PublicID == label {
415+
return l, nil
416+
}
417+
}
418+
419+
// if the label did not exist, build the descriptor from /v3/labels/descriptor
420+
log.Printf("[DEBUG] GetLabel for %s: not found in existing customer labels", label)
421+
return c.buildLabelDescriptor(ctx, label)
422+
423+
}

sysdig/internal/client/monitor/client.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,23 @@ type SysdigMonitorClient interface {
2424
UpdateAlertV2Prometheus(context.Context, AlertV2Prometheus) (AlertV2Prometheus, error)
2525
GetAlertV2PrometheusById(context.Context, int) (AlertV2Prometheus, error)
2626

27+
CreateAlertV2Event(context.Context, AlertV2Event) (AlertV2Event, error)
28+
DeleteAlertV2Event(context.Context, int) error
29+
UpdateAlertV2Event(context.Context, AlertV2Event) (AlertV2Event, error)
30+
GetAlertV2EventById(context.Context, int) (AlertV2Event, error)
31+
32+
CreateAlertV2Metric(context.Context, AlertV2Metric) (AlertV2Metric, error)
33+
DeleteAlertV2Metric(context.Context, int) error
34+
UpdateAlertV2Metric(context.Context, AlertV2Metric) (AlertV2Metric, error)
35+
GetAlertV2MetricById(context.Context, int) (AlertV2Metric, error)
36+
37+
CreateAlertV2Downtime(context.Context, AlertV2Downtime) (AlertV2Downtime, error)
38+
DeleteAlertV2Downtime(context.Context, int) error
39+
UpdateAlertV2Downtime(context.Context, AlertV2Downtime) (AlertV2Downtime, error)
40+
GetAlertV2DowntimeById(context.Context, int) (AlertV2Downtime, error)
41+
42+
GetLabelDescriptor(ctx context.Context, label string) (LabelDescriptorV3, error)
43+
2744
CreateTeam(context.Context, Team) (Team, error)
2845
GetTeamById(context.Context, int) (Team, error)
2946
UpdateTeam(context.Context, Team) (Team, error)

0 commit comments

Comments
 (0)