From 12469dcbafd014a5f284f30f541ba3b3600d7649 Mon Sep 17 00:00:00 2001 From: Pawel Date: Sat, 15 Oct 2022 02:10:53 -0700 Subject: [PATCH 1/4] added notifications e2e tests --- client/notification.go | 36 ++++- rollbar/resource_notification.go | 16 +-- rollbar/resource_notification_test.go | 186 ++++++++++++++++++++++++++ 3 files changed, 228 insertions(+), 10 deletions(-) create mode 100644 rollbar/resource_notification_test.go diff --git a/client/notification.go b/client/notification.go index 111b0915..f98b59be 100644 --- a/client/notification.go +++ b/client/notification.go @@ -23,9 +23,10 @@ package client import ( - "github.com/rs/zerolog/log" "strconv" "strings" + + "github.com/rs/zerolog/log" ) type Notification struct { @@ -165,6 +166,39 @@ func (c *RollbarAPIClient) DeleteNotification(notificationID int, channel string return nil } +func (c *RollbarAPIClient) ListNotifications(channel string) ([]Notification, error) { + u := c.BaseURL + pathNotificationCreate + + l := log.With(). + Logger() + l.Debug().Msg("Reading notifications from API") + + resp, err := c.Resty.R(). + SetResult(notificationsResponse{}). + SetError(ErrorResult{}). + SetPathParams(map[string]string{ + "channel": channel, + }). + Get(u) + + if err != nil { + l.Err(err).Msg(resp.Status()) + return nil, err + } + err = errorFromResponse(resp) + if err != nil { + l.Err(err).Send() + return nil, err + } + nr := resp.Result().(*notificationsResponse) + if nr.Err != 0 { + l.Warn().Msg("Notification not found") + return nil, ErrNotFound + } + l.Debug().Msg("Notification successfully read") + return nr.Result, nil +} + type notificationResponse struct { Err int `json:"err"` Result Notification `json:"result"` diff --git a/rollbar/resource_notification.go b/rollbar/resource_notification.go index 64a57bdc..7cda739d 100644 --- a/rollbar/resource_notification.go +++ b/rollbar/resource_notification.go @@ -36,7 +36,8 @@ import ( var configMap = map[string][]string{"email": {"users", "teams"}, "slack": {"message_template", "channel", "show_message_buttons"}, - "pagerduty": {"service_key"}} + "pagerduty": {"service_key"}, + "webhook": {"url", "format"}} func CustomNotificationImport(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { splitID := strings.Split(d.Id(), ComplexImportSeparator) @@ -281,14 +282,11 @@ func resourceNotificationUpdate(ctx context.Context, d *schema.ResourceData, m i func flattenConfig(config map[string]interface{}) *schema.Set { var out = make([]interface{}, 0) - m := make(map[string]interface{}) - for key, value := range config { - m[key] = value - out = append(out, m) - } + out = append(out, config) specResource := resourceNotification().Schema["config"].Elem.(*schema.Resource) f := schema.HashResource(specResource) - return schema.NewSet(f, out) + g := schema.NewSet(f, out) + return g } func flattenRule(filters []interface{}, trigger string) *schema.Set { @@ -317,10 +315,10 @@ func flattenRule(filters []interface{}, trigger string) *schema.Set { m["filters"] = filters out = append(out, m) m["trigger"] = trigger - out = append(out, m) specResource := resourceNotification().Schema["rule"].Elem.(*schema.Resource) f := schema.HashResource(specResource) - return schema.NewSet(f, out) + g := schema.NewSet(f, out) + return g } func resourceNotificationRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { diff --git a/rollbar/resource_notification_test.go b/rollbar/resource_notification_test.go new file mode 100644 index 00000000..822d1c42 --- /dev/null +++ b/rollbar/resource_notification_test.go @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2022 Rollbar, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package rollbar + +import ( + "os" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/rollbar/terraform-provider-rollbar/client" + "github.com/rs/zerolog/log" +) + +func init() { + resource.AddTestSweepers("rollbar_notification", &resource.Sweeper{ + Name: "rollbar_notification", + F: sweepResourceNotification, + }) +} + +// TestNotificationCreate tests creating a project access token. +func (s *AccSuite) TestNotificationCreate() { + notificationResourceName := "rollbar_notification.webhook_notification" + // language=hcl + config := ` + resource "rollbar_notification" "webhook_notification" { + rule { + filters { + type = "environment" + operation = "eq" + value = "production" + } + filters { + type = "framework" + operation = "eq" + value = 13 + } + trigger = "new_item" + } + channel = "webhook" + config { + url = "https://www.rollbar.com" + format = "json" + } +} + ` + resource.ParallelTest(s.T(), resource.TestCase{ + PreCheck: func() { s.preCheck() }, + Providers: s.providers, + CheckDestroy: nil, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + s.checkResourceStateSanity(notificationResourceName), + resource.TestCheckResourceAttr(notificationResourceName, "channel", "webhook"), + resource.TestCheckResourceAttr(notificationResourceName, "rule.0.filters.0.type", "environment"), + resource.TestCheckResourceAttr(notificationResourceName, "rule.0.filters.1.type", "framework"), + resource.TestCheckResourceAttr(notificationResourceName, "rule.0.trigger", "new_item"), + resource.TestCheckResourceAttr(notificationResourceName, "config.0.url", "https://www.rollbar.com"), + resource.TestCheckResourceAttr(notificationResourceName, "config.0.format", "json"), + ), + }, + }, + }) +} + +// TestNotificationCreate tests creating a project access token. +func (s *AccSuite) TestNotificationUpdate() { + notificationResourceName := "rollbar_notification.webhook_notification" + // language=hcl + config1 := ` + resource "rollbar_notification" "webhook_notification" { + rule { + filters { + type = "environment" + operation = "eq" + value = "production" + } + filters { + type = "framework" + operation = "eq" + value = 13 + } + trigger = "new_item" + } + channel = "webhook" + config { + url = "https://www.rollbar.com" + format = "json" + } +} + ` + config2 := ` + resource "rollbar_notification" "webhook_notification" { + rule { + filters { + type = "environment" + operation = "eq" + value = "production" + } + filters { + type = "framework" + operation = "eq" + value = 13 + } + trigger = "new_item" + } + channel = "webhook" + config { + url = "https://www.rollbar.com" + format = "xml" + } +} + ` + resource.ParallelTest(s.T(), resource.TestCase{ + PreCheck: func() { s.preCheck() }, + Providers: s.providers, + CheckDestroy: nil, + Steps: []resource.TestStep{ + { + Config: config1, + Check: resource.ComposeTestCheckFunc( + s.checkResourceStateSanity(notificationResourceName), + resource.TestCheckResourceAttr(notificationResourceName, "channel", "webhook"), + resource.TestCheckResourceAttr(notificationResourceName, "rule.0.filters.0.type", "environment"), + resource.TestCheckResourceAttr(notificationResourceName, "rule.0.filters.1.type", "framework"), + resource.TestCheckResourceAttr(notificationResourceName, "rule.0.trigger", "new_item"), + resource.TestCheckResourceAttr(notificationResourceName, "config.0.url", "https://www.rollbar.com"), + resource.TestCheckResourceAttr(notificationResourceName, "config.0.format", "json"), + ), + }, + { + Config: config2, + Check: resource.ComposeTestCheckFunc( + s.checkResourceStateSanity(notificationResourceName), + resource.TestCheckResourceAttr(notificationResourceName, "channel", "webhook"), + resource.TestCheckResourceAttr(notificationResourceName, "rule.0.filters.0.type", "environment"), + resource.TestCheckResourceAttr(notificationResourceName, "rule.0.filters.1.type", "framework"), + resource.TestCheckResourceAttr(notificationResourceName, "rule.0.trigger", "new_item"), + resource.TestCheckResourceAttr(notificationResourceName, "config.0.url", "https://www.rollbar.com"), + resource.TestCheckResourceAttr(notificationResourceName, "config.0.format", "xml"), + ), + }, + }, + }) +} + +// sweepResourceUser cleans up orphaned Rollbar users. +func sweepResourceNotification(_ string) error { + log.Info().Msg("Cleaning up Rollbar users from acceptance test runs.") + + c := client.NewClient(client.DefaultBaseURL, os.Getenv("ROLLBAR_PROJECT_API_KEY")) + notifications, err := c.ListNotifications("webhook") + if err != nil { + log.Err(err).Send() + return err + } + for _, n := range notifications { + err = c.DeleteProject(n.ID) + if err != nil { + log.Err(err).Send() + return err + } + } + return nil +} From 765b7ccbafb72e647a09f7c59b27e9a50bfcd345 Mon Sep 17 00:00:00 2001 From: Pawel Date: Sun, 16 Oct 2022 22:35:54 -0700 Subject: [PATCH 2/4] added tests --- client/service_link.go | 37 ++++++- rollbar/resource_integration_test.go | 101 +++++++++++++++++++ rollbar/resource_notification_test.go | 10 +- rollbar/resource_service_link_test.go | 133 ++++++++++++++++++++++++++ 4 files changed, 275 insertions(+), 6 deletions(-) create mode 100644 rollbar/resource_integration_test.go create mode 100644 rollbar/resource_service_link_test.go diff --git a/client/service_link.go b/client/service_link.go index ebb96e36..a356e03e 100644 --- a/client/service_link.go +++ b/client/service_link.go @@ -23,8 +23,9 @@ package client import ( - "github.com/rs/zerolog/log" "strconv" + + "github.com/rs/zerolog/log" ) type ServiceLink struct { @@ -158,7 +159,41 @@ func (c *RollbarAPIClient) DeleteServiceLink(id int) error { return nil } +func (c *RollbarAPIClient) ListSerivceLinks() ([]ServiceLink, error) { + u := c.BaseURL + pathServiceLinkCreate + + l := log.With(). + Logger() + l.Debug().Msg("Reading service links from API") + + resp, err := c.Resty.R(). + SetResult(serviceLinksResponse{}). + SetError(ErrorResult{}). + Get(u) + + if err != nil { + l.Err(err).Msg(resp.Status()) + return nil, err + } + err = errorFromResponse(resp) + if err != nil { + l.Err(err).Send() + return nil, err + } + sl := resp.Result().(*serviceLinksResponse) + if sl.Err != 0 { + l.Warn().Msg("Service link not found") + return nil, ErrNotFound + } + l.Debug().Msg("Service link successfully read") + return sl.Result, nil +} + type serviceLinkResponse struct { Err int `json:"err"` Result ServiceLink `json:"result"` } +type serviceLinksResponse struct { + Err int `json:"err"` + Result []ServiceLink `json:"result"` +} diff --git a/rollbar/resource_integration_test.go b/rollbar/resource_integration_test.go new file mode 100644 index 00000000..c64ff27d --- /dev/null +++ b/rollbar/resource_integration_test.go @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2022 Rollbar, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package rollbar + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +// TestIntegrationCreate tests creating an integration +func (s *AccSuite) TestIntegrationCreate() { + integrationResourceName := "rollbar_integration.webhook_integration" + // language=hcl + config := ` + resource "rollbar_integration" "webhook_integration" { + webhook { + enabled = true + url = "https://www.rollbar.com" + } +} + ` + resource.ParallelTest(s.T(), resource.TestCase{ + PreCheck: func() { s.preCheck() }, + Providers: s.providers, + CheckDestroy: nil, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + s.checkResourceStateSanity(integrationResourceName), + resource.TestCheckResourceAttr(integrationResourceName, "webhook.0.url", "https://www.rollbar.com"), + resource.TestCheckResourceAttr(integrationResourceName, "webhook.0.enabled", "true"), + ), + }, + }, + }) +} + +// TestIntegrationUpdate tests updating an integration +func (s *AccSuite) TestIntegrationUpdate() { + integrationResourceName := "rollbar_integration.webhook_integration" + // language=hcl + config1 := ` + resource "rollbar_integration" "webhook_integration" { + webhook { + enabled = true + url = "https://www.rollbar.com" + } +} + ` + config2 := ` + resource "rollbar_integration" "webhook_integration" { + webhook { + enabled = false + url = "https://www.rollbar.com" + } +} + ` + resource.ParallelTest(s.T(), resource.TestCase{ + PreCheck: func() { s.preCheck() }, + Providers: s.providers, + CheckDestroy: nil, + Steps: []resource.TestStep{ + { + Config: config1, + Check: resource.ComposeTestCheckFunc( + s.checkResourceStateSanity(integrationResourceName), + resource.TestCheckResourceAttr(integrationResourceName, "webhook.0.url", "https://www.rollbar.com"), + resource.TestCheckResourceAttr(integrationResourceName, "webhook.0.enabled", "true"), + ), + }, + { + Config: config2, + Check: resource.ComposeTestCheckFunc( + s.checkResourceStateSanity(integrationResourceName), + resource.TestCheckResourceAttr(integrationResourceName, "webhook.0.url", "https://www.rollbar.com"), + resource.TestCheckResourceAttr(integrationResourceName, "webhook.0.enabled", "false"), + ), + }, + }, + }) +} diff --git a/rollbar/resource_notification_test.go b/rollbar/resource_notification_test.go index 822d1c42..562985e8 100644 --- a/rollbar/resource_notification_test.go +++ b/rollbar/resource_notification_test.go @@ -37,7 +37,7 @@ func init() { }) } -// TestNotificationCreate tests creating a project access token. +// TestNotificationCreate tests creating a notification func (s *AccSuite) TestNotificationCreate() { notificationResourceName := "rollbar_notification.webhook_notification" // language=hcl @@ -84,7 +84,7 @@ func (s *AccSuite) TestNotificationCreate() { }) } -// TestNotificationCreate tests creating a project access token. +// TestNotificationUpdate tests updating a notification func (s *AccSuite) TestNotificationUpdate() { notificationResourceName := "rollbar_notification.webhook_notification" // language=hcl @@ -165,9 +165,9 @@ func (s *AccSuite) TestNotificationUpdate() { }) } -// sweepResourceUser cleans up orphaned Rollbar users. +// sweepResourceNotification cleans up notifications func sweepResourceNotification(_ string) error { - log.Info().Msg("Cleaning up Rollbar users from acceptance test runs.") + log.Info().Msg("Cleaning up Rollbar notifications from acceptance test runs.") c := client.NewClient(client.DefaultBaseURL, os.Getenv("ROLLBAR_PROJECT_API_KEY")) notifications, err := c.ListNotifications("webhook") @@ -176,7 +176,7 @@ func sweepResourceNotification(_ string) error { return err } for _, n := range notifications { - err = c.DeleteProject(n.ID) + err = c.DeleteNotification(n.ID, "webhook") if err != nil { log.Err(err).Send() return err diff --git a/rollbar/resource_service_link_test.go b/rollbar/resource_service_link_test.go new file mode 100644 index 00000000..e6b298b2 --- /dev/null +++ b/rollbar/resource_service_link_test.go @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2022 Rollbar, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package rollbar + +import ( + "fmt" + "os" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/rollbar/terraform-provider-rollbar/client" + "github.com/rs/zerolog/log" +) + +func init() { + resource.AddTestSweepers("rollbar_service_link", &resource.Sweeper{ + Name: "rollbar_service_link", + F: sweepResourceNotification, + }) +} + +// TestServiceLinkCreate tests creating a service link +func (s *AccSuite) TestServiceLinkCreate() { + serviceLinkResourceName := "rollbar_service_link.service_link" + // language=hcl + tmpl := ` + resource "rollbar_service_link" "service_link" { + name = "%s" + template = "sometemplate_new.{{ss}}" + } + ` + + config := fmt.Sprintf(tmpl, s.randName) + resource.ParallelTest(s.T(), resource.TestCase{ + PreCheck: func() { s.preCheck() }, + Providers: s.providers, + CheckDestroy: nil, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + s.checkResourceStateSanity(serviceLinkResourceName), + resource.TestCheckResourceAttr(serviceLinkResourceName, "name", s.randName), + resource.TestCheckResourceAttr(serviceLinkResourceName, "template", "sometemplate_new.{{ss}}"), + ), + }, + }, + }) +} + +// TestServiceLinkUpdate tests updating a service link +func (s *AccSuite) TestServiceLinkUpdate() { + serviceLinkResourceName := "rollbar_service_link.service_link" + // language=hcl + tmpl1 := ` + resource "rollbar_service_link" "service_link" { + name = "%s" + template = "sometemplate.{{ss}}" + } + ` + config1 := fmt.Sprintf(tmpl1, s.randName) + tmpl2 := ` + resource "rollbar_service_link" "service_link" { + name = "%s" + template = "sometemplate_new.{{ss}}" + } + ` + config2 := fmt.Sprintf(tmpl2, s.randName) + resource.ParallelTest(s.T(), resource.TestCase{ + PreCheck: func() { s.preCheck() }, + Providers: s.providers, + CheckDestroy: nil, + Steps: []resource.TestStep{ + { + Config: config1, + Check: resource.ComposeTestCheckFunc( + s.checkResourceStateSanity(serviceLinkResourceName), + resource.TestCheckResourceAttr(serviceLinkResourceName, "name", s.randName), + resource.TestCheckResourceAttr(serviceLinkResourceName, "template", "sometemplate.{{ss}}"), + ), + }, + { + Config: config2, + Check: resource.ComposeTestCheckFunc( + s.checkResourceStateSanity(serviceLinkResourceName), + resource.TestCheckResourceAttr(serviceLinkResourceName, "name", s.randName), + resource.TestCheckResourceAttr(serviceLinkResourceName, "template", "sometemplate_new.{{ss}}"), + ), + }, + }, + }) + c := s.provider.Meta().(map[string]*client.RollbarAPIClient)[projectKeyToken] + c.ListSerivceLinks() +} + +// sweepResourceServiceLink cleans up service links. +func sweepResourceServiceLink(_ string) error { + log.Info().Msg("Cleaning up Rollbar service links from acceptance test runs.") + + c := client.NewClient(client.DefaultBaseURL, os.Getenv("ROLLBAR_PROJECT_API_KEY")) + serviceLinks, err := c.ListSerivceLinks() + if err != nil { + log.Err(err).Send() + return err + } + for _, s := range serviceLinks { + err = c.DeleteServiceLink(s.ID) + if err != nil { + log.Err(err).Send() + return err + } + } + return nil +} From 329e30ebd772bde9f5e551ed87a2af7e22772253 Mon Sep 17 00:00:00 2001 From: Pawel Date: Sun, 16 Oct 2022 22:47:40 -0700 Subject: [PATCH 3/4] cleanup --- rollbar/resource_service_link_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/rollbar/resource_service_link_test.go b/rollbar/resource_service_link_test.go index e6b298b2..3d22365f 100644 --- a/rollbar/resource_service_link_test.go +++ b/rollbar/resource_service_link_test.go @@ -108,8 +108,6 @@ func (s *AccSuite) TestServiceLinkUpdate() { }, }, }) - c := s.provider.Meta().(map[string]*client.RollbarAPIClient)[projectKeyToken] - c.ListSerivceLinks() } // sweepResourceServiceLink cleans up service links. From 807feab050bd31cab18864522865c75abc69dd05 Mon Sep 17 00:00:00 2001 From: Pawel Date: Sun, 16 Oct 2022 22:50:13 -0700 Subject: [PATCH 4/4] clean up --- rollbar/resource_notification.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rollbar/resource_notification.go b/rollbar/resource_notification.go index 7cda739d..767610a8 100644 --- a/rollbar/resource_notification.go +++ b/rollbar/resource_notification.go @@ -285,8 +285,8 @@ func flattenConfig(config map[string]interface{}) *schema.Set { out = append(out, config) specResource := resourceNotification().Schema["config"].Elem.(*schema.Resource) f := schema.HashResource(specResource) - g := schema.NewSet(f, out) - return g + set := schema.NewSet(f, out) + return set } func flattenRule(filters []interface{}, trigger string) *schema.Set { @@ -317,8 +317,8 @@ func flattenRule(filters []interface{}, trigger string) *schema.Set { m["trigger"] = trigger specResource := resourceNotification().Schema["rule"].Elem.(*schema.Resource) f := schema.HashResource(specResource) - g := schema.NewSet(f, out) - return g + set := schema.NewSet(f, out) + return set } func resourceNotificationRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {