Skip to content

Commit c0887c1

Browse files
committed
feat(cockpit): add waiters for alerts
1 parent c87d8dd commit c0887c1

File tree

1 file changed

+162
-0
lines changed

1 file changed

+162
-0
lines changed

api/cockpit/v1/cockpit_utils.go

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
package cockpit
2+
3+
import (
4+
"time"
5+
6+
"github.com/scaleway/scaleway-sdk-go/errors"
7+
"github.com/scaleway/scaleway-sdk-go/internal/async"
8+
"github.com/scaleway/scaleway-sdk-go/scw"
9+
)
10+
11+
const (
12+
defaultRetryInterval = 5 * time.Second
13+
defaultTimeout = 5 * time.Minute
14+
)
15+
16+
// WaitForAlertRequest is used by WaitForAlert method.
17+
type WaitForAlertRequest struct {
18+
Region scw.Region
19+
AlertID string
20+
Timeout *time.Duration
21+
RetryInterval *time.Duration
22+
}
23+
24+
// WaitForAlert waits for the alert to be in a "terminal state" before returning.
25+
// This function can be used to wait for an alert to be enabled or disabled.
26+
func (s *RegionalAPI) WaitForAlert(req *WaitForAlertRequest, opts ...scw.RequestOption) (*Alert, error) {
27+
timeout := defaultTimeout
28+
if req.Timeout != nil {
29+
timeout = *req.Timeout
30+
}
31+
retryInterval := defaultRetryInterval
32+
if req.RetryInterval != nil {
33+
retryInterval = *req.RetryInterval
34+
}
35+
36+
terminalStatus := map[AlertStatus]struct{}{
37+
AlertStatusEnabled: {},
38+
AlertStatusDisabled: {},
39+
}
40+
41+
alert, err := async.WaitSync(&async.WaitSyncConfig{
42+
Get: func() (any, bool, error) {
43+
// List all alerts and find the one with matching ID
44+
res, err := s.ListAlerts(&RegionalAPIListAlertsRequest{
45+
Region: req.Region,
46+
}, opts...)
47+
if err != nil {
48+
return nil, false, err
49+
}
50+
51+
// Find the alert by ID
52+
for _, alert := range res.Alerts {
53+
if alert.ID == req.AlertID {
54+
_, isTerminal := terminalStatus[alert.RuleStatus]
55+
return alert, isTerminal, nil
56+
}
57+
}
58+
59+
return nil, false, errors.New("alert not found")
60+
},
61+
Timeout: timeout,
62+
IntervalStrategy: async.LinearIntervalStrategy(retryInterval),
63+
})
64+
if err != nil {
65+
return nil, errors.Wrap(err, "waiting for alert failed")
66+
}
67+
return alert.(*Alert), nil
68+
}
69+
70+
// WaitForPreconfiguredAlertsRequest is used by WaitForPreconfiguredAlerts method.
71+
type WaitForPreconfiguredAlertsRequest struct {
72+
Region scw.Region
73+
ProjectID string
74+
PreconfiguredRules []string
75+
TargetStatus AlertStatus
76+
Timeout *time.Duration
77+
RetryInterval *time.Duration
78+
}
79+
80+
// WaitForPreconfiguredAlerts waits for multiple preconfigured alerts to reach a target status.
81+
// This function can be used to wait for alerts to be enabled or disabled after calling
82+
// EnableAlertRules or DisableAlertRules.
83+
func (s *RegionalAPI) WaitForPreconfiguredAlerts(req *WaitForPreconfiguredAlertsRequest, opts ...scw.RequestOption) ([]*Alert, error) {
84+
timeout := defaultTimeout
85+
if req.Timeout != nil {
86+
timeout = *req.Timeout
87+
}
88+
retryInterval := defaultRetryInterval
89+
if req.RetryInterval != nil {
90+
retryInterval = *req.RetryInterval
91+
}
92+
93+
// For enabling/disabling, accept transitional states as terminal
94+
var terminalStatus map[AlertStatus]struct{}
95+
switch req.TargetStatus {
96+
case AlertStatusEnabled:
97+
// Accept both enabled and enabling as terminal states
98+
terminalStatus = map[AlertStatus]struct{}{
99+
AlertStatusEnabled: {},
100+
AlertStatusEnabling: {},
101+
}
102+
case AlertStatusDisabled:
103+
// Accept both disabled and disabling as terminal states
104+
terminalStatus = map[AlertStatus]struct{}{
105+
AlertStatusDisabled: {},
106+
AlertStatusDisabling: {},
107+
}
108+
default:
109+
terminalStatus = map[AlertStatus]struct{}{
110+
req.TargetStatus: {},
111+
}
112+
}
113+
114+
result, err := async.WaitSync(&async.WaitSyncConfig{
115+
Get: func() (any, bool, error) {
116+
// List all preconfigured alerts for the project
117+
res, err := s.ListAlerts(&RegionalAPIListAlertsRequest{
118+
Region: req.Region,
119+
ProjectID: req.ProjectID,
120+
IsPreconfigured: scw.BoolPtr(true),
121+
}, opts...)
122+
if err != nil {
123+
return nil, false, err
124+
}
125+
126+
// Create a map of rule ID to alert
127+
alertsByRuleID := make(map[string]*Alert)
128+
for _, alert := range res.Alerts {
129+
if alert.PreconfiguredData != nil && alert.PreconfiguredData.PreconfiguredRuleID != "" {
130+
alertsByRuleID[alert.PreconfiguredData.PreconfiguredRuleID] = alert
131+
}
132+
}
133+
134+
// Check if all requested alerts are in terminal state
135+
var matchedAlerts []*Alert
136+
for _, ruleID := range req.PreconfiguredRules {
137+
alert, found := alertsByRuleID[ruleID]
138+
if !found {
139+
return nil, false, errors.New("preconfigured alert with rule ID %s not found", ruleID)
140+
}
141+
142+
_, isTerminal := terminalStatus[alert.RuleStatus]
143+
if !isTerminal {
144+
// At least one alert is not in terminal state, continue waiting
145+
return nil, false, nil
146+
}
147+
148+
matchedAlerts = append(matchedAlerts, alert)
149+
}
150+
151+
// All alerts are in terminal state
152+
return matchedAlerts, true, nil
153+
},
154+
Timeout: timeout,
155+
IntervalStrategy: async.LinearIntervalStrategy(retryInterval),
156+
})
157+
if err != nil {
158+
return nil, errors.Wrap(err, "waiting for preconfigured alerts failed")
159+
}
160+
return result.([]*Alert), nil
161+
}
162+

0 commit comments

Comments
 (0)