Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
64 changes: 64 additions & 0 deletions alert_channels.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package linodego

import (
"context"
)

// 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 {
ID int `json:"id"`
Label string `json:"label"`
ChannelType string `json:"channel_type"`
Content ChannelContent `json:"content"`
Created string `json:"created"`
CreatedBy string `json:"created_by"`
Updated string `json:"updated"`
UpdatedBy string `json:"updated_by"`
}

// AlertChannelCreateOptions are the options used to create a new Monitor Channel.
type AlertChannelCreateOptions struct {
Label string `json:"label"`
Type string `json:"type"`
Details AlertChannelDetailOptions `json:"details"`
}

// AlertChannelDetailOptions are the options used to create the details of a new Monitor Channel.
type AlertChannelDetailOptions struct {
To string `json:"to,omitempty"`
}
Comment on lines +27 to +37
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems only GET endpoints are supported currently. Can you remove the CreateOptions?


// AlertingChannelCreateOptions are the options used to create a new Monitor Channel.
//
// Deprecated: AlertChannelCreateOptions should be used in all new implementations.
type AlertingChannelCreateOptions = AlertChannelCreateOptions

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,omitempty"`
// Other channel types like 'webhook', 'slack' could be added here as optional fields.
}

// 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)
}

// GetAlertChannel gets an Alert Channel by ID.
func (c *Client) GetAlertChannel(ctx context.Context, channelID int) (*AlertChannel, error) {
e := formatAPIPath("monitor/alert-channels/%d", channelID)
return doGETRequest[AlertChannel](ctx, c, e)
}
Comment on lines +61 to +64
Copy link
Contributor

Choose a reason for hiding this comment

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

Just curious, is getting a single alert channel available now? Didn't see it in the api doc though

2 changes: 0 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,4 @@ require (

go 1.24.0

toolchain go1.25.1

Comment on lines -23 to -24
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you revert the change to go mod? should not be necessary here

retract v1.0.0 // Accidental branch push
230 changes: 230 additions & 0 deletions monitor_alert_definitions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
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"`
Rule *Rule `json:"rule"`
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this field rule is under this struct in the api doc. It should be removed from here.

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 []string `json:"entity_ids"`
Copy link
Contributor

Choose a reason for hiding this comment

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

Judging from the api doc, the entity_ids seems could be any type: The format type for an entity_id may vary, based on the Akamai Cloud service_type

You should build them as []any to accommodate that.

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 {
Copy link
Contributor

@yec-akamai yec-akamai Dec 9, 2025

Choose a reason for hiding this comment

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

The Rule object is not the same as it is in the create options. i.e. unit and label is not part of the create body param. Can you double check it, and create a separate option struct RuleOptions to distinguish them? There could be other fields which diverge on read and write path that I may miss, can you double check them as well?

AggregateFunction string `json:"aggregate_function,omitempty"`
DimensionFilters []DimensionFilter `json:"dimension_filters,omitempty"`
Label string `json:"label,omitempty"`
Metric string `json:"metric,omitempty"`
Operator string `json:"operator,omitempty"`
Threshold *float64 `json:"threshold,omitempty"`
Unit *string `json:"unit,omitempty"`
Comment on lines +60 to +61
Copy link
Contributor

Choose a reason for hiding this comment

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

Just curious why is is necessary to use pointer for these two fields?

}

// DimensionFilter represents a single dimension filter used inside a Rule.
type DimensionFilter struct {
DimensionLabel string `json:"dimension_label"`
Label string `json:"label"`
Copy link
Contributor

@yec-akamai yec-akamai Dec 9, 2025

Choose a reason for hiding this comment

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

I find label should not be part of the create options too. It only in the return result

Operator string `json:"operator"`
Value any `json:"value"`
}

// 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 {
ServiceType string `json:"service_type"` // mandatory
Copy link
Contributor

Choose a reason for hiding this comment

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

You don't need to include this field here since it's a path param.

Label string `json:"label"` // mandatory
Severity int `json:"severity"` // mandatory
ChannelIDs []int `json:"channel_ids"` // mandatory
RuleCriteria *RuleCriteria `json:"rule_criteria,omitempty"` // optional
TriggerConditions *TriggerConditions `json:"trigger_conditions,omitempty"` // optional
EntityIDs []string `json:"entity_ids,omitempty"` // optional
Description string `json:"description,omitempty"` // optional
}

// AlertDefinitionUpdateOptions are the options used to update an alert definition.
type AlertDefinitionUpdateOptions struct {
ServiceType string `json:"service_type"` // mandatory, must not be empty
AlertID int `json:"alert_id"` // mandatory, must not be zero
Comment on lines +112 to +113
Copy link
Contributor

Choose a reason for hiding this comment

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

These two path param can be removed from options as well

Label string `json:"label,omitempty"` // optional
Severity int `json:"severity,omitempty"` // optional, should be int to match AlertDefinition
Description string `json:"description,omitempty"` // optional
RuleCriteria *RuleCriteria `json:"rule_criteria,omitempty"` // optional
TriggerConditions *TriggerConditions `json:"trigger_conditions,omitempty"` // optional
EntityIDs []string `json:"entity_ids,omitempty"` // optional
ChannelIDs []int `json:"channel_ids,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 gets a paginated list of ACLP Monitor Alert Definitions.
func (c *Client) ListMonitorAlertDefinitions(
ctx context.Context,
serviceType string,
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 +153 to +157
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.


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