diff --git a/docs/resources/issue_alert.md b/docs/resources/issue_alert.md index 07b54909..94b5b0b3 100644 --- a/docs/resources/issue_alert.md +++ b/docs/resources/issue_alert.md @@ -23,31 +23,35 @@ resource "sentry_issue_alert" "main" { frequency = 30 conditions = [ + # A new issue is created { - id = "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition" - name = "A new issue is created" + id = "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition" }, + + # The issue changes state from resolved to unresolved { - id = "sentry.rules.conditions.regression_event.RegressionEventCondition" - name = "The issue changes state from resolved to unresolved" + id = "sentry.rules.conditions.regression_event.RegressionEventCondition" }, + + # The issue is seen more than 100 times in 1h { id = "sentry.rules.conditions.event_frequency.EventFrequencyCondition" - name = "The issue is seen more than 100 times in 1h" value = 100 comparisonType = "count" interval = "1h" }, + + # The issue is seen by more than 100 users in 1h { id = "sentry.rules.conditions.event_frequency.EventUniqueUserFrequencyCondition" - name = "The issue is seen by more than 100 users in 1h" value = 100 comparisonType = "count" interval = "1h" }, + + # The issue affects more than 50.0 percent of sessions in 1h { id = "sentry.rules.conditions.event_frequency.EventFrequencyPercentCondition" - name = "The issue affects more than 50.0 percent of sessions in 1h" value = 50.0 comparisonType = "count" interval = "1h" @@ -55,69 +59,95 @@ resource "sentry_issue_alert" "main" { ] filters = [ + # The issue is older than 10 minute { id = "sentry.rules.filters.age_comparison.AgeComparisonFilter" - name = "The issue is older than 10 minute" value = 10 time = "minute" comparison_type = "older" }, + + # The issue has happened at least 10 times { id = "sentry.rules.filters.issue_occurrences.IssueOccurrencesFilter" - name = "The issue has happened at least 10 times" value = 10 }, + + # The issue is assigned to Team { id = "sentry.rules.filters.assigned_to.AssignedToFilter" - name = "The issue is assigned to Team" targetType = "Team" targetIdentifier = sentry_team.main.team_id }, + + # The event is from the latest release { - id = "sentry.rules.filters.latest_release.LatestReleaseFilter" - name = "The event is from the latest release" + id = "sentry.rules.filters.latest_release.LatestReleaseFilter" }, + + # The event's message value contains test { id = "sentry.rules.filters.event_attribute.EventAttributeFilter" - name = "The event's message value contains test" attribute = "message" match = "co" value = "test" }, + + # The event's tags match test contains test { id = "sentry.rules.filters.tagged_event.TaggedEventFilter" - name = "The event's tags match test contains test" key = "test" match = "co" value = "test" }, + + # The event's level is equal to fatal { id = "sentry.rules.filters.level.LevelFilter" - name = "The event's level is equal to fatal" match = "eq" level = "50" } ] actions = [ + # Send a notification to IssueOwners { id = "sentry.mail.actions.NotifyEmailAction" - name = "Send a notification to IssueOwners" targetType = "IssueOwners" targetIdentifier = "" }, + + # Send a notification to Team { id = "sentry.mail.actions.NotifyEmailAction" - name = "Send a notification to Team" targetType = "Team" targetIdentifier = sentry_team.main.team_id }, + + # Send a notification (for all legacy integrations) { - id = "sentry.rules.actions.notify_event.NotifyEventAction" - name = "Send a notification (for all legacy integrations)" - } + id = "sentry.rules.actions.notify_event.NotifyEventAction" + }, + + # Send a notification to the Slack workspace to #general + { + id = "sentry.integrations.slack.notify_action.SlackNotifyServiceAction" + channel = "#general" + + # From: https://sentry.io/settings/[org-slug]/integrations/slack/[slack-integration-id]/ + # Or use the sentry_organization_integration data source to retrieve the integration ID: + workspace = data.sentry_organization_integration.slack.internal_id + }, ] } + +# Retrieve a Slack integration +data "sentry_organization_integration" "slack" { + organization = sentry_project.test.organization + + provider_key = "slack" + name = "Slack Workspace" # Name of your Slack workspace +} ``` @@ -151,7 +181,6 @@ Import is supported using the following syntax: ```shell # import using the organization, project slugs and rule id from the URL: -# https://sentry.io/organizations/[org-slug]/projects/[project-slug]/ -# https://sentry.io/organizations/[org-slug]/alerts/rules/details/[rule-id]/ +# https://sentry.io/organizations/[org-slug]/alerts/rules/[project-slug]/[rule-id]/details/ terraform import sentry_issue_alert.default org-slug/project-slug/rule-id ``` diff --git a/docs/resources/metric_alert.md b/docs/resources/metric_alert.md index b1689ce9..97771c29 100644 --- a/docs/resources/metric_alert.md +++ b/docs/resources/metric_alert.md @@ -131,5 +131,7 @@ Import is supported using the following syntax: # import using the organization, project slugs and rule id from the URL: # https://sentry.io/organizations/[org-slug]/projects/[project-slug]/ # https://sentry.io/organizations/[org-slug]/alerts/rules/details/[rule-id]/ +# or +# https://sentry.io/organizations/[org-slug]/alerts/metric-rules/[project-slug]/[rule-id]/ terraform import sentry_metric_alert.default org-slug/project-slug/rule-id ``` diff --git a/examples/resources/sentry_issue_alert/resource.tf b/examples/resources/sentry_issue_alert/resource.tf index 4a9df3d0..114a6c28 100644 --- a/examples/resources/sentry_issue_alert/resource.tf +++ b/examples/resources/sentry_issue_alert/resource.tf @@ -8,31 +8,35 @@ resource "sentry_issue_alert" "main" { frequency = 30 conditions = [ + # A new issue is created { - id = "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition" - name = "A new issue is created" + id = "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition" }, + + # The issue changes state from resolved to unresolved { - id = "sentry.rules.conditions.regression_event.RegressionEventCondition" - name = "The issue changes state from resolved to unresolved" + id = "sentry.rules.conditions.regression_event.RegressionEventCondition" }, + + # The issue is seen more than 100 times in 1h { id = "sentry.rules.conditions.event_frequency.EventFrequencyCondition" - name = "The issue is seen more than 100 times in 1h" value = 100 comparisonType = "count" interval = "1h" }, + + # The issue is seen by more than 100 users in 1h { id = "sentry.rules.conditions.event_frequency.EventUniqueUserFrequencyCondition" - name = "The issue is seen by more than 100 users in 1h" value = 100 comparisonType = "count" interval = "1h" }, + + # The issue affects more than 50.0 percent of sessions in 1h { id = "sentry.rules.conditions.event_frequency.EventFrequencyPercentCondition" - name = "The issue affects more than 50.0 percent of sessions in 1h" value = 50.0 comparisonType = "count" interval = "1h" @@ -40,66 +44,92 @@ resource "sentry_issue_alert" "main" { ] filters = [ + # The issue is older than 10 minute { id = "sentry.rules.filters.age_comparison.AgeComparisonFilter" - name = "The issue is older than 10 minute" value = 10 time = "minute" comparison_type = "older" }, + + # The issue has happened at least 10 times { id = "sentry.rules.filters.issue_occurrences.IssueOccurrencesFilter" - name = "The issue has happened at least 10 times" value = 10 }, + + # The issue is assigned to Team { id = "sentry.rules.filters.assigned_to.AssignedToFilter" - name = "The issue is assigned to Team" targetType = "Team" targetIdentifier = sentry_team.main.team_id }, + + # The event is from the latest release { - id = "sentry.rules.filters.latest_release.LatestReleaseFilter" - name = "The event is from the latest release" + id = "sentry.rules.filters.latest_release.LatestReleaseFilter" }, + + # The event's message value contains test { id = "sentry.rules.filters.event_attribute.EventAttributeFilter" - name = "The event's message value contains test" attribute = "message" match = "co" value = "test" }, + + # The event's tags match test contains test { id = "sentry.rules.filters.tagged_event.TaggedEventFilter" - name = "The event's tags match test contains test" key = "test" match = "co" value = "test" }, + + # The event's level is equal to fatal { id = "sentry.rules.filters.level.LevelFilter" - name = "The event's level is equal to fatal" match = "eq" level = "50" } ] actions = [ + # Send a notification to IssueOwners { id = "sentry.mail.actions.NotifyEmailAction" - name = "Send a notification to IssueOwners" targetType = "IssueOwners" targetIdentifier = "" }, + + # Send a notification to Team { id = "sentry.mail.actions.NotifyEmailAction" - name = "Send a notification to Team" targetType = "Team" targetIdentifier = sentry_team.main.team_id }, + + # Send a notification (for all legacy integrations) { - id = "sentry.rules.actions.notify_event.NotifyEventAction" - name = "Send a notification (for all legacy integrations)" - } + id = "sentry.rules.actions.notify_event.NotifyEventAction" + }, + + # Send a notification to the Slack workspace to #general + { + id = "sentry.integrations.slack.notify_action.SlackNotifyServiceAction" + channel = "#general" + + # From: https://sentry.io/settings/[org-slug]/integrations/slack/[slack-integration-id]/ + # Or use the sentry_organization_integration data source to retrieve the integration ID: + workspace = data.sentry_organization_integration.slack.internal_id + }, ] } + +# Retrieve a Slack integration +data "sentry_organization_integration" "slack" { + organization = sentry_project.test.organization + + provider_key = "slack" + name = "Slack Workspace" # Name of your Slack workspace +} diff --git a/go.mod b/go.mod index 0c292b2e..77a5a78f 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/jianyuan/terraform-provider-sentry go 1.19 require ( + github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-retryablehttp v0.7.2 github.com/hashicorp/terraform-plugin-docs v0.13.0 @@ -32,7 +33,6 @@ require ( github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-checkpoint v0.5.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect github.com/hashicorp/go-hclog v1.3.1 // indirect github.com/hashicorp/go-plugin v1.4.6 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect diff --git a/go.sum b/go.sum index fb8c2338..bfabfc01 100644 --- a/go.sum +++ b/go.sum @@ -90,8 +90,6 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-plugin v1.4.6 h1:MDV3UrKQBM3du3G7MApDGvOsMYy3JQJ4exhSoKBAeVA= github.com/hashicorp/go-plugin v1.4.6/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= -github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ= -github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0= github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -136,8 +134,6 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= -github.com/jianyuan/go-sentry/v2 v2.1.1 h1:vBYdRvRouHOyyAhl9s7l8x8s0O51plUV+q9+vUdpvLg= -github.com/jianyuan/go-sentry/v2 v2.1.1/go.mod h1:OZZZB/l6QO4g1qzLWiW4wu6KvOBSmebOnskOr4Uc99o= github.com/jianyuan/go-sentry/v2 v2.2.0 h1:yAJxTjEBFLh/3ED8n4XiheaeadPaeBuMr8owTq1ZUJE= github.com/jianyuan/go-sentry/v2 v2.2.0/go.mod h1:OZZZB/l6QO4g1qzLWiW4wu6KvOBSmebOnskOr4Uc99o= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= @@ -250,13 +246,9 @@ golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= -golang.org/x/net v0.3.0 h1:VWL6FNY2bEEmsGVKabSlHu5Irp34xmMRoqb/9lF9lxk= -golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.3.0 h1:6l90koy8/LaBLmLu8jpHeHexzMwEita0zFfYlggy2F8= -golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk= golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M= golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -280,18 +272,14 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= +golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/sentry/helpers.go b/sentry/helpers.go index 4f521b43..928cc496 100644 --- a/sentry/helpers.go +++ b/sentry/helpers.go @@ -54,6 +54,36 @@ func SuppressEquivalentJSONDiffs(k, old, new string, d *schema.ResourceData) boo return reflect.DeepEqual(o, n) } +// followShape reshapes the value into the provided shape +func followShape(shape, value interface{}) interface{} { + switch shape := shape.(type) { + case map[string]interface{}: + value, ok := interface{}(value).(map[string]interface{}) + if !ok { + return nil + } + + v := make(map[string]interface{}) + for k, shapeValue := range shape { + v[k] = followShape(shapeValue, value[k]) + } + return v + case []interface{}: + value, ok := interface{}(value).([]interface{}) + if !ok { + return nil + } + + v := make([]interface{}, 0, len(shape)) + for i, shapeValue := range shape { + v = append(v, followShape(shapeValue, value[i])) + } + return v + default: + return value + } +} + func flattenStringSet(strings []string) *schema.Set { flattenedStrings := schema.NewSet(schema.HashString, []interface{}{}) for _, v := range strings { diff --git a/sentry/helpers_test.go b/sentry/helpers_test.go new file mode 100644 index 00000000..11743472 --- /dev/null +++ b/sentry/helpers_test.go @@ -0,0 +1,104 @@ +package sentry + +import ( + "reflect" + "testing" +) + +func TestFollowShape(t *testing.T) { + testCases := []struct { + name string + shape interface{} + value interface{} + want interface{} + }{ + { + name: "shape is nil", + shape: nil, + value: []interface{}{ + map[string]interface{}{ + "a": "a", + "b": "b", + "c": "c", + }, + map[string]interface{}{ + "a": "a", + "b": "b", + "c": "c", + }, + map[string]interface{}{ + "a": "a", + "b": "b", + "c": "c", + }, + }, + want: []interface{}{ + map[string]interface{}{ + "a": "a", + "b": "b", + "c": "c", + }, + map[string]interface{}{ + "a": "a", + "b": "b", + "c": "c", + }, + map[string]interface{}{ + "a": "a", + "b": "b", + "c": "c", + }, + }, + }, + { + name: "complex shape", + shape: []interface{}{ + map[string]interface{}{ + "a": "", + }, + map[string]interface{}{ + "b": "", + }, + map[string]interface{}{ + "c": 0, + }, + }, + value: []interface{}{ + map[string]interface{}{ + "a": "a", + "b": "b", + "c": 3, + }, + map[string]interface{}{ + "a": "a", + "b": "b", + "c": 3, + }, + map[string]interface{}{ + "a": "a", + "b": "b", + "c": 3, + }, + }, + want: []interface{}{ + map[string]interface{}{ + "a": "a", + }, + map[string]interface{}{ + "b": "b", + }, + map[string]interface{}{ + "c": 3, + }, + }, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got := followShape(tc.shape, tc.value) + if !reflect.DeepEqual(got, tc.want) { + t.Errorf("got %v; want %v", got, tc.want) + } + }) + } +} diff --git a/sentry/resource_sentry_issue_alert.go b/sentry/resource_sentry_issue_alert.go index 6b8aea38..ec402f4e 100644 --- a/sentry/resource_sentry_issue_alert.go +++ b/sentry/resource_sentry_issue_alert.go @@ -66,7 +66,6 @@ func resourceSentryIssueAlertSchema() map[string]*schema.Schema { Elem: &schema.Schema{ Type: schema.TypeMap, }, - DiffSuppressFunc: SuppressEquivalentJSONDiffs, }, "filters": { Description: "List of filters.", @@ -75,7 +74,6 @@ func resourceSentryIssueAlertSchema() map[string]*schema.Schema { Elem: &schema.Schema{ Type: schema.TypeMap, }, - DiffSuppressFunc: SuppressEquivalentJSONDiffs, }, "actions": { Description: "List of actions.", @@ -84,7 +82,6 @@ func resourceSentryIssueAlertSchema() map[string]*schema.Schema { Elem: &schema.Schema{ Type: schema.TypeMap, }, - DiffSuppressFunc: SuppressEquivalentJSONDiffs, }, "action_match": { Description: "Trigger actions when an event is captured by Sentry and `any` or `all` of the specified conditions happen.", @@ -224,18 +221,9 @@ func resourceSentryIssueAlertRead(ctx context.Context, d *schema.ResourceData, m return diag.FromErr(err) } - conditions := make([]interface{}, 0, len(alert.Conditions)) - for _, condition := range alert.Conditions { - conditions = append(conditions, *condition) - } - filters := make([]interface{}, 0, len(alert.Filters)) - for _, filter := range alert.Filters { - filters = append(filters, *filter) - } - actions := make([]interface{}, 0, len(alert.Actions)) - for _, action := range alert.Actions { - actions = append(actions, *action) - } + conditions := followShape(d.Get("conditions"), normalizeSentryIssueAlertProperty(alert.Conditions)) + filters := followShape(d.Get("filters"), normalizeSentryIssueAlertProperty(alert.Filters)) + actions := followShape(d.Get("actions"), normalizeSentryIssueAlertProperty(alert.Actions)) d.SetId(buildThreePartID(org, project, sentry.StringValue(alert.ID))) retErr := multierror.Append( @@ -297,3 +285,15 @@ func resourceSentryIssueAlertDelete(ctx context.Context, d *schema.ResourceData, _, err = client.IssueAlerts.Delete(ctx, org, project, alertID) return diag.FromErr(err) } + +func normalizeSentryIssueAlertProperty[T interface{ ~map[string]interface{} }](v []*T) []interface{} { + out := make([]interface{}, 0, len(v)) + for _, c := range v { + m := make(map[string]interface{}) + for k, v := range *c { + m[k] = v + } + out = append(out, m) + } + return out +} diff --git a/sentry/resource_sentry_issue_alert_test.go b/sentry/resource_sentry_issue_alert_test.go index c4b9b6cd..9540ba85 100644 --- a/sentry/resource_sentry_issue_alert_test.go +++ b/sentry/resource_sentry_issue_alert_test.go @@ -40,30 +40,25 @@ func TestAccSentryIssueAlert_basic(t *testing.T) { resource.TestCheckResourceAttr(rn, "conditions.3.id", "sentry.rules.conditions.event_frequency.EventUniqueUserFrequencyCondition"), resource.TestCheckResourceAttr(rn, "conditions.4.id", "sentry.rules.conditions.event_frequency.EventFrequencyPercentCondition"), resource.TestCheckTypeSetElemNestedAttrs(rn, "conditions.*", map[string]string{ - "id": "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition", - "name": "A new issue is created", + "id": "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition", }), resource.TestCheckTypeSetElemNestedAttrs(rn, "conditions.*", map[string]string{ - "id": "sentry.rules.conditions.regression_event.RegressionEventCondition", - "name": "The issue changes state from resolved to unresolved", + "id": "sentry.rules.conditions.regression_event.RegressionEventCondition", }), resource.TestCheckTypeSetElemNestedAttrs(rn, "conditions.*", map[string]string{ "id": "sentry.rules.conditions.event_frequency.EventFrequencyCondition", - "name": "The issue is seen more than 100 times in 1h", "value": "100", "comparisonType": "count", "interval": "1h", }), resource.TestCheckTypeSetElemNestedAttrs(rn, "conditions.*", map[string]string{ "id": "sentry.rules.conditions.event_frequency.EventUniqueUserFrequencyCondition", - "name": "The issue is seen by more than 100 users in 1h", "value": "100", "comparisonType": "count", "interval": "1h", }), resource.TestCheckTypeSetElemNestedAttrs(rn, "conditions.*", map[string]string{ "id": "sentry.rules.conditions.event_frequency.EventFrequencyPercentCondition", - "name": "The issue affects more than 50.0 percent of sessions in 1h", "value": "50.0", "comparisonType": "count", "interval": "1h", @@ -79,43 +74,36 @@ func TestAccSentryIssueAlert_basic(t *testing.T) { resource.TestCheckResourceAttr(rn, "filters.6.id", "sentry.rules.filters.level.LevelFilter"), resource.TestCheckTypeSetElemNestedAttrs(rn, "filters.*", map[string]string{ "id": "sentry.rules.filters.age_comparison.AgeComparisonFilter", - "name": "The issue is older than 10 minute", "value": "10", "time": "minute", "comparison_type": "older", }), resource.TestCheckTypeSetElemNestedAttrs(rn, "filters.*", map[string]string{ "id": "sentry.rules.filters.issue_occurrences.IssueOccurrencesFilter", - "name": "The issue has happened at least 10 times", "value": "10", }), resource.TestCheckTypeSetElemNestedAttrs(rn, "filters.*", map[string]string{ "id": "sentry.rules.filters.assigned_to.AssignedToFilter", - "name": "The issue is assigned to Team", "targetType": "Team", }), resource.TestCheckResourceAttrPair(rn, "filters.2.targetIdentifier", "sentry_team.test", "team_id"), resource.TestCheckTypeSetElemNestedAttrs(rn, "filters.*", map[string]string{ - "id": "sentry.rules.filters.latest_release.LatestReleaseFilter", - "name": "The event is from the latest release", + "id": "sentry.rules.filters.latest_release.LatestReleaseFilter", }), resource.TestCheckTypeSetElemNestedAttrs(rn, "filters.*", map[string]string{ "id": "sentry.rules.filters.event_attribute.EventAttributeFilter", - "name": "The event's message value contains test", "attribute": "message", "match": "co", "value": "test", }), resource.TestCheckTypeSetElemNestedAttrs(rn, "filters.*", map[string]string{ "id": "sentry.rules.filters.tagged_event.TaggedEventFilter", - "name": "The event's tags match test contains test", "key": "test", "match": "co", "value": "test", }), resource.TestCheckTypeSetElemNestedAttrs(rn, "filters.*", map[string]string{ "id": "sentry.rules.filters.level.LevelFilter", - "name": "The event's level is equal to fatal", "match": "eq", "level": "50", }), @@ -126,19 +114,16 @@ func TestAccSentryIssueAlert_basic(t *testing.T) { resource.TestCheckResourceAttr(rn, "actions.2.id", "sentry.rules.actions.notify_event.NotifyEventAction"), resource.TestCheckTypeSetElemNestedAttrs(rn, "actions.*", map[string]string{ "id": "sentry.mail.actions.NotifyEmailAction", - "name": "Send a notification to IssueOwners and if none can be found then send a notification to ActiveMembers", "targetType": "IssueOwners", "targetIdentifier": "", }), resource.TestCheckTypeSetElemNestedAttrs(rn, "actions.*", map[string]string{ "id": "sentry.mail.actions.NotifyEmailAction", - "name": "Send a notification to Team and if none can be found then send a notification to ActiveMembers", "targetType": "Team", "targetIdentifier": "", }), resource.TestCheckTypeSetElemNestedAttrs(rn, "actions.*", map[string]string{ - "id": "sentry.rules.actions.notify_event.NotifyEventAction", - "name": "Send a notification (for all legacy integrations)", + "id": "sentry.rules.actions.notify_event.NotifyEventAction", }), ) } @@ -157,9 +142,8 @@ func TestAccSentryIssueAlert_basic(t *testing.T) { Check: check(alertName + "-renamed"), }, { - ResourceName: rn, - ImportState: true, - ImportStateVerify: true, + ResourceName: rn, + ImportState: true, }, }, }) @@ -233,31 +217,26 @@ resource "sentry_issue_alert" "test" { conditions = [ { - id = "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition" - name = "A new issue is created" + id = "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition" }, { - id = "sentry.rules.conditions.regression_event.RegressionEventCondition" - name = "The issue changes state from resolved to unresolved" + id = "sentry.rules.conditions.regression_event.RegressionEventCondition" }, { id = "sentry.rules.conditions.event_frequency.EventFrequencyCondition" - name = "The issue is seen more than 100 times in 1h" value = 100 comparisonType = "count" interval = "1h" }, { id = "sentry.rules.conditions.event_frequency.EventUniqueUserFrequencyCondition" - name = "The issue is seen by more than 100 users in 1h" value = 100 comparisonType = "count" interval = "1h" }, { id = "sentry.rules.conditions.event_frequency.EventFrequencyPercentCondition" - name = "The issue affects more than 50.0 percent of sessions in 1h" - value = 50.0 + value = "50.0" comparisonType = "count" interval = "1h" }, @@ -266,43 +245,36 @@ resource "sentry_issue_alert" "test" { filters = [ { id = "sentry.rules.filters.age_comparison.AgeComparisonFilter" - name = "The issue is older than 10 minute" value = 10 time = "minute" comparison_type = "older" }, { id = "sentry.rules.filters.issue_occurrences.IssueOccurrencesFilter" - name = "The issue has happened at least 10 times" value = 10 }, { id = "sentry.rules.filters.assigned_to.AssignedToFilter" - name = "The issue is assigned to Team" targetType = "Team" targetIdentifier = sentry_team.test.team_id }, { - id = "sentry.rules.filters.latest_release.LatestReleaseFilter" - name = "The event is from the latest release" + id = "sentry.rules.filters.latest_release.LatestReleaseFilter" }, { id = "sentry.rules.filters.event_attribute.EventAttributeFilter" - name = "The event's message value contains test" attribute = "message" match = "co" value = "test" }, { id = "sentry.rules.filters.tagged_event.TaggedEventFilter" - name = "The event's tags match test contains test" key = "test" match = "co" value = "test" }, { id = "sentry.rules.filters.level.LevelFilter" - name = "The event's level is equal to fatal" match = "eq" level = "50" } @@ -311,19 +283,16 @@ resource "sentry_issue_alert" "test" { actions = [ { id = "sentry.mail.actions.NotifyEmailAction" - name = "Send a notification to IssueOwners and if none can be found then send a notification to ActiveMembers" targetType = "IssueOwners" targetIdentifier = "" }, { id = "sentry.mail.actions.NotifyEmailAction" - name = "Send a notification to Team and if none can be found then send a notification to ActiveMembers" targetType = "Team" targetIdentifier = sentry_team.test.team_id }, { - id = "sentry.rules.actions.notify_event.NotifyEventAction" - name = "Send a notification (for all legacy integrations)" + id = "sentry.rules.actions.notify_event.NotifyEventAction" } ] }