Skip to content

Commit aed99f8

Browse files
Srinidhi Bhatlgarber-akamai
authored andcommitted
DI-27070 Add support for ACLP alerting
1 parent e689c8b commit aed99f8

10 files changed

+1088
-249
lines changed

alert_channels.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package linodego
2+
3+
import (
4+
"context"
5+
)
6+
7+
// AlertChannelEnvelope represents a single alert channel entry returned inside alert definition
8+
type AlertChannelEnvelope struct {
9+
ID int `json:"id"`
10+
Label string `json:"label"`
11+
Type string `json:"type"`
12+
URL string `json:"url"`
13+
}
14+
15+
// AlertChannel represents a Monitor Channel object.
16+
type AlertChannel struct {
17+
ID int `json:"id"`
18+
Label string `json:"label"`
19+
ChannelType string `json:"channel_type"`
20+
Content ChannelContent `json:"content"`
21+
Created string `json:"created"`
22+
CreatedBy string `json:"created_by"`
23+
Updated string `json:"updated"`
24+
UpdatedBy string `json:"updated_by"`
25+
URL string `json:"url"`
26+
}
27+
28+
// AlertChannelDetail represents the details of a Monitor Channel.
29+
type AlertChannelDetail struct {
30+
To string `json:"to,omitempty"`
31+
From string `json:"from,omitempty"`
32+
User string `json:"user,omitempty"`
33+
Token string `json:"token,omitempty"`
34+
URL string `json:"url,omitempty"`
35+
}
36+
37+
// AlertChannelCreateOptions are the options used to create a new Monitor Channel.
38+
type AlertChannelCreateOptions struct {
39+
Label string `json:"label"`
40+
Type string `json:"type"`
41+
Details AlertChannelDetailOptions `json:"details"`
42+
}
43+
44+
// AlertChannelDetailOptions are the options used to create the details of a new Monitor Channel.
45+
type AlertChannelDetailOptions struct {
46+
To string `json:"to,omitempty"`
47+
}
48+
49+
// Backwards-compat alias for older name
50+
type AlertingChannelCreateOptions = AlertChannelCreateOptions
51+
52+
type EmailChannelContent struct {
53+
EmailAddresses []string `json:"email_addresses"`
54+
}
55+
56+
// ChannelContent represents the content block for an AlertChannel, which varies by channel type.
57+
type ChannelContent struct {
58+
Email *EmailChannelContent `json:"email,omitempty"`
59+
// Other channel types like 'webhook', 'slack' could be added here as optional fields.
60+
}
61+
62+
// ListAlertChannels gets a paginated list of Alert Channels.
63+
func (c *Client) ListAlertChannels(ctx context.Context, opts *ListOptions) ([]AlertChannel, error) {
64+
endpoint := formatAPIV4BetaPath("monitor/alert-channels")
65+
return getPaginatedResults[AlertChannel](ctx, c, endpoint, opts)
66+
}
67+
68+
// GetAlertChannel gets an Alert Channel by ID.
69+
func (c *Client) GetAlertChannel(ctx context.Context, channelID int) (*AlertChannel, error) {
70+
e := formatAPIV4BetaPath("monitor/alert-channels/%d", channelID)
71+
return doGETRequest[AlertChannel](ctx, c, e)
72+
}

monitor_alert_definitions.go

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
package linodego
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"time"
7+
8+
"github.com/linode/linodego/internal/parseabletime"
9+
)
10+
11+
// AlertDefinition represents an ACLP Alert Definition object
12+
type AlertDefinition struct {
13+
ID int `json:"id"`
14+
Label string `json:"label"`
15+
Severity int `json:"severity"`
16+
Type string `json:"type"`
17+
ServiceType string `json:"service_type"`
18+
Status string `json:"status"`
19+
HasMoreResources bool `json:"has_more_resources"`
20+
Rule *Rule `json:"rule"`
21+
RuleCriteria *RuleCriteria `json:"rule_criteria"`
22+
TriggerConditions *TriggerConditions `json:"trigger_conditions"`
23+
AlertChannels []AlertChannelEnvelope `json:"alert_channels"`
24+
Created *time.Time `json:"-"`
25+
Updated *time.Time `json:"-"`
26+
UpdatedBy string `json:"updated_by"`
27+
CreatedBy string `json:"created_by"`
28+
EntityIDs []string `json:"entity_ids"`
29+
Description string `json:"description"`
30+
Class string `json:"class"`
31+
}
32+
33+
// Backwards-compatible alias
34+
type MonitorAlertDefinition = AlertDefinition
35+
36+
// TriggerConditions represents the trigger conditions for an alert.
37+
type TriggerConditions struct {
38+
CriteriaCondition string `json:"criteria_condition,omitempty"`
39+
EvaluationPeriodSeconds int `json:"evaluation_period_seconds,omitempty"`
40+
PollingIntervalSeconds int `json:"polling_interval_seconds,omitempty"`
41+
TriggerOccurrences int `json:"trigger_occurrences,omitempty"`
42+
}
43+
44+
// RuleCriteria represents the rule criteria for an alert.
45+
type RuleCriteria struct {
46+
Rules []Rule `json:"rules,omitempty"`
47+
}
48+
49+
// Rule represents a single rule for an alert.
50+
type Rule struct {
51+
AggregateFunction string `json:"aggregate_function,omitempty"`
52+
DimensionFilters []DimensionFilter `json:"dimension_filters,omitempty"`
53+
Label string `json:"label,omitempty"`
54+
Metric string `json:"metric,omitempty"`
55+
Operator string `json:"operator,omitempty"`
56+
Threshold *float64 `json:"threshold,omitempty"`
57+
Unit *string `json:"unit,omitempty"`
58+
}
59+
60+
// DimensionFilter represents a single dimension filter used inside a Rule.
61+
type DimensionFilter struct {
62+
DimensionLabel string `json:"dimension_label"`
63+
Label string `json:"label"`
64+
Operator string `json:"operator"`
65+
Value interface{} `json:"value"`
66+
}
67+
68+
// AlertType represents the type of alert: "user" or "system"
69+
type AlertType string
70+
71+
const (
72+
AlertTypeUser AlertType = "user"
73+
AlertTypeSystem AlertType = "system"
74+
)
75+
76+
// Severity represents the severity level of an alert.
77+
// 0 = Severe, 1 = Medium, 2 = Low, 3 = Info
78+
type Severity int
79+
80+
const (
81+
SeveritySevere Severity = 0
82+
SeverityMedium Severity = 1
83+
SeverityLow Severity = 2
84+
SeverityInfo Severity = 3
85+
)
86+
87+
// CriteriaCondition represents supported criteria conditions
88+
type CriteriaCondition string
89+
90+
const (
91+
CriteriaConditionAll CriteriaCondition = "ALL"
92+
)
93+
94+
// AlertDefinitionCreateOptions are the options used to create a new alert definition.
95+
type AlertDefinitionCreateOptions struct {
96+
ServiceType string `json:"service_type"` // mandatory
97+
Label string `json:"label"` // mandatory
98+
Severity int `json:"severity"` // mandatory
99+
ChannelIDs []int `json:"channel_ids"` // mandatory
100+
RuleCriteria *RuleCriteria `json:"rule_criteria,omitempty"` // optional
101+
TriggerConditions *TriggerConditions `json:"trigger_conditions,omitempty"` // optional
102+
EntityIDs []string `json:"entity_ids,omitempty"` // optional
103+
Description string `json:"description,omitempty"` // optional
104+
}
105+
106+
// AlertDefinitionUpdateOptions are the options used to update an alert definition.
107+
type AlertDefinitionUpdateOptions struct {
108+
ServiceType string `json:"service_type"` // mandatory, must not be empty
109+
AlertID int `json:"alert_id"` // mandatory, must not be zero
110+
Label string `json:"label,omitempty"` // optional
111+
Severity int `json:"severity,omitempty"` // optional, should be int to match AlertDefinition
112+
Description string `json:"description,omitempty"` // optional
113+
RuleCriteria *RuleCriteria `json:"rule_criteria,omitempty"` // optional
114+
TriggerConditions *TriggerConditions `json:"trigger_conditions,omitempty"` // optional
115+
EntityIDs []string `json:"entity_ids,omitempty"` // optional
116+
ChannelIDs []int `json:"channel_ids,omitempty"` // optional
117+
}
118+
119+
// UnmarshalJSON implements the json.Unmarshaler interface
120+
func (i *AlertDefinition) UnmarshalJSON(b []byte) error {
121+
type Mask AlertDefinition
122+
123+
p := struct {
124+
*Mask
125+
Created *parseabletime.ParseableTime `json:"created"`
126+
Updated *parseabletime.ParseableTime `json:"updated"`
127+
}{
128+
Mask: (*Mask)(i),
129+
}
130+
131+
if err := json.Unmarshal(b, &p); err != nil {
132+
return err
133+
}
134+
135+
i.Created = (*time.Time)(p.Created)
136+
i.Updated = (*time.Time)(p.Updated)
137+
138+
return nil
139+
}
140+
141+
// ListMonitorAlertDefinitions gets a paginated list of ACLP Monitor Alert Definitions.
142+
func (c *Client) ListMonitorAlertDefinitions(ctx context.Context, serviceType string, opts *ListOptions) ([]MonitorAlertDefinition, error) {
143+
var endpoint string
144+
if serviceType != "" {
145+
endpoint = formatAPIV4BetaPath("monitor/services/%s/alert-definitions", serviceType)
146+
} else {
147+
endpoint = formatAPIV4BetaPath("monitor/alert-definitions")
148+
}
149+
return getPaginatedResults[AlertDefinition](ctx, c, endpoint, opts)
150+
}
151+
152+
// GetMonitorAlertDefinition gets an ACLP Monitor Alert Definition.
153+
func (c *Client) GetMonitorAlertDefinition(ctx context.Context, serviceType string, alertID int) (*MonitorAlertDefinition, error) {
154+
e := formatAPIV4BetaPath("monitor/services/%s/alert-definitions/%d", serviceType, alertID)
155+
return doGETRequest[AlertDefinition](ctx, c, e)
156+
}
157+
158+
// CreateMonitorAlertDefinition creates an ACLP Monitor Alert Definition.
159+
func (c *Client) CreateMonitorAlertDefinition(ctx context.Context, serviceType string, opts AlertDefinitionCreateOptions) (*MonitorAlertDefinition, error) {
160+
e := formatAPIV4BetaPath("monitor/services/%s/alert-definitions", serviceType)
161+
return doPOSTRequest[AlertDefinition](ctx, c, e, opts)
162+
}
163+
164+
// CreateMonitorAlertDefinitionWithIdempotency creates an ACLP Monitor Alert Definition
165+
// and optionally sends an Idempotency-Key header to make the request idempotent.
166+
func (c *Client) CreateMonitorAlertDefinitionWithIdempotency(ctx context.Context, serviceType string, opts AlertDefinitionCreateOptions, idempotencyKey string) (*MonitorAlertDefinition, error) {
167+
e := formatAPIV4BetaPath("monitor/services/%s/alert-definitions", serviceType)
168+
169+
var result AlertDefinition
170+
req := c.R(ctx).SetResult(&result)
171+
172+
if idempotencyKey != "" {
173+
req.SetHeader("Idempotency-Key", idempotencyKey)
174+
}
175+
176+
body, err := json.Marshal(opts)
177+
if err != nil {
178+
return nil, err
179+
}
180+
181+
req.SetBody(string(body))
182+
183+
r, err := coupleAPIErrors(req.Post(e))
184+
if err != nil {
185+
return nil, err
186+
}
187+
188+
return r.Result().(*AlertDefinition), nil
189+
}
190+
191+
// UpdateMonitorAlertDefinition updates an ACLP Monitor Alert Definition.
192+
func (c *Client) UpdateMonitorAlertDefinition(ctx context.Context, serviceType string, alertID int, opts AlertDefinitionUpdateOptions) (*AlertDefinition, error) {
193+
e := formatAPIV4BetaPath("monitor/services/%s/alert-definitions/%d", serviceType, alertID)
194+
return doPUTRequest[AlertDefinition](ctx, c, e, opts)
195+
}
196+
197+
// DeleteMonitorAlertDefinition deletes an ACLP Monitor Alert Definition.
198+
func (c *Client) DeleteMonitorAlertDefinition(ctx context.Context, serviceType string, alertID int) error {
199+
e := formatAPIV4BetaPath("monitor/services/%s/alert-definitions/%d", serviceType, alertID)
200+
return doDELETERequest(ctx, c, e)
201+
}

monitor_dashboards.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const (
2828
ServiceTypeDBaaS ServiceType = "dbaas"
2929
ServiceTypeACLB ServiceType = "aclb"
3030
ServiceTypeNodeBalancer ServiceType = "nodebalancer"
31-
ServiceTypeObjectStorage ServiceType = "objectstorage"
31+
ServiceTypeObjectStorage ServiceType = "object_storage"
3232
ServiceTypeVPC ServiceType = "vpc"
3333
ServiceTypeFirewallService ServiceType = "firewall"
3434
ServiceTypeNetLoadBalancer ServiceType = "netloadbalancer"

request_helpers.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"net/url"
88
"reflect"
9+
"strings"
910
)
1011

1112
// paginatedResponse represents a single response from a paginated
@@ -316,6 +317,17 @@ func formatAPIPath(format string, args ...any) string {
316317
return fmt.Sprintf(format, escapedArgs...)
317318
}
318319

320+
// formatAPIV4BetaPath builds a fully-qualified URL for v4beta endpoints.
321+
// We return a full URL (including scheme and host) so requests made with the
322+
// standard client (which is pointed at /v4) will hit the /v4beta host/path
323+
// directly.
324+
func formatAPIV4BetaPath(format string, args ...any) string {
325+
p := formatAPIPath(format, args...)
326+
// Ensure we don't produce a double slash when joining
327+
p = strings.TrimPrefix(p, "/")
328+
return fmt.Sprintf("%s://%s/%s/%s", APIProto, APIHost, "v4beta", p)
329+
}
330+
319331
func isNil(i any) bool {
320332
if i == nil {
321333
return true

0 commit comments

Comments
 (0)