Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3001996
DI-27070 Add support for ACLP alerting
srbhaakamai Nov 13, 2025
625296b
Fix lint
lgarber-akamai Nov 13, 2025
926f2a0
Fix deprecation formatting
lgarber-akamai Nov 13, 2025
d72eefa
Fix lint
lgarber-akamai Nov 13, 2025
18272f3
fixed review comments
srbhaakamai Nov 27, 2025
423915d
fixed review comments
srbhaakamai Nov 27, 2025
e933ea7
Merge branch 'main' into main
srbhaakamai Nov 27, 2025
e5b6ef1
Delete request_helpers.go
srbhaakamai Nov 27, 2025
e836182
fixed review comments
srbhaakamai Nov 27, 2025
5e17334
fixed review comments
srbhaakamai Nov 27, 2025
a066c29
fix format
lgarber-akamai Dec 1, 2025
51b93c7
formatting issues fixed
srbhaakamai Dec 1, 2025
da18794
Fixed test failures
ezilber-akamai Dec 8, 2025
3a909a6
reverted change to go.mod
srbhaakamai Dec 10, 2025
76699b2
fixed review comments
srbhaakamai Dec 10, 2025
0a35eb6
Added new test cases
srbhaakamai Dec 10, 2025
a4c44e2
Fixed review comments
srbhaakamai Dec 10, 2025
093163e
Merge branch 'main' into main
ezilber-akamai Dec 16, 2025
164a464
Ran make tidy
ezilber-akamai Dec 16, 2025
03831e1
fixed review comments from ye-chen and copilot
srbhaakamai Dec 17, 2025
9c2b0e4
Apply suggestion from @yec-akamai
srbhaakamai Dec 18, 2025
5da0bdf
fixed fixtures and other review comments
srbhaakamai Dec 18, 2025
ba2563f
Merge branch 'main' into main
srbhaakamai Dec 18, 2025
8f24b73
fixed comments and lint errors
srbhaakamai Dec 19, 2025
d0ad712
fix fixtures
yec-akamai Dec 22, 2025
c823d20
fix alert channel type
yec-akamai Dec 22, 2025
77d36ee
import resort
yec-akamai Dec 22, 2025
e7a4f8b
Merge branch 'main' into main
yec-akamai Dec 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions alert_channels.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package linodego

import (
"context"
"encoding/json"
"time"

"github.com/linode/linodego/internal/parseabletime"
)

type AlertNotificationType string

const (
EmailAlertNotification AlertNotificationType = "email"
)

type AlertChannelType string

const (
SystemAlertChannel AlertChannelType = "system"
UserAlertChannel AlertChannelType = "user"
)

// AlertChannelEnvelope represents a single alert channel entry returned inside alert definition
type AlertChannelEnvelope struct {
ID int `json:"id"`
Label string `json:"label"`
Type string `json:"type"`
URL string `json:"url"`
}

// AlertChannel represents a Monitor Channel object.
type AlertChannel struct {
Alerts []AlertChannelEnvelope `json:"alerts"`
ChannelType AlertNotificationType `json:"channel_type"`
Content ChannelContent `json:"content"`
Created *time.Time `json:"-"`
CreatedBy string `json:"created_by"`
Updated *time.Time `json:"-"`
UpdatedBy string `json:"updated_by"`
ID int `json:"id"`
Label string `json:"label"`
Type AlertChannelType `json:"type"`
}

type EmailChannelContent struct {
EmailAddresses []string `json:"email_addresses"`
}

// ChannelContent represents the content block for an AlertChannel, which varies by channel type.
type ChannelContent struct {
Email *EmailChannelContent `json:"email"`
// Other channel types like 'webhook', 'slack' could be added here as optional fields.
}

// UnmarshalJSON implements the json.Unmarshaler interface
func (a *AlertChannel) UnmarshalJSON(b []byte) error {
type Mask AlertChannel

p := struct {
*Mask

Updated *parseabletime.ParseableTime `json:"updated"`
Created *parseabletime.ParseableTime `json:"created"`
}{
Mask: (*Mask)(a),
}

if err := json.Unmarshal(b, &p); err != nil {
return err
}

a.Updated = (*time.Time)(p.Updated)
a.Created = (*time.Time)(p.Created)

return nil
}

// ListAlertChannels gets a paginated list of Alert Channels.
func (c *Client) ListAlertChannels(ctx context.Context, opts *ListOptions) ([]AlertChannel, error) {
endpoint := formatAPIPath("monitor/alert-channels")
return getPaginatedResults[AlertChannel](ctx, c, endpoint, opts)
}
256 changes: 256 additions & 0 deletions monitor_alert_definitions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
package linodego

import (
"context"
"encoding/json"
"time"

"github.com/linode/linodego/internal/parseabletime"
)

// AlertDefinition represents an ACLP Alert Definition object
type AlertDefinition struct {
ID int `json:"id"`
Label string `json:"label"`
Severity int `json:"severity"`
Type string `json:"type"`
ServiceType string `json:"service_type"`
Status string `json:"status"`
HasMoreResources bool `json:"has_more_resources"`
RuleCriteria RuleCriteria `json:"rule_criteria"`
TriggerConditions TriggerConditions `json:"trigger_conditions"`
AlertChannels []AlertChannelEnvelope `json:"alert_channels"`
Created *time.Time `json:"-"`
Updated *time.Time `json:"-"`
UpdatedBy string `json:"updated_by"`
CreatedBy string `json:"created_by"`
EntityIDs []any `json:"entity_ids"`
Description string `json:"description"`
Class string `json:"class"`
}

// Backwards-compatible alias

// MonitorAlertDefinition represents an ACLP Alert Definition object
//
// Deprecated: AlertDefinition should be used in all new implementations.
type MonitorAlertDefinition = AlertDefinition

// TriggerConditions represents the trigger conditions for an alert.
type TriggerConditions struct {
CriteriaCondition string `json:"criteria_condition,omitempty"`
EvaluationPeriodSeconds int `json:"evaluation_period_seconds,omitempty"`
PollingIntervalSeconds int `json:"polling_interval_seconds,omitempty"`
TriggerOccurrences int `json:"trigger_occurrences,omitempty"`
}

// RuleCriteria represents the rule criteria for an alert.
type RuleCriteria struct {
Rules []Rule `json:"rules,omitempty"`
}

// Rule represents a single rule for an alert.
type Rule struct {
AggregateFunction string `json:"aggregate_function"`
DimensionFilters []DimensionFilter `json:"dimension_filters"`
Label string `json:"label"`
Metric string `json:"metric"`
Operator string `json:"operator"`
Threshold float64 `json:"threshold"`
Unit string `json:"unit"`
}

// DimensionFilter represents a single dimension filter used inside a Rule.
type DimensionFilter struct {
DimensionLabel string `json:"dimension_label"`
Label string `json:"label"`
Operator string `json:"operator"`
Value string `json:"value"`
}

// RuleCriteriaOptions represents the rule criteria options for an alert.
type RuleCriteriaOptions struct {
Rules []RuleOptions `json:"rules,omitempty"`
}

// RuleOptions represents a single rule option for an alert.
type RuleOptions struct {
AggregateFunction string `json:"aggregate_function,omitempty"`
DimensionFilters []DimensionFilterOptions `json:"dimension_filters,omitempty"`
Metric string `json:"metric,omitempty"`
Operator string `json:"operator,omitempty"`
Threshold float64 `json:"threshold,omitempty"`
}

// DimensionFilterOptions represents a single dimension filter option used inside a Rule.
type DimensionFilterOptions struct {
DimensionLabel string `json:"dimension_label,omitempty"`
Operator string `json:"operator,omitempty"`
Value string `json:"value,omitempty"`
}

// AlertType represents the type of alert: "user" or "system"
type AlertType string

const (
AlertTypeUser AlertType = "user"
AlertTypeSystem AlertType = "system"
)

// Severity represents the severity level of an alert.
// 0 = Severe, 1 = Medium, 2 = Low, 3 = Info
type Severity int

const (
SeveritySevere Severity = 0
SeverityMedium Severity = 1
SeverityLow Severity = 2
SeverityInfo Severity = 3
)

// CriteriaCondition represents supported criteria conditions
type CriteriaCondition string

const (
CriteriaConditionAll CriteriaCondition = "ALL"
)

// AlertDefinitionCreateOptions are the options used to create a new alert definition.
type AlertDefinitionCreateOptions struct {
Label string `json:"label"` // mandatory
Severity int `json:"severity"` // mandatory
ChannelIDs []int `json:"channel_ids"` // mandatory
RuleCriteria RuleCriteriaOptions `json:"rule_criteria"` // optional
TriggerConditions TriggerConditions `json:"trigger_conditions"` // optional
EntityIDs []string `json:"entity_ids,omitempty"` // optional
Description string `json:"description,omitempty"` // optional
Comment on lines +123 to +126
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same problems here

}

// AlertDefinitionUpdateOptions are the options used to update an alert definition.
type AlertDefinitionUpdateOptions struct {
Label string `json:"label"` // mandatory
Severity int `json:"severity"` // mandatory
ChannelIDs []int `json:"channel_ids"` // mandatory
RuleCriteria RuleCriteriaOptions `json:"rule_criteria"` // optional
TriggerConditions TriggerConditions `json:"trigger_conditions"` // optional
Comment on lines +134 to +135
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

omitempty should apply to all option fields, and optional fields should be pointers with an exception of nil-able slice

EntityIDs []string `json:"entity_ids,omitempty"` // optional
Description string `json:"description,omitempty"` // optional
}

// UnmarshalJSON implements the json.Unmarshaler interface
func (i *AlertDefinition) UnmarshalJSON(b []byte) error {
type Mask AlertDefinition

p := struct {
*Mask

Created *parseabletime.ParseableTime `json:"created"`
Updated *parseabletime.ParseableTime `json:"updated"`
}{
Mask: (*Mask)(i),
}

if err := json.Unmarshal(b, &p); err != nil {
return err
}

i.Created = (*time.Time)(p.Created)
i.Updated = (*time.Time)(p.Updated)

return nil
}

// ListMonitorAlertDefinitions returns a paginated list of ACLP Monitor Alert Definitions by service type.
func (c *Client) ListMonitorAlertDefinitions(
ctx context.Context,
serviceType string,
Copy link
Member

@zliang-akamai zliang-akamai Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should serviceType be ServiceType Enum type?

opts *ListOptions,
) ([]AlertDefinition, error) {
var endpoint string
if serviceType != "" {
endpoint = formatAPIPath("monitor/services/%s/alert-definitions", serviceType)
} else {
endpoint = formatAPIPath("monitor/alert-definitions")
}
Comment on lines +170 to +174
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably need to separate these two endpoints into two functions in linodego. Because go can't take optional parameters.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

created separate function ListAllMonitorAlertDefinitions please check

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you clean it up if you implement it in another function?


return getPaginatedResults[AlertDefinition](ctx, c, endpoint, opts)
}

// ListAllMonitorAlertDefinitions returns a paginated list of all ACLP Monitor Alert Definitions under this account.
func (c *Client) ListAllMonitorAlertDefinitions(
ctx context.Context,
opts *ListOptions,
) ([]AlertDefinition, error) {
endpoint := formatAPIPath("monitor/alert-definitions")
return getPaginatedResults[AlertDefinition](ctx, c, endpoint, opts)
}

// GetMonitorAlertDefinition gets an ACLP Monitor Alert Definition.
func (c *Client) GetMonitorAlertDefinition(
ctx context.Context,
serviceType string,
alertID int,
) (*MonitorAlertDefinition, error) {
e := formatAPIPath("monitor/services/%s/alert-definitions/%d", serviceType, alertID)
return doGETRequest[AlertDefinition](ctx, c, e)
}

// CreateMonitorAlertDefinition creates an ACLP Monitor Alert Definition.
func (c *Client) CreateMonitorAlertDefinition(
ctx context.Context,
serviceType string,
opts AlertDefinitionCreateOptions,
) (*MonitorAlertDefinition, error) {
e := formatAPIPath("monitor/services/%s/alert-definitions", serviceType)
return doPOSTRequest[AlertDefinition](ctx, c, e, opts)
}

// CreateMonitorAlertDefinitionWithIdempotency creates an ACLP Monitor Alert Definition
// and optionally sends an Idempotency-Key header to make the request idempotent.
func (c *Client) CreateMonitorAlertDefinitionWithIdempotency(
ctx context.Context,
serviceType string,
opts AlertDefinitionCreateOptions,
idempotencyKey string,
) (*MonitorAlertDefinition, error) {
e := formatAPIPath("monitor/services/%s/alert-definitions", serviceType)

var result AlertDefinition

req := c.R(ctx).SetResult(&result)

if idempotencyKey != "" {
req.SetHeader("Idempotency-Key", idempotencyKey)
}

body, err := json.Marshal(opts)
if err != nil {
return nil, err
}

req.SetBody(string(body))

r, err := coupleAPIErrors(req.Post(e))
if err != nil {
return nil, err
}

return r.Result().(*AlertDefinition), nil
}

// UpdateMonitorAlertDefinition updates an ACLP Monitor Alert Definition.
func (c *Client) UpdateMonitorAlertDefinition(
ctx context.Context,
serviceType string,
alertID int,
opts AlertDefinitionUpdateOptions,
) (*AlertDefinition, error) {
e := formatAPIPath("monitor/services/%s/alert-definitions/%d", serviceType, alertID)
return doPUTRequest[AlertDefinition](ctx, c, e, opts)
}

// DeleteMonitorAlertDefinition deletes an ACLP Monitor Alert Definition.
func (c *Client) DeleteMonitorAlertDefinition(ctx context.Context, serviceType string, alertID int) error {
e := formatAPIPath("monitor/services/%s/alert-definitions/%d", serviceType, alertID)
return doDELETERequest(ctx, c, e)
}
2 changes: 1 addition & 1 deletion monitor_dashboards.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const (
ServiceTypeDBaaS ServiceType = "dbaas"
ServiceTypeACLB ServiceType = "aclb"
ServiceTypeNodeBalancer ServiceType = "nodebalancer"
ServiceTypeObjectStorage ServiceType = "objectstorage"
ServiceTypeObjectStorage ServiceType = "object_storage"
ServiceTypeVPC ServiceType = "vpc"
ServiceTypeFirewallService ServiceType = "firewall"
ServiceTypeNetLoadBalancer ServiceType = "netloadbalancer"
Expand Down
Loading