fix(observability): remove invalid fields match and match_regex from main route in alert_config (#915)

* fix: remove invalid `match` and `match_regex` from main route in alert_config
- deprecated `match` and `match_regex` in child routes
- add new `matchers` field
This commit is contained in:
Marcel Jacek 2025-09-12 09:58:50 +02:00 committed by GitHub
parent 433efa001c
commit 65c6106ea9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 157 additions and 111 deletions

View file

@ -133,8 +133,6 @@ Read-Only:
- `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.
- `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.)
- `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.) .
- `match` (Map of String) A set of equality matchers an alert has to fulfill to match the node.
- `match_regex` (Map of String) A set of regex-matchers an alert has to fulfill to match the node.
- `receiver` (String) The name of the receiver to route the alerts to.
- `repeat_interval` (String) How long to wait before sending a notification again if it has already been sent successfully for an alert. (Usually ~3h or more).
- `routes` (Attributes List) List of child routes. (see [below for nested schema](#nestedatt--alert_config--route--routes))
@ -147,7 +145,8 @@ Read-Only:
- `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.
- `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.)
- `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.)
- `match` (Map of String) A set of equality matchers an alert has to fulfill to match the node.
- `match_regex` (Map of String) A set of regex-matchers an alert has to fulfill to match the node.
- `match` (Map of String, Deprecated) A set of equality matchers an alert has to fulfill to match the node. This field is deprecated and will be removed after 10th March 2026, use `matchers` in the `routes` instead
- `match_regex` (Map of String, Deprecated) A set of regex-matchers an alert has to fulfill to match the node. This field is deprecated and will be removed after 10th March 2026, use `matchers` in the `routes` instead
- `matchers` (List of String) A list of matchers that an alert has to fulfill to match the node. A matcher is a string with a syntax inspired by PromQL and OpenMetrics.
- `receiver` (String) The name of the receiver to route the alerts to.
- `repeat_interval` (String) How long to wait before sending a notification again if it has already been sent successfully for an alert. (Usually ~3h or more).

View file

@ -142,8 +142,6 @@ Optional:
- `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.
- `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.)
- `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.)
- `match` (Map of String) A set of equality matchers an alert has to fulfill to match the node.
- `match_regex` (Map of String) A set of regex-matchers an alert has to fulfill to match the node.
- `repeat_interval` (String) How long to wait before sending a notification again if it has already been sent successfully for an alert. (Usually ~3h or more).
- `routes` (Attributes List) List of child routes. (see [below for nested schema](#nestedatt--alert_config--route--routes))
@ -159,8 +157,9 @@ Optional:
- `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.
- `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.)
- `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.)
- `match` (Map of String) A set of equality matchers an alert has to fulfill to match the node.
- `match_regex` (Map of String) A set of regex-matchers an alert has to fulfill to match the node.
- `match` (Map of String, Deprecated) A set of equality matchers an alert has to fulfill to match the node. This field is deprecated and will be removed after 10th March 2026, use `matchers` in the `routes` instead
- `match_regex` (Map of String, Deprecated) A set of regex-matchers an alert has to fulfill to match the node. This field is deprecated and will be removed after 10th March 2026, use `matchers` in the `routes` instead
- `matchers` (List of String) A list of matchers that an alert has to fulfill to match the node. A matcher is a string with a syntax inspired by PromQL and OpenMetrics.
- `repeat_interval` (String) How long to wait before sending a notification again if it has already been sent successfully for an alert. (Usually ~3h or more).

View file

@ -296,16 +296,6 @@ func (d *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques
Description: "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.) .",
Computed: true,
},
"match": schema.MapAttribute{
Description: "A set of equality matchers an alert has to fulfill to match the node.",
Computed: true,
ElementType: types.StringType,
},
"match_regex": schema.MapAttribute{
Description: "A set of regex-matchers an alert has to fulfill to match the node.",
Computed: true,
ElementType: types.StringType,
},
"receiver": schema.StringAttribute{
Description: "The name of the receiver to route the alerts to.",
Computed: true,

View file

@ -40,7 +40,7 @@ import (
)
// Currently, due to incorrect types in the API, the maximum recursion level for child routes is set to 1.
// Once this is fixed, the value should be set to 10.
// Once this is fixed, the value should be set to 10 and toRoutePayload needs to be adjusted, to support it.
const childRouteMaxRecursionLevel = 1
// Ensure the implementation satisfies the expected interfaces.
@ -118,12 +118,26 @@ var globalConfigurationTypes = map[string]attr.Type{
}
// Struct corresponding to Model.AlertConfig.route
type routeModel struct {
type mainRouteModel struct {
GroupBy types.List `tfsdk:"group_by"`
GroupInterval types.String `tfsdk:"group_interval"`
GroupWait types.String `tfsdk:"group_wait"`
Match types.Map `tfsdk:"match"`
Receiver types.String `tfsdk:"receiver"`
RepeatInterval types.String `tfsdk:"repeat_interval"`
Routes types.List `tfsdk:"routes"`
}
// Struct corresponding to Model.AlertConfig.route
// This is used to map the routes between the mainRouteModel and the last level of recursion of the routes field
type routeModelMiddle struct {
GroupBy types.List `tfsdk:"group_by"`
GroupInterval types.String `tfsdk:"group_interval"`
GroupWait types.String `tfsdk:"group_wait"`
// Deprecated: Match is deprecated and will be removed after 10th March 2026. Use Matchers instead
Match types.Map `tfsdk:"match"`
// Deprecated: MatchRegex is deprecated and will be removed after 10th March 2026. Use Matchers instead
MatchRegex types.Map `tfsdk:"match_regex"`
Matchers types.List `tfsdk:"matchers"`
Receiver types.String `tfsdk:"receiver"`
RepeatInterval types.String `tfsdk:"repeat_interval"`
Routes types.List `tfsdk:"routes"`
@ -132,11 +146,14 @@ type routeModel struct {
// Struct corresponding to Model.AlertConfig.route but without the recursive routes field
// This is used to map the last level of recursion of the routes field
type routeModelNoRoutes struct {
GroupBy types.List `tfsdk:"group_by"`
GroupInterval types.String `tfsdk:"group_interval"`
GroupWait types.String `tfsdk:"group_wait"`
Match types.Map `tfsdk:"match"`
GroupBy types.List `tfsdk:"group_by"`
GroupInterval types.String `tfsdk:"group_interval"`
GroupWait types.String `tfsdk:"group_wait"`
// Deprecated: Match is deprecated and will be removed after 10th March 2026. Use Matchers instead
Match types.Map `tfsdk:"match"`
// Deprecated: MatchRegex is deprecated and will be removed after 10th March 2026. Use Matchers instead
MatchRegex types.Map `tfsdk:"match_regex"`
Matchers types.List `tfsdk:"matchers"`
Receiver types.String `tfsdk:"receiver"`
RepeatInterval types.String `tfsdk:"repeat_interval"`
}
@ -145,8 +162,6 @@ var routeTypes = map[string]attr.Type{
"group_by": types.ListType{ElemType: types.StringType},
"group_interval": types.StringType,
"group_wait": types.StringType,
"match": types.MapType{ElemType: types.StringType},
"match_regex": types.MapType{ElemType: types.StringType},
"receiver": types.StringType,
"repeat_interval": types.StringType,
"routes": types.ListType{ElemType: getRouteListType()},
@ -218,8 +233,9 @@ var routeDescriptions = map[string]string{
"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.",
"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.)",
"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.)",
"match": "A set of equality matchers an alert has to fulfill to match the node.",
"match_regex": "A set of regex-matchers an alert has to fulfill to match the node.",
"match": "A set of equality matchers an alert has to fulfill to match the node. This field is deprecated and will be removed after 10th March 2026, use `matchers` in the `routes` instead",
"match_regex": "A set of regex-matchers an alert has to fulfill to match the node. This field is deprecated and will be removed after 10th March 2026, use `matchers` in the `routes` instead",
"matchers": "A list of matchers that an alert has to fulfill to match the node. A matcher is a string with a syntax inspired by PromQL and OpenMetrics.",
"receiver": "The name of the receiver to route the alerts to.",
"repeat_interval": "How long to wait before sending a notification again if it has already been sent successfully for an alert. (Usually ~3h or more).",
"routes": "List of child routes.",
@ -241,6 +257,7 @@ func getRouteListTypeAux(level, limit int) types.ObjectType {
"group_wait": types.StringType,
"match": types.MapType{ElemType: types.StringType},
"match_regex": types.MapType{ElemType: types.StringType},
"matchers": types.ListType{ElemType: types.StringType},
"receiver": types.StringType,
"repeat_interval": types.StringType,
}
@ -290,13 +307,21 @@ func getRouteNestedObjectAux(isDatasource bool, level, limit int) schema.ListNes
},
},
"match": schema.MapAttribute{
Description: routeDescriptions["match"],
Optional: !isDatasource,
Computed: isDatasource,
ElementType: types.StringType,
Description: routeDescriptions["match"],
DeprecationMessage: "Use `matchers` in the `routes` instead.",
Optional: !isDatasource,
Computed: isDatasource,
ElementType: types.StringType,
},
"match_regex": schema.MapAttribute{
Description: routeDescriptions["match_regex"],
Description: routeDescriptions["match_regex"],
DeprecationMessage: "Use `matchers` in the `routes` instead.",
Optional: !isDatasource,
Computed: isDatasource,
ElementType: types.StringType,
},
"matchers": schema.ListAttribute{
Description: routeDescriptions["matchers"],
Optional: !isDatasource,
Computed: isDatasource,
ElementType: types.StringType,
@ -701,16 +726,6 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r
stringplanmodifier.UseStateForUnknown(),
},
},
"match": schema.MapAttribute{
Description: routeDescriptions["match"],
Optional: true,
ElementType: types.StringType,
},
"match_regex": schema.MapAttribute{
Description: routeDescriptions["match_regex"],
Optional: true,
ElementType: types.StringType,
},
"receiver": schema.StringAttribute{
Description: routeDescriptions["receiver"],
Required: true,
@ -1521,8 +1536,6 @@ func getMockAlertConfig(ctx context.Context) (alertConfigModel, error) {
"group_wait": types.StringValue("30s"),
"group_interval": types.StringValue("5m"),
"repeat_interval": types.StringValue("4h"),
"match": types.MapNull(types.StringType),
"match_regex": types.MapNull(types.StringType),
"routes": types.ListNull(getRouteListType()),
})
if diags.HasError() {
@ -1735,16 +1748,6 @@ func mapRouteToAttributes(ctx context.Context, route *observability.Route) (attr
return types.ObjectNull(routeTypes), fmt.Errorf("mapping group by: %w", core.DiagsToError(diags))
}
matchModel, diags := types.MapValueFrom(ctx, types.StringType, route.Match)
if diags.HasError() {
return types.ObjectNull(routeTypes), fmt.Errorf("mapping match: %w", core.DiagsToError(diags))
}
matchRegexModel, diags := types.MapValueFrom(ctx, types.StringType, route.MatchRe)
if diags.HasError() {
return types.ObjectNull(routeTypes), fmt.Errorf("mapping match regex: %w", core.DiagsToError(diags))
}
childRoutes, err := mapChildRoutesToAttributes(ctx, route.Routes)
if err != nil {
return types.ObjectNull(routeTypes), fmt.Errorf("mapping child routes: %w", err)
@ -1754,8 +1757,6 @@ func mapRouteToAttributes(ctx context.Context, route *observability.Route) (attr
"group_by": groupByModel,
"group_interval": types.StringPointerValue(route.GroupInterval),
"group_wait": types.StringPointerValue(route.GroupWait),
"match": matchModel,
"match_regex": matchRegexModel,
"receiver": types.StringPointerValue(route.Receiver),
"repeat_interval": types.StringPointerValue(route.RepeatInterval),
"routes": childRoutes,
@ -1796,12 +1797,18 @@ func mapChildRoutesToAttributes(ctx context.Context, routes *[]observability.Rou
return nullList, fmt.Errorf("mapping match regex: %w", core.DiagsToError(diags))
}
matchersModel, diags := types.ListValueFrom(ctx, types.StringType, route.Matchers)
if diags.HasError() {
return nullList, fmt.Errorf("mapping matchers: %w", core.DiagsToError(diags))
}
routeMap := map[string]attr.Value{
"group_by": groupByModel,
"group_interval": types.StringPointerValue(route.GroupInterval),
"group_wait": types.StringPointerValue(route.GroupWait),
"match": matchModel,
"match_regex": matchRegexModel,
"matchers": matchersModel,
"receiver": types.StringPointerValue(route.Receiver),
"repeat_interval": types.StringPointerValue(route.RepeatInterval),
}
@ -1921,7 +1928,7 @@ func toUpdateAlertConfigPayload(ctx context.Context, model *alertConfigModel) (*
return nil, fmt.Errorf("mapping receivers: %w", err)
}
routeTF := routeModel{}
routeTF := mainRouteModel{}
diags := model.Route.As(ctx, &routeTF, basetypes.ObjectAsOptions{})
if diags.HasError() {
return nil, fmt.Errorf("mapping route: %w", core.DiagsToError(diags))
@ -2023,14 +2030,12 @@ func toReceiverPayload(ctx context.Context, model *alertConfigModel) (*[]observa
return &receivers, nil
}
func toRoutePayload(ctx context.Context, routeTF *routeModel) (*observability.UpdateAlertConfigsPayloadRoute, error) {
func toRoutePayload(ctx context.Context, routeTF *mainRouteModel) (*observability.UpdateAlertConfigsPayloadRoute, error) {
if routeTF == nil {
return nil, fmt.Errorf("nil route model")
}
var groupByPayload *[]string
var matchPayload *map[string]interface{}
var matchRegexPayload *map[string]interface{}
var childRoutesPayload *[]observability.UpdateAlertConfigsPayloadRouteRoutesInner
if !routeTF.GroupBy.IsNull() && !routeTF.GroupBy.IsUnknown() {
@ -2041,24 +2046,8 @@ func toRoutePayload(ctx context.Context, routeTF *routeModel) (*observability.Up
}
}
if !routeTF.Match.IsNull() && !routeTF.Match.IsUnknown() {
matchMap, err := conversion.ToStringInterfaceMap(ctx, routeTF.Match)
if err != nil {
return nil, fmt.Errorf("mapping match: %w", err)
}
matchPayload = &matchMap
}
if !routeTF.MatchRegex.IsNull() && !routeTF.MatchRegex.IsUnknown() {
matchRegexMap, err := conversion.ToStringInterfaceMap(ctx, routeTF.MatchRegex)
if err != nil {
return nil, fmt.Errorf("mapping match regex: %w", err)
}
matchRegexPayload = &matchRegexMap
}
if !routeTF.Routes.IsNull() && !routeTF.Routes.IsUnknown() {
childRoutes := []routeModel{}
childRoutes := []routeModelMiddle{}
diags := routeTF.Routes.ElementsAs(ctx, &childRoutes, false)
if diags.HasError() {
// If there is an error, we will try to map the child routes as if they are the last child routes
@ -2070,12 +2059,13 @@ func toRoutePayload(ctx context.Context, routeTF *routeModel) (*observability.Up
return nil, fmt.Errorf("mapping child routes: %w", core.DiagsToError(diags))
}
for i := range lastChildRoutes {
childRoute := routeModel{
childRoute := routeModelMiddle{
GroupBy: lastChildRoutes[i].GroupBy,
GroupInterval: lastChildRoutes[i].GroupInterval,
GroupWait: lastChildRoutes[i].GroupWait,
Match: lastChildRoutes[i].Match,
MatchRegex: lastChildRoutes[i].MatchRegex,
Matchers: lastChildRoutes[i].Matchers,
Receiver: lastChildRoutes[i].Receiver,
RepeatInterval: lastChildRoutes[i].RepeatInterval,
Routes: types.ListNull(getRouteListType()),
@ -2087,11 +2077,11 @@ func toRoutePayload(ctx context.Context, routeTF *routeModel) (*observability.Up
childRoutesList := []observability.UpdateAlertConfigsPayloadRouteRoutesInner{}
for i := range childRoutes {
childRoute := childRoutes[i]
childRoutePayload, err := toRoutePayload(ctx, &childRoute)
childRoutePayload, err := toChildRoutePayload(ctx, &childRoute)
if err != nil {
return nil, fmt.Errorf("mapping child route: %w", err)
}
childRoutesList = append(childRoutesList, *toChildRoutePayload(childRoutePayload))
childRoutesList = append(childRoutesList, *childRoutePayload)
}
childRoutesPayload = &childRoutesList
@ -2101,28 +2091,63 @@ func toRoutePayload(ctx context.Context, routeTF *routeModel) (*observability.Up
GroupBy: groupByPayload,
GroupInterval: conversion.StringValueToPointer(routeTF.GroupInterval),
GroupWait: conversion.StringValueToPointer(routeTF.GroupWait),
Match: matchPayload,
MatchRe: matchRegexPayload,
Receiver: conversion.StringValueToPointer(routeTF.Receiver),
RepeatInterval: conversion.StringValueToPointer(routeTF.RepeatInterval),
Routes: childRoutesPayload,
}, nil
}
func toChildRoutePayload(in *observability.UpdateAlertConfigsPayloadRoute) *observability.UpdateAlertConfigsPayloadRouteRoutesInner {
if in == nil {
return nil
func toChildRoutePayload(ctx context.Context, routeTF *routeModelMiddle) (*observability.UpdateAlertConfigsPayloadRouteRoutesInner, error) {
if routeTF == nil {
return nil, fmt.Errorf("nil route model")
}
var groupByPayload, matchersPayload *[]string
var matchPayload, matchRegexPayload *map[string]interface{}
if !utils.IsUndefined(routeTF.GroupBy) {
groupByPayload = &[]string{}
diags := routeTF.GroupBy.ElementsAs(ctx, groupByPayload, false)
if diags.HasError() {
return nil, fmt.Errorf("mapping group by: %w", core.DiagsToError(diags))
}
}
if !utils.IsUndefined(routeTF.Match) {
matchMap, err := conversion.ToStringInterfaceMap(ctx, routeTF.Match)
if err != nil {
return nil, fmt.Errorf("mapping match: %w", err)
}
matchPayload = &matchMap
}
if !utils.IsUndefined(routeTF.MatchRegex) {
matchRegexMap, err := conversion.ToStringInterfaceMap(ctx, routeTF.MatchRegex)
if err != nil {
return nil, fmt.Errorf("mapping match regex: %w", err)
}
matchRegexPayload = &matchRegexMap
}
if !utils.IsUndefined(routeTF.Matchers) {
matchersList, err := conversion.StringListToPointer(routeTF.Matchers)
if err != nil {
return nil, fmt.Errorf("mapping match regex: %w", err)
}
matchersPayload = matchersList
}
return &observability.UpdateAlertConfigsPayloadRouteRoutesInner{
GroupBy: in.GroupBy,
GroupInterval: in.GroupInterval,
GroupWait: in.GroupWait,
Match: in.Match,
MatchRe: in.MatchRe,
Receiver: in.Receiver,
RepeatInterval: in.RepeatInterval,
GroupBy: groupByPayload,
GroupInterval: conversion.StringValueToPointer(routeTF.GroupInterval),
GroupWait: conversion.StringValueToPointer(routeTF.GroupWait),
Match: matchPayload,
MatchRe: matchRegexPayload,
Matchers: matchersPayload,
Receiver: conversion.StringValueToPointer(routeTF.Receiver),
RepeatInterval: conversion.StringValueToPointer(routeTF.RepeatInterval),
// Routes not currently supported
}
}, nil
}
func toGlobalConfigPayload(ctx context.Context, model *alertConfigModel) (*observability.UpdateAlertConfigsPayloadGlobal, error) {

View file

@ -68,8 +68,6 @@ func fixtureRouteModel() basetypes.ObjectValue {
}),
"group_interval": types.StringValue("1m"),
"group_wait": types.StringValue("1m"),
"match": types.MapValueMust(types.StringType, map[string]attr.Value{"key": types.StringValue("value")}),
"match_regex": types.MapValueMust(types.StringType, map[string]attr.Value{"key": types.StringValue("value")}),
"receiver": types.StringValue("name"),
"repeat_interval": types.StringValue("1m"),
// "routes": types.ListNull(getRouteListType()),
@ -79,10 +77,14 @@ func fixtureRouteModel() basetypes.ObjectValue {
types.StringValue("label1"),
types.StringValue("label2"),
}),
"group_interval": types.StringValue("1m"),
"group_wait": types.StringValue("1m"),
"match": types.MapValueMust(types.StringType, map[string]attr.Value{"key": types.StringValue("value")}),
"match_regex": types.MapValueMust(types.StringType, map[string]attr.Value{"key": types.StringValue("value")}),
"group_interval": types.StringValue("1m"),
"group_wait": types.StringValue("1m"),
"match": types.MapValueMust(types.StringType, map[string]attr.Value{"key": types.StringValue("value")}),
"match_regex": types.MapValueMust(types.StringType, map[string]attr.Value{"key": types.StringValue("value")}),
"matchers": types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("matcher1"),
types.StringValue("matcher2"),
}),
"receiver": types.StringValue("name"),
"repeat_interval": types.StringValue("1m"),
}),
@ -95,8 +97,6 @@ func fixtureNullRouteModel() basetypes.ObjectValue {
"group_by": types.ListNull(types.StringType),
"group_interval": types.StringNull(),
"group_wait": types.StringNull(),
"match": types.MapNull(types.StringType),
"match_regex": types.MapNull(types.StringType),
"receiver": types.StringNull(),
"repeat_interval": types.StringNull(),
"routes": types.ListNull(getRouteListType()),
@ -171,8 +171,6 @@ func fixtureRoutePayload() *observability.UpdateAlertConfigsPayloadRoute {
GroupBy: utils.Ptr([]string{"label1", "label2"}),
GroupInterval: utils.Ptr("1m"),
GroupWait: utils.Ptr("1m"),
Match: &map[string]interface{}{"key": "value"},
MatchRe: &map[string]interface{}{"key": "value"},
Receiver: utils.Ptr("name"),
RepeatInterval: utils.Ptr("1m"),
Routes: &[]observability.UpdateAlertConfigsPayloadRouteRoutesInner{
@ -182,6 +180,7 @@ func fixtureRoutePayload() *observability.UpdateAlertConfigsPayloadRoute {
GroupWait: utils.Ptr("1m"),
Match: &map[string]interface{}{"key": "value"},
MatchRe: &map[string]interface{}{"key": "value"},
Matchers: &[]string{"matcher1", "matcher2"},
Receiver: utils.Ptr("name"),
RepeatInterval: utils.Ptr("1m"),
},
@ -246,6 +245,7 @@ func fixtureRouteResponse() *observability.Route {
GroupWait: utils.Ptr("1m"),
Match: &map[string]string{"key": "value"},
MatchRe: &map[string]string{"key": "value"},
Matchers: &[]string{"matcher1", "matcher2"},
Receiver: utils.Ptr("name"),
RepeatInterval: utils.Ptr("1m"),
Routes: &[]observability.RouteSerializer{
@ -255,6 +255,7 @@ func fixtureRouteResponse() *observability.Route {
GroupWait: utils.Ptr("1m"),
Match: &map[string]string{"key": "value"},
MatchRe: &map[string]string{"key": "value"},
Matchers: &[]string{"matcher1", "matcher2"},
Receiver: utils.Ptr("name"),
RepeatInterval: utils.Ptr("1m"),
},
@ -300,13 +301,21 @@ func fixtureRouteAttributeSchema(route *schema.ListNestedAttribute, isDatasource
},
},
"match": schema.MapAttribute{
Description: routeDescriptions["match"],
Optional: !isDatasource,
Computed: isDatasource,
ElementType: types.StringType,
Description: routeDescriptions["match"],
DeprecationMessage: "Use `matchers` in the `routes` instead.",
Optional: !isDatasource,
Computed: isDatasource,
ElementType: types.StringType,
},
"match_regex": schema.MapAttribute{
Description: routeDescriptions["match_regex"],
Description: routeDescriptions["match_regex"],
DeprecationMessage: "Use `matchers` in the `routes` instead.",
Optional: !isDatasource,
Computed: isDatasource,
ElementType: types.StringType,
},
"matchers": schema.ListAttribute{
Description: routeDescriptions["matchers"],
Optional: !isDatasource,
Computed: isDatasource,
ElementType: types.StringType,
@ -1484,6 +1493,7 @@ func TestGetRouteListTypeAux(t *testing.T) {
"group_wait": types.StringType,
"match": types.MapType{ElemType: types.StringType},
"match_regex": types.MapType{ElemType: types.StringType},
"matchers": types.ListType{ElemType: types.StringType},
"receiver": types.StringType,
"repeat_interval": types.StringType,
},
@ -1500,6 +1510,7 @@ func TestGetRouteListTypeAux(t *testing.T) {
"group_wait": types.StringType,
"match": types.MapType{ElemType: types.StringType},
"match_regex": types.MapType{ElemType: types.StringType},
"matchers": types.ListType{ElemType: types.StringType},
"receiver": types.StringType,
"repeat_interval": types.StringType,
"routes": types.ListType{ElemType: types.ObjectType{AttrTypes: map[string]attr.Type{
@ -1508,6 +1519,7 @@ func TestGetRouteListTypeAux(t *testing.T) {
"group_wait": types.StringType,
"match": types.MapType{ElemType: types.StringType},
"match_regex": types.MapType{ElemType: types.StringType},
"matchers": types.ListType{ElemType: types.StringType},
"receiver": types.StringType,
"repeat_interval": types.StringType,
}}},
@ -1525,6 +1537,7 @@ func TestGetRouteListTypeAux(t *testing.T) {
"group_wait": types.StringType,
"match": types.MapType{ElemType: types.StringType},
"match_regex": types.MapType{ElemType: types.StringType},
"matchers": types.ListType{ElemType: types.StringType},
"receiver": types.StringType,
"repeat_interval": types.StringType,
},

View file

@ -100,6 +100,7 @@ var testConfigVarsMax = config.Variables{
"smtp_smart_host": config.StringVariable("smtp.gmail.com:587"),
"match": config.StringVariable("alert1"),
"match_regex": config.StringVariable("alert1"),
"matchers": config.StringVariable("instance =~ \".*\""),
// logalertgroup
"logalertgroup_for_time": config.StringVariable("60s"),
"logalertgroup_label": config.StringVariable("label1"),
@ -134,6 +135,7 @@ func configVarsMaxUpdated() config.Variables {
tempConfig["webhook_configs_url"] = config.StringVariable("https://chat.googleapis.com/api")
tempConfig["ms_teams"] = config.StringVariable("false")
tempConfig["google_chat"] = config.StringVariable("true")
tempConfig["matchers"] = config.StringVariable("instance =~ \"my.*\"")
return tempConfig
}
@ -529,6 +531,8 @@ func TestAccResourceMax(t *testing.T) {
resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.route.routes.0.repeat_interval", testutil.ConvertConfigVariable(testConfigVarsMax["repeat_interval"])),
resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.route.routes.0.match.match1", testutil.ConvertConfigVariable(testConfigVarsMax["match"])),
resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.route.routes.0.match_regex.match_regex1", testutil.ConvertConfigVariable(testConfigVarsMax["match_regex"])),
resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.route.routes.0.matchers.0", testutil.ConvertConfigVariable(testConfigVarsMax["matchers"])),
resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.route.routes.0.matchers.#", "1"),
resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.global.opsgenie_api_key", testutil.ConvertConfigVariable(testConfigVarsMax["opsgenie_api_key"])),
resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.global.opsgenie_api_url", testutil.ConvertConfigVariable(testConfigVarsMax["opsgenie_api_url"])),
@ -692,6 +696,8 @@ func TestAccResourceMax(t *testing.T) {
resource.TestCheckResourceAttr("data.stackit_observability_instance.instance", "alert_config.route.routes.0.repeat_interval", testutil.ConvertConfigVariable(testConfigVarsMax["repeat_interval"])),
resource.TestCheckResourceAttr("data.stackit_observability_instance.instance", "alert_config.route.routes.0.match.match1", testutil.ConvertConfigVariable(testConfigVarsMax["match"])),
resource.TestCheckResourceAttr("data.stackit_observability_instance.instance", "alert_config.route.routes.0.match_regex.match_regex1", testutil.ConvertConfigVariable(testConfigVarsMax["match_regex"])),
resource.TestCheckResourceAttr("data.stackit_observability_instance.instance", "alert_config.route.routes.0.matchers.0", testutil.ConvertConfigVariable(testConfigVarsMax["matchers"])),
resource.TestCheckResourceAttr("data.stackit_observability_instance.instance", "alert_config.route.routes.0.matchers.#", "1"),
resource.TestCheckResourceAttr("data.stackit_observability_instance.instance", "alert_config.global.opsgenie_api_key", testutil.ConvertConfigVariable(testConfigVarsMax["opsgenie_api_key"])),
resource.TestCheckResourceAttr("data.stackit_observability_instance.instance", "alert_config.global.opsgenie_api_url", testutil.ConvertConfigVariable(testConfigVarsMax["opsgenie_api_url"])),
@ -916,6 +922,8 @@ func TestAccResourceMax(t *testing.T) {
resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.route.routes.0.repeat_interval", testutil.ConvertConfigVariable(testConfigVarsMax["repeat_interval"])),
resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.route.routes.0.match.match1", testutil.ConvertConfigVariable(testConfigVarsMax["match"])),
resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.route.routes.0.match_regex.match_regex1", testutil.ConvertConfigVariable(testConfigVarsMax["match_regex"])),
resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.route.routes.0.matchers.0", testutil.ConvertConfigVariable(configVarsMaxUpdated()["matchers"])),
resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.route.routes.0.matchers.#", "1"),
resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.global.opsgenie_api_key", testutil.ConvertConfigVariable(testConfigVarsMax["opsgenie_api_key"])),
resource.TestCheckResourceAttr("stackit_observability_instance.instance", "alert_config.global.opsgenie_api_url", testutil.ConvertConfigVariable(testConfigVarsMax["opsgenie_api_url"])),

View file

@ -42,6 +42,7 @@ variable "smtp_from" {}
variable "smtp_smart_host" {}
variable "match" {}
variable "match_regex" {}
variable "matchers" {}
variable "logalertgroup_name" {}
variable "logalertgroup_alert" {}
@ -151,6 +152,9 @@ resource "stackit_observability_instance" "instance" {
match_regex = {
match_regex1 = var.match_regex
}
matchers = [
var.matchers
]
}
]
},

View file

@ -556,7 +556,10 @@ func ConvertConfigVariable(variable config.Variable) string {
tmpByteArray, _ := variable.MarshalJSON()
// In case the variable is a string, the quotes should be removed
if tmpByteArray[0] == '"' && tmpByteArray[len(tmpByteArray)-1] == '"' {
return string(tmpByteArray[1 : len(tmpByteArray)-1])
result := string(tmpByteArray[1 : len(tmpByteArray)-1])
// Replace escaped quotes which where added MarshalJSON
rawString := strings.ReplaceAll(result, `\"`, `"`)
return rawString
}
return string(tmpByteArray)
}

View file

@ -32,6 +32,11 @@ func TestConvertConfigVariable(t *testing.T) {
variable: config.IntegerVariable(10),
want: "10",
},
{
name: "quoted string",
variable: config.StringVariable(`instance =~ ".*"`),
want: `instance =~ ".*"`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {