Skip to content

Commit 4e8e42f

Browse files
authored
fix(rules): temp switch to map[string]string for compat (#2426)
* fix(rules): temp switch to map[string]string for compat * fix: lint
1 parent 2ee3898 commit 4e8e42f

File tree

7 files changed

+144
-360
lines changed

7 files changed

+144
-360
lines changed

docs/resources/apps_rules_alertrule_v0alpha1.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ resource "grafana_apps_rules_alertrule_v0alpha1" "example" {
2929
}
3030
paused = true
3131
expressions = {
32-
"A" = {
32+
"A" = jsonencode({
3333
model = {
3434
datasource = {
3535
type = "prometheus"
@@ -51,8 +51,8 @@ resource "grafana_apps_rules_alertrule_v0alpha1" "example" {
5151
}
5252
query_type = ""
5353
source = true
54-
}
55-
"B" = {
54+
})
55+
"B" = jsonencode({
5656
model = {
5757
conditions = [
5858
{
@@ -86,7 +86,7 @@ resource "grafana_apps_rules_alertrule_v0alpha1" "example" {
8686
datasource_uid = "__expr__"
8787
query_type = ""
8888
source = false
89-
}
89+
})
9090
}
9191
for = "5m"
9292
labels = {
@@ -151,7 +151,7 @@ Optional:
151151
Required:
152152

153153
- `exec_err_state` (String) Describes what state to enter when the rule's query is invalid and the rule cannot be executed. Options are OK, Error, KeepLast, and Alerting.
154-
- `expressions` (Dynamic) A sequence of stages that describe the contents of the rule.
154+
- `expressions` (Map of String) A sequence of stages that describe the contents of the rule. Each value is a JSON string representing an expression object.
155155
- `no_data_state` (String) Describes what state to enter when the rule's query returns No Data. Options are OK, NoData, KeepLast, and Alerting.
156156
- `title` (String) The title of the alert rule.
157157

docs/resources/apps_rules_recordingrule_v0alpha1.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ resource "grafana_apps_rules_recordingrule_v0alpha1" "example" {
3131
}
3232
paused = true
3333
expressions = {
34-
"A" = {
34+
"A" = jsonencode({
3535
model = {
3636
editorMode = "code"
3737
expr = "count(up{})"
@@ -49,7 +49,7 @@ resource "grafana_apps_rules_recordingrule_v0alpha1" "example" {
4949
}
5050
query_type = ""
5151
source = true
52-
}
52+
})
5353
}
5454
target_datasource_uid = "target_ds_uid"
5555
metric = "tf-metric"
@@ -105,7 +105,7 @@ Optional:
105105

106106
Required:
107107

108-
- `expressions` (Dynamic) A sequence of stages that describe the contents of the rule.
108+
- `expressions` (Map of String) A sequence of stages that describe the contents of the rule. Each value is a JSON string representing an expression object.
109109
- `metric` (String) The name of the metric to write to.
110110
- `target_datasource_uid` (String) The UID of the datasource to write the metric to.
111111
- `title` (String) The title of the recording rule.

examples/resources/grafana_apps_rules_alertrule_v0alpha1/resource.tf

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ resource "grafana_apps_rules_alertrule_v0alpha1" "example" {
1414
}
1515
paused = true
1616
expressions = {
17-
"A" = {
17+
"A" = jsonencode({
1818
model = {
1919
datasource = {
2020
type = "prometheus"
@@ -36,8 +36,8 @@ resource "grafana_apps_rules_alertrule_v0alpha1" "example" {
3636
}
3737
query_type = ""
3838
source = true
39-
}
40-
"B" = {
39+
})
40+
"B" = jsonencode({
4141
model = {
4242
conditions = [
4343
{
@@ -71,7 +71,7 @@ resource "grafana_apps_rules_alertrule_v0alpha1" "example" {
7171
datasource_uid = "__expr__"
7272
query_type = ""
7373
source = false
74-
}
74+
})
7575
}
7676
for = "5m"
7777
labels = {

examples/resources/grafana_apps_rules_recordingrule_v0alpha1/resource.tf

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ resource "grafana_apps_rules_recordingrule_v0alpha1" "example" {
1616
}
1717
paused = true
1818
expressions = {
19-
"A" = {
19+
"A" = jsonencode({
2020
model = {
2121
editorMode = "code"
2222
expr = "count(up{})"
@@ -34,7 +34,7 @@ resource "grafana_apps_rules_recordingrule_v0alpha1" "example" {
3434
}
3535
query_type = ""
3636
source = true
37-
}
37+
})
3838
}
3939
target_datasource_uid = "target_ds_uid"
4040
metric = "tf-metric"

internal/resources/appplatform/alertrule_resource.go

Lines changed: 62 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818
var alertRuleSpecType = types.ObjectType{
1919
AttrTypes: map[string]attr.Type{
2020
"title": types.StringType,
21-
"expressions": types.DynamicType,
21+
"expressions": types.MapType{ElemType: types.StringType},
2222
"paused": types.BoolType,
2323
"trigger": ruleTriggerType,
2424
"no_data_state": types.StringType,
@@ -35,7 +35,7 @@ var alertRuleSpecType = types.ObjectType{
3535

3636
type AlertRuleSpecModel struct {
3737
Title types.String `tfsdk:"title"`
38-
Expressions types.Dynamic `tfsdk:"expressions"`
38+
Expressions types.Map `tfsdk:"expressions"`
3939
Paused types.Bool `tfsdk:"paused"`
4040
Trigger types.Object `tfsdk:"trigger"`
4141
NoDataState types.String `tfsdk:"no_data_state"`
@@ -98,12 +98,10 @@ Manages Grafana Alert Rules.
9898
Required: true,
9999
Description: "The title of the alert rule.",
100100
},
101-
"expressions": schema.DynamicAttribute{
101+
"expressions": schema.MapAttribute{
102102
Required: true,
103-
Description: "A sequence of stages that describe the contents of the rule.",
104-
Validators: []validator.Dynamic{
105-
ExpressionsDynamicValidator{},
106-
},
103+
ElementType: types.StringType,
104+
Description: "A sequence of stages that describe the contents of the rule. Each value is a JSON string representing an expression object.",
107105
},
108106
"paused": schema.BoolAttribute{
109107
Optional: true,
@@ -282,18 +280,28 @@ func parseAlertRuleExpressions(ctx context.Context, data *AlertRuleSpecModel, sp
282280
return nil
283281
}
284282

285-
expressionsMap, diags := ParseExpressionsFromDynamic(ctx, data.Expressions)
286-
if diags.HasError() {
287-
return diags
288-
}
289-
283+
// Parse map[string]string where each string is a JSON-encoded expression
290284
spec.Expressions = make(map[string]v0alpha1.AlertRuleExpression)
291-
for ref, obj := range expressionsMap {
292-
exprData, diags := parseAlertRuleExpressionModel(ctx, obj)
293-
if diags.HasError() {
294-
return diags
285+
for ref, val := range data.Expressions.Elements() {
286+
strVal, ok := val.(types.String)
287+
if !ok || strVal.IsNull() || strVal.IsUnknown() {
288+
continue
289+
}
290+
291+
// Parse JSON string to expression data
292+
var exprJSON map[string]interface{}
293+
if err := json.Unmarshal([]byte(strVal.ValueString()), &exprJSON); err != nil {
294+
return diag.Diagnostics{
295+
diag.NewErrorDiagnostic("Failed to parse expression JSON", err.Error()),
296+
}
295297
}
296-
spec.Expressions[ref] = exprData
298+
299+
// Convert JSON to expression object
300+
exprObj, d := convertJSONToAlertRuleExpression(ctx, exprJSON)
301+
if d.HasError() {
302+
return d
303+
}
304+
spec.Expressions[ref] = exprObj
297305
}
298306
return nil
299307
}
@@ -424,25 +432,26 @@ func saveAlertRuleSpec(ctx context.Context, src *v0alpha1.AlertRule, dst *Resour
424432
values["panel_ref"] = types.DynamicNull()
425433
}
426434
if len(src.Spec.Expressions) > 0 {
427-
// Convert expressions to a map of objects for the dynamic type
435+
// Convert expressions to map[string]string where each string is JSON
428436
expressionsMap := make(map[string]attr.Value)
429437
for ref, expr := range src.Spec.Expressions {
430-
// Use the conversion function to parse JSON strings back to HCL objects
431-
exprObj, d := ConvertAPIExpressionToTerraform(ctx, expr, ruleExpressionType.AttrTypes)
432-
if d.HasError() {
433-
return d
438+
// Marshal expression to JSON
439+
jsonBytes, err := json.Marshal(expr)
440+
if err != nil {
441+
return diag.Diagnostics{
442+
diag.NewErrorDiagnostic("Failed to marshal expression to JSON", err.Error()),
443+
}
434444
}
435-
expressionsMap[ref] = exprObj
445+
expressionsMap[ref] = types.StringValue(string(jsonBytes))
436446
}
437-
// Use shared conversion function
438-
dynamicValue, d := ConvertExpressionsMapToDynamic(ctx, expressionsMap)
447+
mapValue, d := types.MapValue(types.StringType, expressionsMap)
439448
if d.HasError() {
440449
return d
441450
}
442-
values["expressions"] = dynamicValue
451+
values["expressions"] = mapValue
443452
} else {
444453
// Set to null if no expressions
445-
values["expressions"] = types.DynamicNull()
454+
values["expressions"] = types.MapNull(types.StringType)
446455
}
447456

448457
spec, d := types.ObjectValue(alertRuleSpecType.AttrTypes, values)
@@ -559,65 +568,40 @@ func parsePanelRef(ctx context.Context, src types.Dynamic) (v0alpha1.AlertRuleV0
559568
}, diag.Diagnostics{}
560569
}
561570

562-
func parseAlertRuleRelativeTimeRange(ctx context.Context, src types.Object) (v0alpha1.AlertRuleRelativeTimeRange, diag.Diagnostics) {
563-
var data RelativeTimeRangeModel
564-
if diag := src.As(ctx, &data, basetypes.ObjectAsOptions{
565-
UnhandledNullAsEmpty: true,
566-
UnhandledUnknownAsEmpty: true,
567-
}); diag.HasError() {
568-
return v0alpha1.AlertRuleRelativeTimeRange{}, diag
569-
}
570-
571-
return v0alpha1.AlertRuleRelativeTimeRange{
572-
From: v0alpha1.AlertRulePromDurationWMillis(data.From.ValueString()),
573-
To: v0alpha1.AlertRulePromDurationWMillis(data.To.ValueString()),
574-
}, diag.Diagnostics{}
575-
}
576-
577-
func parseAlertRuleExpressionModel(ctx context.Context, src types.Object) (v0alpha1.AlertRuleExpression, diag.Diagnostics) {
578-
var srcExpr RuleExpressionModel
579-
if diag := src.As(ctx, &srcExpr, basetypes.ObjectAsOptions{
580-
UnhandledNullAsEmpty: true,
581-
UnhandledUnknownAsEmpty: true,
582-
}); diag.HasError() {
583-
return v0alpha1.AlertRuleExpression{}, diag
584-
}
585-
571+
// convertJSONToAlertRuleExpression converts a JSON map to an AlertRuleExpression
572+
func convertJSONToAlertRuleExpression(ctx context.Context, exprJSON map[string]interface{}) (v0alpha1.AlertRuleExpression, diag.Diagnostics) {
586573
dstExpr := v0alpha1.AlertRuleExpression{}
587574

588-
// Model should be a map/object for the API, not a JSON string
589-
// Parse the JSON string back to a map
590-
if !srcExpr.Model.IsNull() && !srcExpr.Model.IsUnknown() {
591-
modelStr := srcExpr.Model.ValueString()
592-
var modelMap map[string]interface{}
593-
if err := json.Unmarshal([]byte(modelStr), &modelMap); err != nil {
594-
return v0alpha1.AlertRuleExpression{}, diag.Diagnostics{
595-
diag.NewErrorDiagnostic("Failed to parse model JSON", err.Error()),
596-
}
597-
}
598-
dstExpr.Model = modelMap
575+
// Extract model
576+
if model, ok := exprJSON["model"].(map[string]interface{}); ok {
577+
dstExpr.Model = model
599578
}
600579

601-
// Handle relative time range if present
602-
if !srcExpr.RelativeTimeRange.IsNull() && !srcExpr.RelativeTimeRange.IsUnknown() {
603-
relativeTimeRange, diags := parseAlertRuleRelativeTimeRange(ctx, srcExpr.RelativeTimeRange)
604-
if diags.HasError() {
605-
return v0alpha1.AlertRuleExpression{}, diags
606-
}
607-
dstExpr.RelativeTimeRange = &v0alpha1.AlertRuleRelativeTimeRange{
608-
From: relativeTimeRange.From,
609-
To: relativeTimeRange.To,
610-
}
580+
// Extract query_type
581+
if queryType, ok := exprJSON["query_type"].(string); ok && queryType != "" {
582+
dstExpr.QueryType = util.Ptr(queryType)
611583
}
612584

613-
if srcExpr.QueryType.ValueString() != "" {
614-
dstExpr.QueryType = util.Ptr(srcExpr.QueryType.ValueString())
585+
// Extract datasource_uid
586+
if datasourceUID, ok := exprJSON["datasource_uid"].(string); ok && datasourceUID != "" {
587+
dstExpr.DatasourceUID = util.Ptr(v0alpha1.AlertRuleDatasourceUID(datasourceUID))
615588
}
616-
if srcExpr.DatasourceUID.ValueString() != "" {
617-
dstExpr.DatasourceUID = util.Ptr(v0alpha1.AlertRuleDatasourceUID(srcExpr.DatasourceUID.ValueString()))
589+
590+
// Extract source
591+
if source, ok := exprJSON["source"].(bool); ok {
592+
dstExpr.Source = util.Ptr(source)
618593
}
619-
if !srcExpr.Source.IsNull() && !srcExpr.Source.IsUnknown() {
620-
dstExpr.Source = util.Ptr(srcExpr.Source.ValueBool())
594+
595+
// Extract relative_time_range
596+
if relTimeRange, ok := exprJSON["relative_time_range"].(map[string]interface{}); ok {
597+
from, _ := relTimeRange["from"].(string)
598+
to, _ := relTimeRange["to"].(string)
599+
if from != "" || to != "" {
600+
dstExpr.RelativeTimeRange = &v0alpha1.AlertRuleRelativeTimeRange{
601+
From: v0alpha1.AlertRulePromDurationWMillis(from),
602+
To: v0alpha1.AlertRulePromDurationWMillis(to),
603+
}
604+
}
621605
}
622606

623607
return dstExpr, diag.Diagnostics{}

0 commit comments

Comments
 (0)