Skip to content

Commit f0438e8

Browse files
authored
feat(observability): continue attribute for instance -> alert config (#993)
1 parent 07f6c5f commit f0438e8

File tree

6 files changed

+45
-11
lines changed

6 files changed

+45
-11
lines changed

docs/data-sources/observability_instance.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ Read-Only:
145145

146146
Read-Only:
147147

148+
- `continue` (Boolean) Whether an alert should continue matching subsequent sibling nodes.
148149
- `group_by` (List of String) The labels by which incoming alerts are grouped together. For example, multiple alerts coming in for cluster=A and alertname=LatencyHigh would be batched into a single group. To aggregate by all possible labels use the special value '...' as the sole label name, for example: group_by: ['...']. This effectively disables aggregation entirely, passing through all alerts as-is. This is unlikely to be what you want, unless you have a very low alert volume or your upstream notification system performs its own grouping.
149150
- `group_interval` (String) How long to wait before sending a notification about new alerts that are added to a group of alerts for which an initial notification has already been sent. (Usually ~5m or more.)
150151
- `group_wait` (String) How long to initially wait to send a notification for a group of alerts. Allows to wait for an inhibiting alert to arrive or collect more initial alerts for the same group. (Usually ~0s to few minutes.)

docs/resources/observability_instance.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ Required:
157157

158158
Optional:
159159

160+
- `continue` (Boolean) Whether an alert should continue matching subsequent sibling nodes.
160161
- `group_by` (List of String) The labels by which incoming alerts are grouped together. For example, multiple alerts coming in for cluster=A and alertname=LatencyHigh would be batched into a single group. To aggregate by all possible labels use the special value '...' as the sole label name, for example: group_by: ['...']. This effectively disables aggregation entirely, passing through all alerts as-is. This is unlikely to be what you want, unless you have a very low alert volume or your upstream notification system performs its own grouping.
161162
- `group_interval` (String) How long to wait before sending a notification about new alerts that are added to a group of alerts for which an initial notification has already been sent. (Usually ~5m or more.)
162163
- `group_wait` (String) How long to initially wait to send a notification for a group of alerts. Allows to wait for an inhibiting alert to arrive or collect more initial alerts for the same group. (Usually ~0s to few minutes.)

stackit/internal/services/observability/instance/resource.go

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ type alertConfigModel struct {
9090

9191
var alertConfigTypes = map[string]attr.Type{
9292
"receivers": types.ListType{ElemType: types.ObjectType{AttrTypes: receiversTypes}},
93-
"route": types.ObjectType{AttrTypes: routeTypes},
93+
"route": types.ObjectType{AttrTypes: mainRouteTypes},
9494
"global": types.ObjectType{AttrTypes: globalConfigurationTypes},
9595
}
9696

@@ -130,6 +130,7 @@ type mainRouteModel struct {
130130
// Struct corresponding to Model.AlertConfig.route
131131
// This is used to map the routes between the mainRouteModel and the last level of recursion of the routes field
132132
type routeModelMiddle struct {
133+
Continue types.Bool `tfsdk:"continue"`
133134
GroupBy types.List `tfsdk:"group_by"`
134135
GroupInterval types.String `tfsdk:"group_interval"`
135136
GroupWait types.String `tfsdk:"group_wait"`
@@ -146,6 +147,7 @@ type routeModelMiddle struct {
146147
// Struct corresponding to Model.AlertConfig.route but without the recursive routes field
147148
// This is used to map the last level of recursion of the routes field
148149
type routeModelNoRoutes struct {
150+
Continue types.Bool `tfsdk:"continue"`
149151
GroupBy types.List `tfsdk:"group_by"`
150152
GroupInterval types.String `tfsdk:"group_interval"`
151153
GroupWait types.String `tfsdk:"group_wait"`
@@ -158,7 +160,7 @@ type routeModelNoRoutes struct {
158160
RepeatInterval types.String `tfsdk:"repeat_interval"`
159161
}
160162

161-
var routeTypes = map[string]attr.Type{
163+
var mainRouteTypes = map[string]attr.Type{
162164
"group_by": types.ListType{ElemType: types.StringType},
163165
"group_interval": types.StringType,
164166
"group_wait": types.StringType,
@@ -236,6 +238,7 @@ var webHooksConfigsTypes = map[string]attr.Type{
236238
}
237239

238240
var routeDescriptions = map[string]string{
241+
"continue": "Whether an alert should continue matching subsequent sibling nodes.",
239242
"group_by": "The labels by which incoming alerts are grouped together. For example, multiple alerts coming in for cluster=A and alertname=LatencyHigh would be batched into a single group. To aggregate by all possible labels use the special value '...' as the sole label name, for example: group_by: ['...']. This effectively disables aggregation entirely, passing through all alerts as-is. This is unlikely to be what you want, unless you have a very low alert volume or your upstream notification system performs its own grouping.",
240243
"group_interval": "How long to wait before sending a notification about new alerts that are added to a group of alerts for which an initial notification has already been sent. (Usually ~5m or more.)",
241244
"group_wait": "How long to initially wait to send a notification for a group of alerts. Allows to wait for an inhibiting alert to arrive or collect more initial alerts for the same group. (Usually ~0s to few minutes.)",
@@ -258,6 +261,7 @@ func getRouteListType() types.ObjectType {
258261
// The level should be lower or equal to the limit, if higher, the function will produce a stack overflow.
259262
func getRouteListTypeAux(level, limit int) types.ObjectType {
260263
attributeTypes := map[string]attr.Type{
264+
"continue": types.BoolType,
261265
"group_by": types.ListType{ElemType: types.StringType},
262266
"group_interval": types.StringType,
263267
"group_wait": types.StringType,
@@ -290,6 +294,11 @@ func getDatasourceRouteNestedObject() schema.ListNestedAttribute {
290294
// The level should be lower or equal to the limit, if higher, the function will produce a stack overflow.
291295
func getRouteNestedObjectAux(isDatasource bool, level, limit int) schema.ListNestedAttribute {
292296
attributesMap := map[string]schema.Attribute{
297+
"continue": schema.BoolAttribute{
298+
Description: routeDescriptions["continue"],
299+
Optional: !isDatasource,
300+
Computed: isDatasource,
301+
},
293302
"group_by": schema.ListAttribute{
294303
Description: routeDescriptions["group_by"],
295304
Optional: !isDatasource,
@@ -1549,7 +1558,7 @@ func getMockAlertConfig(ctx context.Context) (alertConfigModel, error) {
15491558
return alertConfigModel{}, fmt.Errorf("mapping group by list: %w", core.DiagsToError(diags))
15501559
}
15511560

1552-
mockRoute, diags := types.ObjectValue(routeTypes, map[string]attr.Value{
1561+
mockRoute, diags := types.ObjectValue(mainRouteTypes, map[string]attr.Value{
15531562
"receiver": types.StringValue("email-me"),
15541563
"group_by": mockGroupByList,
15551564
"group_wait": types.StringValue("30s"),
@@ -1759,17 +1768,17 @@ func mapReceiversToAttributes(ctx context.Context, respReceivers *[]observabilit
17591768

17601769
func mapRouteToAttributes(ctx context.Context, route *observability.Route) (attr.Value, error) {
17611770
if route == nil {
1762-
return types.ObjectNull(routeTypes), nil
1771+
return types.ObjectNull(mainRouteTypes), nil
17631772
}
17641773

17651774
groupByModel, diags := types.ListValueFrom(ctx, types.StringType, route.GroupBy)
17661775
if diags.HasError() {
1767-
return types.ObjectNull(routeTypes), fmt.Errorf("mapping group by: %w", core.DiagsToError(diags))
1776+
return types.ObjectNull(mainRouteTypes), fmt.Errorf("mapping group by: %w", core.DiagsToError(diags))
17681777
}
17691778

17701779
childRoutes, err := mapChildRoutesToAttributes(ctx, route.Routes)
17711780
if err != nil {
1772-
return types.ObjectNull(routeTypes), fmt.Errorf("mapping child routes: %w", err)
1781+
return types.ObjectNull(mainRouteTypes), fmt.Errorf("mapping child routes: %w", err)
17731782
}
17741783

17751784
routeMap := map[string]attr.Value{
@@ -1781,9 +1790,9 @@ func mapRouteToAttributes(ctx context.Context, route *observability.Route) (attr
17811790
"routes": childRoutes,
17821791
}
17831792

1784-
routeModel, diags := types.ObjectValue(routeTypes, routeMap)
1793+
routeModel, diags := types.ObjectValue(mainRouteTypes, routeMap)
17851794
if diags.HasError() {
1786-
return types.ObjectNull(routeTypes), fmt.Errorf("converting route to TF types: %w", core.DiagsToError(diags))
1795+
return types.ObjectNull(mainRouteTypes), fmt.Errorf("converting route to TF types: %w", core.DiagsToError(diags))
17871796
}
17881797

17891798
return routeModel, nil
@@ -1822,6 +1831,7 @@ func mapChildRoutesToAttributes(ctx context.Context, routes *[]observability.Rou
18221831
}
18231832

18241833
routeMap := map[string]attr.Value{
1834+
"continue": types.BoolPointerValue(route.Continue),
18251835
"group_by": groupByModel,
18261836
"group_interval": types.StringPointerValue(route.GroupInterval),
18271837
"group_wait": types.StringPointerValue(route.GroupWait),
@@ -2082,6 +2092,7 @@ func toRoutePayload(ctx context.Context, routeTF *mainRouteModel) (*observabilit
20822092
}
20832093
for i := range lastChildRoutes {
20842094
childRoute := routeModelMiddle{
2095+
Continue: lastChildRoutes[i].Continue,
20852096
GroupBy: lastChildRoutes[i].GroupBy,
20862097
GroupInterval: lastChildRoutes[i].GroupInterval,
20872098
GroupWait: lastChildRoutes[i].GroupWait,
@@ -2160,6 +2171,7 @@ func toChildRoutePayload(ctx context.Context, routeTF *routeModelMiddle) (*obser
21602171
}
21612172

21622173
return &observability.UpdateAlertConfigsPayloadRouteRoutesInner{
2174+
Continue: conversion.BoolValueToPointer(routeTF.Continue),
21632175
GroupBy: groupByPayload,
21642176
GroupInterval: conversion.StringValueToPointer(routeTF.GroupInterval),
21652177
GroupWait: conversion.StringValueToPointer(routeTF.GroupWait),

stackit/internal/services/observability/instance/resource_test.go

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func fixtureReceiverModel(emailConfigs, opsGenieConfigs, webHooksConfigs basetyp
6464
}
6565

6666
func fixtureRouteModel() basetypes.ObjectValue {
67-
return types.ObjectValueMust(routeTypes, map[string]attr.Value{
67+
return types.ObjectValueMust(mainRouteTypes, map[string]attr.Value{
6868
"group_by": types.ListValueMust(types.StringType, []attr.Value{
6969
types.StringValue("label1"),
7070
types.StringValue("label2"),
@@ -76,6 +76,7 @@ func fixtureRouteModel() basetypes.ObjectValue {
7676
// "routes": types.ListNull(getRouteListType()),
7777
"routes": types.ListValueMust(getRouteListType(), []attr.Value{
7878
types.ObjectValueMust(getRouteListType().AttrTypes, map[string]attr.Value{
79+
"continue": types.BoolValue(false),
7980
"group_by": types.ListValueMust(types.StringType, []attr.Value{
8081
types.StringValue("label1"),
8182
types.StringValue("label2"),
@@ -96,7 +97,7 @@ func fixtureRouteModel() basetypes.ObjectValue {
9697
}
9798

9899
func fixtureNullRouteModel() basetypes.ObjectValue {
99-
return types.ObjectValueMust(routeTypes, map[string]attr.Value{
100+
return types.ObjectValueMust(mainRouteTypes, map[string]attr.Value{
100101
"group_by": types.ListNull(types.StringType),
101102
"group_interval": types.StringNull(),
102103
"group_wait": types.StringNull(),
@@ -174,13 +175,15 @@ func fixtureReceiverPayload(emailConfigs *[]observability.CreateAlertConfigRecei
174175

175176
func fixtureRoutePayload() *observability.UpdateAlertConfigsPayloadRoute {
176177
return &observability.UpdateAlertConfigsPayloadRoute{
178+
Continue: nil,
177179
GroupBy: utils.Ptr([]string{"label1", "label2"}),
178180
GroupInterval: utils.Ptr("1m"),
179181
GroupWait: utils.Ptr("1m"),
180182
Receiver: utils.Ptr("name"),
181183
RepeatInterval: utils.Ptr("1m"),
182184
Routes: &[]observability.UpdateAlertConfigsPayloadRouteRoutesInner{
183185
{
186+
Continue: utils.Ptr(false),
184187
GroupBy: utils.Ptr([]string{"label1", "label2"}),
185188
GroupInterval: utils.Ptr("1m"),
186189
GroupWait: utils.Ptr("1m"),
@@ -249,6 +252,7 @@ func fixtureWebHooksConfigsResponse() observability.WebHook {
249252

250253
func fixtureRouteResponse() *observability.Route {
251254
return &observability.Route{
255+
Continue: nil,
252256
GroupBy: utils.Ptr([]string{"label1", "label2"}),
253257
GroupInterval: utils.Ptr("1m"),
254258
GroupWait: utils.Ptr("1m"),
@@ -259,6 +263,7 @@ func fixtureRouteResponse() *observability.Route {
259263
RepeatInterval: utils.Ptr("1m"),
260264
Routes: &[]observability.RouteSerializer{
261265
{
266+
Continue: utils.Ptr(false),
262267
GroupBy: utils.Ptr([]string{"label1", "label2"}),
263268
GroupInterval: utils.Ptr("1m"),
264269
GroupWait: utils.Ptr("1m"),
@@ -287,6 +292,11 @@ func fixtureGlobalConfigResponse() *observability.Global {
287292

288293
func fixtureRouteAttributeSchema(route *schema.ListNestedAttribute, isDatasource bool) map[string]schema.Attribute {
289294
attributeMap := map[string]schema.Attribute{
295+
"continue": schema.BoolAttribute{
296+
Description: routeDescriptions["continue"],
297+
Optional: !isDatasource,
298+
Computed: isDatasource,
299+
},
290300
"group_by": schema.ListAttribute{
291301
Description: routeDescriptions["group_by"],
292302
Optional: !isDatasource,
@@ -877,7 +887,7 @@ func TestMapAlertConfigField(t *testing.T) {
877887
fixtureWebHooksConfigsModel(),
878888
),
879889
}),
880-
"route": types.ObjectNull(routeTypes),
890+
"route": types.ObjectNull(mainRouteTypes),
881891
"global": types.ObjectNull(globalConfigurationTypes),
882892
}),
883893
},
@@ -1497,6 +1507,7 @@ func TestGetRouteListTypeAux(t *testing.T) {
14971507
1,
14981508
types.ObjectType{
14991509
AttrTypes: map[string]attr.Type{
1510+
"continue": types.BoolType,
15001511
"group_by": types.ListType{ElemType: types.StringType},
15011512
"group_interval": types.StringType,
15021513
"group_wait": types.StringType,
@@ -1514,6 +1525,7 @@ func TestGetRouteListTypeAux(t *testing.T) {
15141525
2,
15151526
types.ObjectType{
15161527
AttrTypes: map[string]attr.Type{
1528+
"continue": types.BoolType,
15171529
"group_by": types.ListType{ElemType: types.StringType},
15181530
"group_interval": types.StringType,
15191531
"group_wait": types.StringType,
@@ -1523,6 +1535,7 @@ func TestGetRouteListTypeAux(t *testing.T) {
15231535
"receiver": types.StringType,
15241536
"repeat_interval": types.StringType,
15251537
"routes": types.ListType{ElemType: types.ObjectType{AttrTypes: map[string]attr.Type{
1538+
"continue": types.BoolType,
15261539
"group_by": types.ListType{ElemType: types.StringType},
15271540
"group_interval": types.StringType,
15281541
"group_wait": types.StringType,
@@ -1541,6 +1554,7 @@ func TestGetRouteListTypeAux(t *testing.T) {
15411554
2,
15421555
types.ObjectType{
15431556
AttrTypes: map[string]attr.Type{
1557+
"continue": types.BoolType,
15441558
"group_by": types.ListType{ElemType: types.StringType},
15451559
"group_interval": types.StringType,
15461560
"group_wait": types.StringType,

stackit/internal/services/observability/observability_acc_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ var testConfigVarsMax = config.Variables{
104104
"match": config.StringVariable("alert1"),
105105
"match_regex": config.StringVariable("alert1"),
106106
"matchers": config.StringVariable("instance =~ \".*\""),
107+
"continue": config.StringVariable("true"),
107108
// credential
108109
"credential_description": config.StringVariable("This is a description for the test credential."),
109110
// logalertgroup
@@ -536,6 +537,7 @@ func TestAccResourceMax(t *testing.T) {
536537
resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.route.routes.0.group_wait", testutil.ConvertConfigVariable(testConfigVarsMax["group_wait"])),
537538
resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.route.routes.0.receiver", testutil.ConvertConfigVariable(testConfigVarsMax["receiver_name"])),
538539
resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.route.routes.0.repeat_interval", testutil.ConvertConfigVariable(testConfigVarsMax["repeat_interval"])),
540+
resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.route.routes.0.continue", testutil.ConvertConfigVariable(testConfigVarsMax["continue"])),
539541
resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.route.routes.0.match.match1", testutil.ConvertConfigVariable(testConfigVarsMax["match"])),
540542
resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.route.routes.0.match_regex.match_regex1", testutil.ConvertConfigVariable(testConfigVarsMax["match_regex"])),
541543
resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.route.routes.0.matchers.0", testutil.ConvertConfigVariable(testConfigVarsMax["matchers"])),
@@ -702,6 +704,7 @@ func TestAccResourceMax(t *testing.T) {
702704
resource.TestCheckResourceAttr("data.stackit_observability_instance.instance", "alert_config.route.routes.0.group_wait", testutil.ConvertConfigVariable(testConfigVarsMax["group_wait"])),
703705
resource.TestCheckResourceAttr("data.stackit_observability_instance.instance", "alert_config.route.routes.0.receiver", testutil.ConvertConfigVariable(testConfigVarsMax["receiver_name"])),
704706
resource.TestCheckResourceAttr("data.stackit_observability_instance.instance", "alert_config.route.routes.0.repeat_interval", testutil.ConvertConfigVariable(testConfigVarsMax["repeat_interval"])),
707+
resource.TestCheckResourceAttr("data.stackit_observability_instance.instance", "alert_config.route.routes.0.continue", testutil.ConvertConfigVariable(testConfigVarsMax["continue"])),
705708
resource.TestCheckResourceAttr("data.stackit_observability_instance.instance", "alert_config.route.routes.0.match.match1", testutil.ConvertConfigVariable(testConfigVarsMax["match"])),
706709
resource.TestCheckResourceAttr("data.stackit_observability_instance.instance", "alert_config.route.routes.0.match_regex.match_regex1", testutil.ConvertConfigVariable(testConfigVarsMax["match_regex"])),
707710
resource.TestCheckResourceAttr("data.stackit_observability_instance.instance", "alert_config.route.routes.0.matchers.0", testutil.ConvertConfigVariable(testConfigVarsMax["matchers"])),
@@ -928,6 +931,7 @@ func TestAccResourceMax(t *testing.T) {
928931
resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.route.routes.0.group_wait", testutil.ConvertConfigVariable(testConfigVarsMax["group_wait"])),
929932
resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.route.routes.0.receiver", testutil.ConvertConfigVariable(testConfigVarsMax["receiver_name"])),
930933
resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.route.routes.0.repeat_interval", testutil.ConvertConfigVariable(testConfigVarsMax["repeat_interval"])),
934+
resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.route.routes.0.continue", testutil.ConvertConfigVariable(testConfigVarsMax["continue"])),
931935
resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.route.routes.0.match.match1", testutil.ConvertConfigVariable(testConfigVarsMax["match"])),
932936
resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.route.routes.0.match_regex.match_regex1", testutil.ConvertConfigVariable(testConfigVarsMax["match_regex"])),
933937
resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.route.routes.0.matchers.0", testutil.ConvertConfigVariable(configVarsMaxUpdated()["matchers"])),

stackit/internal/services/observability/testdata/resource-max.tf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ variable "smtp_smart_host" {}
4646
variable "match" {}
4747
variable "match_regex" {}
4848
variable "matchers" {}
49+
variable "continue" {}
4950

5051
variable "credential_description" {}
5152

@@ -155,6 +156,7 @@ resource "stackit_observability_instance" "instance" {
155156
group_wait = var.group_wait
156157
receiver = var.receiver_name
157158
repeat_interval = var.repeat_interval
159+
continue = var.continue
158160
match = {
159161
match1 = var.match
160162
}

0 commit comments

Comments
 (0)