Skip to content

Commit 6a06083

Browse files
authored
Alerting: Fix contact point update and diff logic (#2305)
* Fix contact point update and diff logic This includes two fixes: - Fixed an issue where notifiers without required fields (WeCom, Webex, Slack) were incorrectly deleted during updates. - Fixed a state drift issue where, if all notifiers of a given type existed in state but not in the API, the missing notifiers were not synced to state during refresh and did not show up in diff.
1 parent badd506 commit 6a06083

File tree

4 files changed

+55
-11
lines changed

4 files changed

+55
-11
lines changed

docs/resources/contact_point.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -492,14 +492,17 @@ Read-Only:
492492
<a id="nestedblock--webex"></a>
493493
### Nested Schema for `webex`
494494

495+
Required:
496+
497+
- `room_id` (String) ID of the Webex Teams room where to send the messages.
498+
- `token` (String, Sensitive) The bearer token used to authorize the client.
499+
495500
Optional:
496501

497502
- `api_url` (String) The URL to send webhook requests to.
498503
- `disable_resolve_message` (Boolean) Whether to disable sending resolve messages. Defaults to `false`.
499504
- `message` (String) The templated title of the message to send.
500-
- `room_id` (String) ID of the Webex Teams room where to send the messages.
501505
- `settings` (Map of String, Sensitive) Additional custom properties to attach to the notifier. Defaults to `map[]`.
502-
- `token` (String, Sensitive) The bearer token used to authorize the client.
503506

504507
Read-Only:
505508

internal/resources/grafana/resource_alerting_contact_point.go

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -274,11 +274,8 @@ func unpackContactPoints(data *schema.ResourceData) []statePair {
274274
deleted = true
275275
processedUIDs[uid.(string)] = true
276276
}
277-
for fieldName, fieldSchema := range n.schema().Schema {
278-
if !fieldSchema.Computed && fieldSchema.Required && !reflect.ValueOf(pointMap[fieldName]).IsZero() {
279-
deleted = false
280-
break
281-
}
277+
if nonEmptyNotifier(n, pointMap) {
278+
deleted = false
282279
}
283280

284281
// Add the point/receiver to the result
@@ -306,6 +303,25 @@ func unpackContactPoints(data *schema.ResourceData) []statePair {
306303
return result
307304
}
308305

306+
type HasData interface {
307+
HasData(data map[string]any) bool
308+
}
309+
310+
func nonEmptyNotifier(n notifier, data map[string]any) bool {
311+
if customEmpty, ok := n.(HasData); ok {
312+
return customEmpty.HasData(data)
313+
}
314+
for fieldName, fieldSchema := range n.schema().Schema {
315+
// We only check required fields to determine if the point is zeroed. This is because some optional fields,
316+
// such as nested schema.Set can be troublesome to check for zero values. Notifiers that lack required fields
317+
// (ex. wecom) can define a custom IsEmpty method to handle this.
318+
if !fieldSchema.Computed && fieldSchema.Required && !reflect.ValueOf(data[fieldName]).IsZero() {
319+
return true
320+
}
321+
}
322+
return false
323+
}
324+
309325
func unpackPointConfig(n notifier, data interface{}, name string) *models.EmbeddedContactPoint {
310326
pt := n.unpack(data, name)
311327
settings := pt.Settings.(map[string]interface{})
@@ -340,8 +356,8 @@ func packContactPoints(ps []*models.EmbeddedContactPoint, data *schema.ResourceD
340356
}
341357
data.Set("disable_provenance", disableProvenance)
342358

343-
for n, pts := range pointsPerNotifier {
344-
data.Set(n.meta().field, pts)
359+
for _, n := range notifiers {
360+
data.Set(n.meta().field, pointsPerNotifier[n])
345361
}
346362

347363
return nil

internal/resources/grafana/resource_alerting_contact_point_notifiers.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1447,6 +1447,12 @@ type slackNotifier struct{}
14471447

14481448
var _ notifier = (*slackNotifier)(nil)
14491449

1450+
func (s slackNotifier) HasData(data map[string]any) bool {
1451+
// Slack has no simple required fields as they require mutual exclusivity. We rely on `Required` to test for
1452+
// deletions on update, so instead we define a custom HasData method.
1453+
return data["url"] != "" || data["token"] != ""
1454+
}
1455+
14501456
func (s slackNotifier) meta() notifierMeta {
14511457
return notifierMeta{
14521458
field: "slack",
@@ -2050,7 +2056,7 @@ func (w webexNotifier) schema() *schema.Resource {
20502056
r := commonNotifierResource()
20512057
r.Schema["token"] = &schema.Schema{
20522058
Type: schema.TypeString,
2053-
Optional: true,
2059+
Required: true,
20542060
Sensitive: true,
20552061
Description: "The bearer token used to authorize the client.",
20562062
}
@@ -2066,7 +2072,7 @@ func (w webexNotifier) schema() *schema.Resource {
20662072
}
20672073
r.Schema["room_id"] = &schema.Schema{
20682074
Type: schema.TypeString,
2069-
Optional: true,
2075+
Required: true,
20702076
Description: "ID of the Webex Teams room where to send the messages.",
20712077
}
20722078
return r
@@ -2250,6 +2256,12 @@ type wecomNotifier struct{}
22502256

22512257
var _ notifier = (*wecomNotifier)(nil)
22522258

2259+
func (w wecomNotifier) HasData(data map[string]any) bool {
2260+
// WeCom has no simple required fields as they require mutual exclusivity. We rely on `Required` to test for
2261+
// deletions on update, so instead we define a custom HasData method.
2262+
return data["url"] != "" || data["secret"] != ""
2263+
}
2264+
22532265
func (w wecomNotifier) meta() notifierMeta {
22542266
return notifierMeta{
22552267
field: "wecom",

internal/resources/grafana/resource_alerting_contact_point_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,19 @@ func TestAccContactPoint_notifiers(t *testing.T) {
320320
resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "wecom.0.to_user", "to_user"),
321321
),
322322
},
323+
// The following test ensures that the plan remains empty for notifiers when updates are made to
324+
// other notifiers in the contact point. This is a regression test to catch certain notifiers without
325+
// required fields being deleted on update.
326+
{
327+
Config: testutils.TestAccExampleWithReplace(t, "resources/grafana_contact_point/_acc_receiver_types.tf", map[string]string{
328+
`victor-ops-url`: `updated-victor-ops-url`, // VictorOps is a "safe" update as it's not being tested.
329+
}),
330+
ExpectNonEmptyPlan: false,
331+
Check: resource.ComposeTestCheckFunc(
332+
resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "victorops.#", "1"),
333+
resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "victorops.0.url", "http://updated-victor-ops-url"),
334+
),
335+
},
323336
// Test blank fields in settings should be omitted.
324337
{
325338
Config: testutils.TestAccExample(t, "resources/grafana_contact_point/_acc_default_settings.tf"),

0 commit comments

Comments
 (0)