Skip to content

Commit e99da3b

Browse files
feat(observability): Add record attribute to alertgroups (#1205)
* feat(observability): Add `record` attribute to `alertgroups` Signed-off-by: Alexander Dahmen <alexander.dahmen@inovex.de> * Update stackit/internal/services/observability/alertgroup/resource.go Co-authored-by: cgoetz-inovex <carlo.goetz@inovex.de> --------- Signed-off-by: Alexander Dahmen <alexander.dahmen@inovex.de> Co-authored-by: cgoetz-inovex <carlo.goetz@inovex.de>
1 parent bbbb3db commit e99da3b

9 files changed

Lines changed: 108 additions & 17 deletions

File tree

docs/data-sources/observability_alertgroup.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,4 @@ Read-Only:
4545
- `expression` (String) The PromQL expression to evaluate. Every evaluation cycle this is evaluated at the current time, and all resultant time series become pending/firing alerts.
4646
- `for` (String) Alerts are considered firing once they have been returned for this long. Alerts which have not yet fired for long enough are considered pending. Default is 0s
4747
- `labels` (Map of String) A map of key:value. Labels to add or overwrite for each alert
48+
- `record` (String) The name of the metric. It's the identifier and must be unique in the group.

docs/resources/observability_alertgroup.md

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,11 @@ resource "stackit_observability_alertgroup" "example" {
3232
}
3333
},
3434
{
35-
alert = "example-alert-name-2"
3635
expression = "kube_node_status_condition{condition=\"Ready\", status=\"false\"} > 0"
37-
for = "1m"
3836
labels = {
3937
severity = "critical"
4038
},
41-
annotations = {
42-
summary : "example summary"
43-
description : "example description"
44-
}
39+
record = "example_record_name"
4540
},
4641
]
4742
}
@@ -76,11 +71,12 @@ import {
7671

7772
Required:
7873

79-
- `alert` (String) The name of the alert rule. Is the identifier and must be unique in the group.
8074
- `expression` (String) The PromQL expression to evaluate. Every evaluation cycle this is evaluated at the current time, and all resultant time series become pending/firing alerts.
8175

8276
Optional:
8377

78+
- `alert` (String) The name of the alert rule. Is the identifier and must be unique in the group.
8479
- `annotations` (Map of String) A map of key:value. Annotations to add or overwrite for each alert
8580
- `for` (String) Alerts are considered firing once they have been returned for this long. Alerts which have not yet fired for long enough are considered pending. Default is 0s
8681
- `labels` (Map of String) A map of key:value. Labels to add or overwrite for each alert
82+
- `record` (String) The name of the metric. It's the identifier and must be unique in the group.

examples/resources/stackit_observability_alertgroup/resource.tf

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,11 @@ resource "stackit_observability_alertgroup" "example" {
1717
}
1818
},
1919
{
20-
alert = "example-alert-name-2"
2120
expression = "kube_node_status_condition{condition=\"Ready\", status=\"false\"} > 0"
22-
for = "1m"
2321
labels = {
2422
severity = "critical"
2523
},
26-
annotations = {
27-
summary : "example summary"
28-
description : "example description"
29-
}
24+
record = "example_record_name"
3025
},
3126
]
3227
}

stackit/internal/services/observability/alertgroup/datasource.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ func (a *alertGroupDataSource) Schema(_ context.Context, _ datasource.SchemaRequ
123123
ElementType: types.StringType,
124124
Computed: true,
125125
},
126+
"record": schema.StringAttribute{
127+
Description: descriptions["record"],
128+
Computed: true,
129+
},
126130
},
127131
},
128132
},

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

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ type rule struct {
5353
Labels types.Map `tfsdk:"labels"`
5454
Expression types.String `tfsdk:"expression"`
5555
For types.String `tfsdk:"for"`
56+
Record types.String `tfsdk:"record"`
5657
}
5758

5859
var ruleTypes = map[string]attr.Type{
@@ -61,6 +62,7 @@ var ruleTypes = map[string]attr.Type{
6162
"labels": basetypes.MapType{ElemType: types.StringType},
6263
"expression": basetypes.StringType{},
6364
"for": basetypes.StringType{},
65+
"record": basetypes.StringType{},
6466
}
6567

6668
// Descriptions for the resource and data source schemas are centralized here.
@@ -75,6 +77,7 @@ var descriptions = map[string]string{
7577
"for": "Alerts are considered firing once they have been returned for this long. Alerts which have not yet fired for long enough are considered pending. Default is 0s",
7678
"labels": "A map of key:value. Labels to add or overwrite for each alert",
7779
"annotations": "A map of key:value. Annotations to add or overwrite for each alert",
80+
"record": "The name of the metric. It's the identifier and must be unique in the group.",
7881
}
7982

8083
// NewAlertGroupResource is a helper function to simplify the provider implementation.
@@ -107,6 +110,40 @@ func (a *alertGroupResource) Configure(ctx context.Context, req resource.Configu
107110
tflog.Info(ctx, "Observability alert group client configured")
108111
}
109112

113+
func (a *alertGroupResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
114+
var resourceModel Model
115+
resp.Diagnostics.Append(req.Config.Get(ctx, &resourceModel)...)
116+
if resp.Diagnostics.HasError() {
117+
return
118+
}
119+
120+
rules := &[]rule{}
121+
diags := resourceModel.Rules.ElementsAs(ctx, rules, false)
122+
resp.Diagnostics.Append(diags...)
123+
if resp.Diagnostics.HasError() {
124+
return
125+
}
126+
127+
// check every rule if the requirements are met, if one fails the whole config fails
128+
rs := *rules
129+
for i := range rs {
130+
// either `alert` or `record` is needed
131+
if rs[i].Alert.IsNull() && rs[i].Record.IsNull() {
132+
core.LogAndAddError(ctx, &resp.Diagnostics, "Error configuring alertgroup", "You need to provide either `alert` or `record` for a `rule`.")
133+
}
134+
135+
// both are set, only one is allowed
136+
if !rs[i].Alert.IsNull() && !rs[i].Record.IsNull() {
137+
core.LogAndAddError(ctx, &resp.Diagnostics, "Error configuring alertgroup", "Both `alert` and `record` were set for a`rule`. Only one is allowed.")
138+
}
139+
140+
// if record is set, `annotations` and `for` are not allowed
141+
if (!rs[i].Record.IsNull() && !rs[i].Record.IsUnknown()) && ((!rs[i].Annotations.IsNull() && !rs[i].Annotations.IsUnknown()) || (!rs[i].For.IsNull() && !rs[i].For.IsUnknown())) {
142+
core.LogAndAddError(ctx, &resp.Diagnostics, "Error configuring alertgroup", "Setting either `annotations` or `for` when using `record` is not allowed.")
143+
}
144+
}
145+
}
146+
110147
// Schema defines the schema for the resource.
111148
func (a *alertGroupResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
112149
resp.Schema = schema.Schema{
@@ -174,7 +211,7 @@ func (a *alertGroupResource) Schema(_ context.Context, _ resource.SchemaRequest,
174211
Attributes: map[string]schema.Attribute{
175212
"alert": schema.StringAttribute{
176213
Description: descriptions["alert"],
177-
Required: true,
214+
Optional: true,
178215
Validators: []validator.String{
179216
stringvalidator.RegexMatches(
180217
regexp.MustCompile(`^[a-zA-Z0-9-]+$`),
@@ -222,6 +259,17 @@ func (a *alertGroupResource) Schema(_ context.Context, _ resource.SchemaRequest,
222259
mapvalidator.SizeAtMost(5),
223260
},
224261
},
262+
"record": schema.StringAttribute{
263+
Description: descriptions["record"],
264+
Optional: true,
265+
Validators: []validator.String{
266+
stringvalidator.RegexMatches(
267+
regexp.MustCompile(`^[a-zA-Z0-9:_]+$`),
268+
"must match expression",
269+
),
270+
stringvalidator.LengthBetween(1, 300),
271+
},
272+
},
225273
},
226274
},
227275
},
@@ -468,6 +516,14 @@ func toRulesPayload(ctx context.Context, model *Model) ([]observability.UpdateAl
468516
oarr.Annotations = &annotations
469517
}
470518

519+
if !utils.IsUndefined(rule.Record) {
520+
record := conversion.StringValueToPointer(rule.Record)
521+
if record == nil {
522+
return nil, fmt.Errorf("found nil record for rule[%d]", i)
523+
}
524+
oarr.Record = record
525+
}
526+
471527
oarrs = append(oarrs, oarr)
472528
}
473529

@@ -539,6 +595,7 @@ func mapRules(_ context.Context, alertGroup *observability.AlertGroup, model *Mo
539595
"for": types.StringPointerValue(r.For),
540596
"labels": types.MapNull(types.StringType),
541597
"annotations": types.MapNull(types.StringType),
598+
"record": types.StringPointerValue(r.Record),
542599
}
543600

544601
if r.Labels != nil {

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ func TestToCreatePayload(t *testing.T) {
7272
"k": types.StringValue("v"),
7373
},
7474
),
75+
"record": types.StringValue("record"),
7576
},
7677
),
7778
},
@@ -91,6 +92,7 @@ func TestToCreatePayload(t *testing.T) {
9192
Labels: &map[string]interface{}{
9293
"k": "v",
9394
},
95+
Record: utils.Ptr("record"),
9496
},
9597
},
9698
},
@@ -153,6 +155,7 @@ func TestToRulesPayload(t *testing.T) {
153155
"annotations": types.MapValueMust(types.StringType, map[string]attr.Value{
154156
"note": types.StringValue("important"),
155157
}),
158+
"record": types.StringValue("record"),
156159
}),
157160
}),
158161
},
@@ -167,6 +170,7 @@ func TestToRulesPayload(t *testing.T) {
167170
Annotations: &map[string]interface{}{
168171
"note": "important",
169172
},
173+
Record: utils.Ptr("record"),
170174
},
171175
},
172176
expectErr: false,
@@ -181,6 +185,7 @@ func TestToRulesPayload(t *testing.T) {
181185
"for": types.StringValue("5s"),
182186
"labels": types.MapNull(types.StringType),
183187
"annotations": types.MapNull(types.StringType),
188+
"record": types.StringValue("record1"),
184189
}),
185190
types.ObjectValueMust(ruleTypes, map[string]attr.Value{
186191
"alert": types.StringValue("alert2"),
@@ -192,14 +197,16 @@ func TestToRulesPayload(t *testing.T) {
192197
"annotations": types.MapValueMust(types.StringType, map[string]attr.Value{
193198
"note": types.StringValue("important"),
194199
}),
200+
"record": types.StringValue("record2"),
195201
}),
196202
}),
197203
},
198204
expect: []observability.UpdateAlertgroupsRequestInnerRulesInner{
199205
{
200-
Alert: utils.Ptr("alert1"),
201-
Expr: utils.Ptr("expr1"),
202-
For: utils.Ptr("5s"),
206+
Alert: utils.Ptr("alert1"),
207+
Expr: utils.Ptr("expr1"),
208+
For: utils.Ptr("5s"),
209+
Record: utils.Ptr("record1"),
203210
},
204211
{
205212
Alert: utils.Ptr("alert2"),
@@ -211,6 +218,7 @@ func TestToRulesPayload(t *testing.T) {
211218
Annotations: &map[string]interface{}{
212219
"note": "important",
213220
},
221+
Record: utils.Ptr("record2"),
214222
},
215223
},
216224
expectErr: false,

stackit/internal/services/observability/observability_acc_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ var testConfigVarsMin = config.Variables{
3939
"alertgroup_name": config.StringVariable(fmt.Sprintf("tf-acc-ag%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum))),
4040
"alert_rule_name": config.StringVariable("alert1"),
4141
"alert_rule_expression": config.StringVariable(alert_rule_expression),
42+
"record_rule_name": config.StringVariable("record1"),
4243
"instance_name": config.StringVariable(fmt.Sprintf("tf-acc-i%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum))),
4344
"plan_name": config.StringVariable("Observability-Medium-EU01"),
4445
"logalertgroup_name": config.StringVariable(fmt.Sprintf("tf-acc-lag%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum))),
@@ -55,6 +56,7 @@ var testConfigVarsMax = config.Variables{
5556
"alertgroup_name": config.StringVariable(fmt.Sprintf("tf-acc-ag%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum))),
5657
"alert_rule_name": config.StringVariable("alert1"),
5758
"alert_rule_expression": config.StringVariable(alert_rule_expression),
59+
"record_rule_name": config.StringVariable("record1"),
5860
"instance_name": config.StringVariable(fmt.Sprintf("tf-acc-i%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum))),
5961
"plan_name": config.StringVariable("Observability-Medium-EU01"),
6062
"logalertgroup_name": config.StringVariable(fmt.Sprintf("tf-acc-lag%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum))),
@@ -215,6 +217,8 @@ func TestAccResourceMin(t *testing.T) {
215217
resource.TestCheckResourceAttr("stackit_observability_alertgroup.alertgroup", "name", testutil.ConvertConfigVariable(testConfigVarsMin["alertgroup_name"])),
216218
resource.TestCheckResourceAttr("stackit_observability_alertgroup.alertgroup", "rules.0.alert", testutil.ConvertConfigVariable(testConfigVarsMin["alert_rule_name"])),
217219
resource.TestCheckResourceAttr("stackit_observability_alertgroup.alertgroup", "rules.0.expression", alert_rule_expression),
220+
resource.TestCheckResourceAttr("stackit_observability_alertgroup.alertgroup", "rules.1.record", testutil.ConvertConfigVariable(testConfigVarsMin["record_rule_name"])),
221+
resource.TestCheckResourceAttr("stackit_observability_alertgroup.alertgroup", "rules.1.expression", alert_rule_expression),
218222

219223
// logalertgroup
220224
resource.TestCheckResourceAttr("stackit_observability_logalertgroup.logalertgroup", "project_id", testutil.ConvertConfigVariable(testConfigVarsMin["project_id"])),
@@ -302,6 +306,8 @@ func TestAccResourceMin(t *testing.T) {
302306
resource.TestCheckResourceAttr("data.stackit_observability_alertgroup.alertgroup", "name", testutil.ConvertConfigVariable(testConfigVarsMin["alertgroup_name"])),
303307
resource.TestCheckResourceAttr("data.stackit_observability_alertgroup.alertgroup", "rules.0.alert", testutil.ConvertConfigVariable(testConfigVarsMin["alert_rule_name"])),
304308
resource.TestCheckResourceAttr("data.stackit_observability_alertgroup.alertgroup", "rules.0.expression", alert_rule_expression),
309+
resource.TestCheckResourceAttr("data.stackit_observability_alertgroup.alertgroup", "rules.1.record", testutil.ConvertConfigVariable(testConfigVarsMin["record_rule_name"])),
310+
resource.TestCheckResourceAttr("data.stackit_observability_alertgroup.alertgroup", "rules.1.expression", alert_rule_expression),
305311

306312
// logalertgroup
307313
resource.TestCheckResourceAttr("data.stackit_observability_logalertgroup.logalertgroup", "project_id", testutil.ConvertConfigVariable(testConfigVarsMin["project_id"])),
@@ -460,6 +466,8 @@ func TestAccResourceMin(t *testing.T) {
460466
resource.TestCheckResourceAttr("stackit_observability_alertgroup.alertgroup", "name", testutil.ConvertConfigVariable(testConfigVarsMin["alertgroup_name"])),
461467
resource.TestCheckResourceAttr("stackit_observability_alertgroup.alertgroup", "rules.0.alert", testutil.ConvertConfigVariable(configVarsMinUpdated()["alert_rule_name"])),
462468
resource.TestCheckResourceAttr("stackit_observability_alertgroup.alertgroup", "rules.0.expression", alert_rule_expression),
469+
resource.TestCheckResourceAttr("stackit_observability_alertgroup.alertgroup", "rules.1.record", testutil.ConvertConfigVariable(configVarsMinUpdated()["record_rule_name"])),
470+
resource.TestCheckResourceAttr("stackit_observability_alertgroup.alertgroup", "rules.1.expression", alert_rule_expression),
463471

464472
// logalertgroup
465473
resource.TestCheckResourceAttr("stackit_observability_logalertgroup.logalertgroup", "project_id", testutil.ConvertConfigVariable(testConfigVarsMin["project_id"])),
@@ -606,6 +614,9 @@ func TestAccResourceMax(t *testing.T) {
606614
resource.TestCheckResourceAttr("stackit_observability_alertgroup.alertgroup", "rules.0.expression", alert_rule_expression),
607615
resource.TestCheckResourceAttr("stackit_observability_alertgroup.alertgroup", "rules.0.for", testutil.ConvertConfigVariable(testConfigVarsMax["alert_for_time"])),
608616
resource.TestCheckResourceAttr("stackit_observability_alertgroup.alertgroup", "rules.0.labels.label1", testutil.ConvertConfigVariable(testConfigVarsMax["alert_label"])),
617+
resource.TestCheckResourceAttr("stackit_observability_alertgroup.alertgroup", "rules.1.record", testutil.ConvertConfigVariable(testConfigVarsMax["record_rule_name"])),
618+
resource.TestCheckResourceAttr("stackit_observability_alertgroup.alertgroup", "rules.1.expression", alert_rule_expression),
619+
resource.TestCheckResourceAttr("stackit_observability_alertgroup.alertgroup", "rules.1.labels.label1", testutil.ConvertConfigVariable(testConfigVarsMax["alert_label"])),
609620

610621
resource.TestCheckResourceAttr("stackit_observability_alertgroup.alertgroup", "rules.0.annotations.annotation1", testutil.ConvertConfigVariable(testConfigVarsMax["alert_annotation"])),
611622
resource.TestCheckResourceAttr("stackit_observability_alertgroup.alertgroup", "interval", testutil.ConvertConfigVariable(testConfigVarsMax["alert_interval"])),
@@ -776,6 +787,9 @@ func TestAccResourceMax(t *testing.T) {
776787
resource.TestCheckResourceAttr("data.stackit_observability_alertgroup.alertgroup", "rules.0.labels.label1", testutil.ConvertConfigVariable(testConfigVarsMax["alert_label"])),
777788
resource.TestCheckResourceAttr("data.stackit_observability_alertgroup.alertgroup", "rules.0.annotations.annotation1", testutil.ConvertConfigVariable(testConfigVarsMax["alert_annotation"])),
778789
resource.TestCheckResourceAttr("data.stackit_observability_alertgroup.alertgroup", "interval", testutil.ConvertConfigVariable(testConfigVarsMax["alert_interval"])),
790+
resource.TestCheckResourceAttr("data.stackit_observability_alertgroup.alertgroup", "rules.1.record", testutil.ConvertConfigVariable(testConfigVarsMax["record_rule_name"])),
791+
resource.TestCheckResourceAttr("data.stackit_observability_alertgroup.alertgroup", "rules.1.expression", alert_rule_expression),
792+
resource.TestCheckResourceAttr("data.stackit_observability_alertgroup.alertgroup", "rules.1.labels.label1", testutil.ConvertConfigVariable(testConfigVarsMax["alert_label"])),
779793

780794
// logalertgroup
781795
resource.TestCheckResourceAttr("stackit_observability_logalertgroup.logalertgroup", "project_id", testutil.ConvertConfigVariable(testConfigVarsMax["project_id"])),
@@ -1006,6 +1020,9 @@ func TestAccResourceMax(t *testing.T) {
10061020
resource.TestCheckResourceAttr("stackit_observability_alertgroup.alertgroup", "rules.0.expression", alert_rule_expression_updated),
10071021
resource.TestCheckResourceAttr("stackit_observability_alertgroup.alertgroup", "rules.0.for", testutil.ConvertConfigVariable(testConfigVarsMax["alert_for_time"])),
10081022
resource.TestCheckResourceAttr("stackit_observability_alertgroup.alertgroup", "rules.0.labels.label1", testutil.ConvertConfigVariable(testConfigVarsMax["alert_label"])),
1023+
resource.TestCheckResourceAttr("stackit_observability_alertgroup.alertgroup", "rules.1.record", testutil.ConvertConfigVariable(testConfigVarsMax["record_rule_name"])),
1024+
resource.TestCheckResourceAttr("stackit_observability_alertgroup.alertgroup", "rules.1.expression", alert_rule_expression_updated),
1025+
resource.TestCheckResourceAttr("stackit_observability_alertgroup.alertgroup", "rules.1.labels.label1", testutil.ConvertConfigVariable(testConfigVarsMax["alert_label"])),
10091026

10101027
resource.TestCheckResourceAttr("stackit_observability_alertgroup.alertgroup", "rules.0.annotations.annotation1", testutil.ConvertConfigVariable(testConfigVarsMax["alert_annotation"])),
10111028
resource.TestCheckResourceAttr("stackit_observability_alertgroup.alertgroup", "interval", testutil.ConvertConfigVariable(configVarsMaxUpdated()["alert_interval"])),

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ variable "alert_for_time" {}
88
variable "alert_label" {}
99
variable "alert_annotation" {}
1010
variable "alert_interval" {}
11+
variable "record_rule_name" {}
1112

1213
variable "instance_name" {}
1314
variable "plan_name" {}
@@ -89,6 +90,13 @@ resource "stackit_observability_alertgroup" "alertgroup" {
8990
annotations = {
9091
annotation1 = var.alert_annotation
9192
}
93+
},
94+
{
95+
record = var.record_rule_name
96+
expression = var.alert_rule_expression
97+
labels = {
98+
label1 = var.alert_label
99+
}
92100
}
93101
]
94102
interval = var.alert_interval

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ variable "project_id" {}
44
variable "alertgroup_name" {}
55
variable "alert_rule_name" {}
66
variable "alert_rule_expression" {}
7+
variable "record_rule_name" {}
78

89
variable "instance_name" {}
910
variable "plan_name" {}
@@ -27,6 +28,10 @@ resource "stackit_observability_alertgroup" "alertgroup" {
2728
{
2829
alert = var.alert_rule_name
2930
expression = var.alert_rule_expression
31+
},
32+
{
33+
record = var.record_rule_name
34+
expression = var.alert_rule_expression
3035
}
3136
]
3237
}

0 commit comments

Comments
 (0)