From cdeac1a2f865cfd56f18cf36484a3a39f023ab8f Mon Sep 17 00:00:00 2001 From: Matthew Date: Tue, 29 Nov 2022 11:11:43 -0800 Subject: [PATCH] - a state migration to work around the previously incorrect id casing --- ..._alert_rule_template_migration_v0_to_v1.go | 136 +++++++++++++++ ...inel_automation_rule_migration_v0_to_v1.go | 160 ++++++++++++++++++ .../sentinel/parse/automation_rule.go | 60 ++++++- .../sentinel/parse/automation_rule_test.go | 142 +++++++++++++++- .../parse/sentinel_alert_rule_template.go | 60 ++++++- .../sentinel_alert_rule_template_test.go | 142 +++++++++++++++- internal/services/sentinel/resourceids.go | 4 +- ...entinel_alert_rule_template_data_source.go | 6 + .../sentinel_automation_rule_resource.go | 6 + .../validate/automation_rule_id_test.go | 4 +- .../sentinel_alert_rule_template_id_test.go | 4 +- .../r/sentinel_automation_rule.html.markdown | 2 +- 12 files changed, 709 insertions(+), 17 deletions(-) create mode 100644 internal/services/sentinel/migration/sentinel_alert_rule_template_migration_v0_to_v1.go create mode 100644 internal/services/sentinel/migration/sentinel_automation_rule_migration_v0_to_v1.go diff --git a/internal/services/sentinel/migration/sentinel_alert_rule_template_migration_v0_to_v1.go b/internal/services/sentinel/migration/sentinel_alert_rule_template_migration_v0_to_v1.go new file mode 100644 index 000000000000..2ba4730d5f01 --- /dev/null +++ b/internal/services/sentinel/migration/sentinel_alert_rule_template_migration_v0_to_v1.go @@ -0,0 +1,136 @@ +package migration + +import ( + "context" + "log" + + "github.com/hashicorp/terraform-provider-azurerm/internal/services/sentinel/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" +) + +type SentinelAlertRuleTemplateV0ToV1 struct{} + +func (s SentinelAlertRuleTemplateV0ToV1) Schema() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + }, + + "display_name": { + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + }, + + "log_analytics_workspace_id": { + Type: pluginsdk.TypeString, + Required: true, + }, + + "scheduled_template": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "description": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "tactics": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + "severity": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "query": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "query_frequency": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "query_period": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "trigger_operator": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "trigger_threshold": { + Type: pluginsdk.TypeInt, + Computed: true, + }, + }, + }, + }, + + "security_incident_template": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "description": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "product_filter": { + Type: pluginsdk.TypeString, + Computed: true, + }, + }, + }, + }, + + "nrt_template": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "description": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "tactics": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + "severity": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "query": { + Type: pluginsdk.TypeString, + Computed: true, + }, + }, + }, + }, + } +} + +func (s SentinelAlertRuleTemplateV0ToV1) UpgradeFunc() pluginsdk.StateUpgraderFunc { + return func(ctx context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) { + oldId := rawState["id"].(string) + newId, err := parse.SentinelAlertRuleTemplateIDInsensitively(oldId) + if err != nil { + return nil, err + } + + log.Printf("[DEBUG] Updating ID from %q to %q", oldId, newId) + + rawState["id"] = newId.ID() + return rawState, nil + } +} diff --git a/internal/services/sentinel/migration/sentinel_automation_rule_migration_v0_to_v1.go b/internal/services/sentinel/migration/sentinel_automation_rule_migration_v0_to_v1.go new file mode 100644 index 000000000000..d3db79198e94 --- /dev/null +++ b/internal/services/sentinel/migration/sentinel_automation_rule_migration_v0_to_v1.go @@ -0,0 +1,160 @@ +package migration + +import ( + "context" + "log" + + "github.com/hashicorp/terraform-provider-azurerm/internal/services/sentinel/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" +) + +type SentinelAutomationRuleV0ToV1 struct{} + +func (s SentinelAutomationRuleV0ToV1) Schema() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + }, + + "log_analytics_workspace_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + }, + + "display_name": { + Type: pluginsdk.TypeString, + Required: true, + }, + + "order": { + Type: pluginsdk.TypeInt, + Required: true, + }, + + "enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: true, + }, + + "expiration": { + Type: pluginsdk.TypeString, + Optional: true, + }, + + "condition": { + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "property": { + Type: pluginsdk.TypeString, + Required: true, + }, + + "operator": { + Type: pluginsdk.TypeString, + Required: true, + }, + + "values": { + Type: pluginsdk.TypeList, + Required: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + }, + }, + }, + + "action_incident": { + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "order": { + Type: pluginsdk.TypeInt, + Required: true, + }, + + "status": { + Type: pluginsdk.TypeString, + Optional: true, + }, + + "classification": { + Type: pluginsdk.TypeString, + Optional: true, + }, + + "classification_comment": { + Type: pluginsdk.TypeString, + Optional: true, + }, + + "labels": { + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + + "owner_id": { + Type: pluginsdk.TypeString, + Optional: true, + }, + + "severity": { + Type: pluginsdk.TypeString, + Optional: true, + }, + }, + }, + }, + + "action_playbook": { + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "order": { + Type: pluginsdk.TypeInt, + Required: true, + }, + + "logic_app_id": { + Type: pluginsdk.TypeString, + Required: true, + }, + + "tenant_id": { + Type: pluginsdk.TypeString, + // We'll use the current tenant id if this property is absent. + Optional: true, + Computed: true, + }, + }, + }, + }, + } +} + +func (s SentinelAutomationRuleV0ToV1) UpgradeFunc() pluginsdk.StateUpgraderFunc { + return func(ctx context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) { + oldId := rawState["id"].(string) + newId, err := parse.AutomationRuleIDInsensitively(oldId) + if err != nil { + return nil, err + } + + log.Printf("[DEBUG] Updating ID from %q to %q", oldId, newId) + + rawState["id"] = newId.ID() + return rawState, nil + } +} diff --git a/internal/services/sentinel/parse/automation_rule.go b/internal/services/sentinel/parse/automation_rule.go index ec120fde4801..602cace52c7b 100644 --- a/internal/services/sentinel/parse/automation_rule.go +++ b/internal/services/sentinel/parse/automation_rule.go @@ -36,7 +36,7 @@ func (id AutomationRuleId) String() string { } func (id AutomationRuleId) ID() string { - fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.OperationalInsights/workspaces/%s/providers/Microsoft.SecurityInsights/AutomationRules/%s" + fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.OperationalInsights/workspaces/%s/providers/Microsoft.SecurityInsights/automationRules/%s" return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.WorkspaceName, id.Name) } @@ -63,7 +63,63 @@ func AutomationRuleID(input string) (*AutomationRuleId, error) { if resourceId.WorkspaceName, err = id.PopSegment("workspaces"); err != nil { return nil, err } - if resourceId.Name, err = id.PopSegment("AutomationRules"); err != nil { + if resourceId.Name, err = id.PopSegment("automationRules"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &resourceId, nil +} + +// AutomationRuleIDInsensitively parses an AutomationRule ID into an AutomationRuleId struct, insensitively +// This should only be used to parse an ID for rewriting, the AutomationRuleID +// method should be used instead for validation etc. +// +// Whilst this may seem strange, this enables Terraform have consistent casing +// which works around issues in Core, whilst handling broken API responses. +func AutomationRuleIDInsensitively(input string) (*AutomationRuleId, error) { + id, err := resourceids.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + resourceId := AutomationRuleId{ + SubscriptionId: id.SubscriptionID, + ResourceGroup: id.ResourceGroup, + } + + if resourceId.SubscriptionId == "" { + return nil, fmt.Errorf("ID was missing the 'subscriptions' element") + } + + if resourceId.ResourceGroup == "" { + return nil, fmt.Errorf("ID was missing the 'resourceGroups' element") + } + + // find the correct casing for the 'workspaces' segment + workspacesKey := "workspaces" + for key := range id.Path { + if strings.EqualFold(key, workspacesKey) { + workspacesKey = key + break + } + } + if resourceId.WorkspaceName, err = id.PopSegment(workspacesKey); err != nil { + return nil, err + } + + // find the correct casing for the 'automationRules' segment + automationRulesKey := "automationRules" + for key := range id.Path { + if strings.EqualFold(key, automationRulesKey) { + automationRulesKey = key + break + } + } + if resourceId.Name, err = id.PopSegment(automationRulesKey); err != nil { return nil, err } diff --git a/internal/services/sentinel/parse/automation_rule_test.go b/internal/services/sentinel/parse/automation_rule_test.go index aa91351a9e9a..eef20c2908f9 100644 --- a/internal/services/sentinel/parse/automation_rule_test.go +++ b/internal/services/sentinel/parse/automation_rule_test.go @@ -12,7 +12,7 @@ var _ resourceids.Id = AutomationRuleId{} func TestAutomationRuleIDFormatter(t *testing.T) { actual := NewAutomationRuleID("12345678-1234-9876-4563-123456789012", "resGroup1", "workspace1", "rule1").ID() - expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/AutomationRules/rule1" + expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/automationRules/rule1" if actual != expected { t.Fatalf("Expected %q but got %q", expected, actual) } @@ -75,13 +75,13 @@ func TestAutomationRuleID(t *testing.T) { { // missing value for Name - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/AutomationRules/", + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/automationRules/", Error: true, }, { // valid - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/AutomationRules/rule1", + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/automationRules/rule1", Expected: &AutomationRuleId{ SubscriptionId: "12345678-1234-9876-4563-123456789012", ResourceGroup: "resGroup1", @@ -126,3 +126,139 @@ func TestAutomationRuleID(t *testing.T) { } } } + +func TestAutomationRuleIDInsensitively(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *AutomationRuleId + }{ + + { + // empty + Input: "", + Error: true, + }, + + { + // missing SubscriptionId + Input: "/", + Error: true, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Error: true, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Error: true, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Error: true, + }, + + { + // missing WorkspaceName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/", + Error: true, + }, + + { + // missing value for WorkspaceName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/", + Error: true, + }, + + { + // missing Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/", + Error: true, + }, + + { + // missing value for Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/automationRules/", + Error: true, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/automationRules/rule1", + Expected: &AutomationRuleId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + WorkspaceName: "workspace1", + Name: "rule1", + }, + }, + + { + // lower-cased segment names + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/automationrules/rule1", + Expected: &AutomationRuleId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + WorkspaceName: "workspace1", + Name: "rule1", + }, + }, + + { + // upper-cased segment names + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/WORKSPACES/workspace1/providers/Microsoft.SecurityInsights/AUTOMATIONRULES/rule1", + Expected: &AutomationRuleId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + WorkspaceName: "workspace1", + Name: "rule1", + }, + }, + + { + // mixed-cased segment names + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/WoRkSpAcEs/workspace1/providers/Microsoft.SecurityInsights/AuToMaTiOnRuLeS/rule1", + Expected: &AutomationRuleId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + WorkspaceName: "workspace1", + Name: "rule1", + }, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := AutomationRuleIDInsensitively(v.Input) + if err != nil { + if v.Error { + continue + } + + t.Fatalf("Expect a value but got an error: %s", err) + } + if v.Error { + t.Fatal("Expect an error but didn't get one") + } + + if actual.SubscriptionId != v.Expected.SubscriptionId { + t.Fatalf("Expected %q but got %q for SubscriptionId", v.Expected.SubscriptionId, actual.SubscriptionId) + } + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) + } + if actual.WorkspaceName != v.Expected.WorkspaceName { + t.Fatalf("Expected %q but got %q for WorkspaceName", v.Expected.WorkspaceName, actual.WorkspaceName) + } + if actual.Name != v.Expected.Name { + t.Fatalf("Expected %q but got %q for Name", v.Expected.Name, actual.Name) + } + } +} diff --git a/internal/services/sentinel/parse/sentinel_alert_rule_template.go b/internal/services/sentinel/parse/sentinel_alert_rule_template.go index 5b9c13ea472a..8d9a10430c18 100644 --- a/internal/services/sentinel/parse/sentinel_alert_rule_template.go +++ b/internal/services/sentinel/parse/sentinel_alert_rule_template.go @@ -36,7 +36,7 @@ func (id SentinelAlertRuleTemplateId) String() string { } func (id SentinelAlertRuleTemplateId) ID() string { - fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.OperationalInsights/workspaces/%s/providers/Microsoft.SecurityInsights/AlertRuleTemplates/%s" + fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.OperationalInsights/workspaces/%s/providers/Microsoft.SecurityInsights/alertRuleTemplates/%s" return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.WorkspaceName, id.AlertRuleTemplateName) } @@ -63,7 +63,63 @@ func SentinelAlertRuleTemplateID(input string) (*SentinelAlertRuleTemplateId, er if resourceId.WorkspaceName, err = id.PopSegment("workspaces"); err != nil { return nil, err } - if resourceId.AlertRuleTemplateName, err = id.PopSegment("AlertRuleTemplates"); err != nil { + if resourceId.AlertRuleTemplateName, err = id.PopSegment("alertRuleTemplates"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &resourceId, nil +} + +// SentinelAlertRuleTemplateIDInsensitively parses an SentinelAlertRuleTemplate ID into an SentinelAlertRuleTemplateId struct, insensitively +// This should only be used to parse an ID for rewriting, the SentinelAlertRuleTemplateID +// method should be used instead for validation etc. +// +// Whilst this may seem strange, this enables Terraform have consistent casing +// which works around issues in Core, whilst handling broken API responses. +func SentinelAlertRuleTemplateIDInsensitively(input string) (*SentinelAlertRuleTemplateId, error) { + id, err := resourceids.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + resourceId := SentinelAlertRuleTemplateId{ + SubscriptionId: id.SubscriptionID, + ResourceGroup: id.ResourceGroup, + } + + if resourceId.SubscriptionId == "" { + return nil, fmt.Errorf("ID was missing the 'subscriptions' element") + } + + if resourceId.ResourceGroup == "" { + return nil, fmt.Errorf("ID was missing the 'resourceGroups' element") + } + + // find the correct casing for the 'workspaces' segment + workspacesKey := "workspaces" + for key := range id.Path { + if strings.EqualFold(key, workspacesKey) { + workspacesKey = key + break + } + } + if resourceId.WorkspaceName, err = id.PopSegment(workspacesKey); err != nil { + return nil, err + } + + // find the correct casing for the 'alertRuleTemplates' segment + alertRuleTemplatesKey := "alertRuleTemplates" + for key := range id.Path { + if strings.EqualFold(key, alertRuleTemplatesKey) { + alertRuleTemplatesKey = key + break + } + } + if resourceId.AlertRuleTemplateName, err = id.PopSegment(alertRuleTemplatesKey); err != nil { return nil, err } diff --git a/internal/services/sentinel/parse/sentinel_alert_rule_template_test.go b/internal/services/sentinel/parse/sentinel_alert_rule_template_test.go index 3f240fe87067..a1d81ae3d141 100644 --- a/internal/services/sentinel/parse/sentinel_alert_rule_template_test.go +++ b/internal/services/sentinel/parse/sentinel_alert_rule_template_test.go @@ -12,7 +12,7 @@ var _ resourceids.Id = SentinelAlertRuleTemplateId{} func TestSentinelAlertRuleTemplateIDFormatter(t *testing.T) { actual := NewSentinelAlertRuleTemplateID("12345678-1234-9876-4563-123456789012", "resGroup1", "workspace1", "template1").ID() - expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/AlertRuleTemplates/template1" + expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/alertRuleTemplates/template1" if actual != expected { t.Fatalf("Expected %q but got %q", expected, actual) } @@ -75,13 +75,13 @@ func TestSentinelAlertRuleTemplateID(t *testing.T) { { // missing value for AlertRuleTemplateName - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/AlertRuleTemplates/", + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/alertRuleTemplates/", Error: true, }, { // valid - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/AlertRuleTemplates/template1", + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/alertRuleTemplates/template1", Expected: &SentinelAlertRuleTemplateId{ SubscriptionId: "12345678-1234-9876-4563-123456789012", ResourceGroup: "resGroup1", @@ -126,3 +126,139 @@ func TestSentinelAlertRuleTemplateID(t *testing.T) { } } } + +func TestSentinelAlertRuleTemplateIDInsensitively(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *SentinelAlertRuleTemplateId + }{ + + { + // empty + Input: "", + Error: true, + }, + + { + // missing SubscriptionId + Input: "/", + Error: true, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Error: true, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Error: true, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Error: true, + }, + + { + // missing WorkspaceName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/", + Error: true, + }, + + { + // missing value for WorkspaceName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/", + Error: true, + }, + + { + // missing AlertRuleTemplateName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/", + Error: true, + }, + + { + // missing value for AlertRuleTemplateName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/alertRuleTemplates/", + Error: true, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/alertRuleTemplates/template1", + Expected: &SentinelAlertRuleTemplateId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + WorkspaceName: "workspace1", + AlertRuleTemplateName: "template1", + }, + }, + + { + // lower-cased segment names + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/alertruletemplates/template1", + Expected: &SentinelAlertRuleTemplateId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + WorkspaceName: "workspace1", + AlertRuleTemplateName: "template1", + }, + }, + + { + // upper-cased segment names + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/WORKSPACES/workspace1/providers/Microsoft.SecurityInsights/ALERTRULETEMPLATES/template1", + Expected: &SentinelAlertRuleTemplateId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + WorkspaceName: "workspace1", + AlertRuleTemplateName: "template1", + }, + }, + + { + // mixed-cased segment names + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/WoRkSpAcEs/workspace1/providers/Microsoft.SecurityInsights/AlErTrUlEtEmPlAtEs/template1", + Expected: &SentinelAlertRuleTemplateId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + WorkspaceName: "workspace1", + AlertRuleTemplateName: "template1", + }, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := SentinelAlertRuleTemplateIDInsensitively(v.Input) + if err != nil { + if v.Error { + continue + } + + t.Fatalf("Expect a value but got an error: %s", err) + } + if v.Error { + t.Fatal("Expect an error but didn't get one") + } + + if actual.SubscriptionId != v.Expected.SubscriptionId { + t.Fatalf("Expected %q but got %q for SubscriptionId", v.Expected.SubscriptionId, actual.SubscriptionId) + } + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) + } + if actual.WorkspaceName != v.Expected.WorkspaceName { + t.Fatalf("Expected %q but got %q for WorkspaceName", v.Expected.WorkspaceName, actual.WorkspaceName) + } + if actual.AlertRuleTemplateName != v.Expected.AlertRuleTemplateName { + t.Fatalf("Expected %q but got %q for AlertRuleTemplateName", v.Expected.AlertRuleTemplateName, actual.AlertRuleTemplateName) + } + } +} diff --git a/internal/services/sentinel/resourceids.go b/internal/services/sentinel/resourceids.go index 1270f3636b0b..dc5677e2a9eb 100644 --- a/internal/services/sentinel/resourceids.go +++ b/internal/services/sentinel/resourceids.go @@ -1,8 +1,8 @@ package sentinel //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=AlertRule -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/alertRules/rule1 -//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=SentinelAlertRuleTemplate -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/AlertRuleTemplates/template1 +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=SentinelAlertRuleTemplate -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/alertRuleTemplates/template1 -rewrite=true //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=DataConnector -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/dataConnectors/dc1 -//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=AutomationRule -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/AutomationRules/rule1 +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=AutomationRule -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/automationRules/rule1 -rewrite=true //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=Watchlist -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/watchlists/list1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=WatchlistItem -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/watchlists/list1/watchlistItems/item1 diff --git a/internal/services/sentinel/sentinel_alert_rule_template_data_source.go b/internal/services/sentinel/sentinel_alert_rule_template_data_source.go index 07639bdbd35c..eb5c6c5bb190 100644 --- a/internal/services/sentinel/sentinel_alert_rule_template_data_source.go +++ b/internal/services/sentinel/sentinel_alert_rule_template_data_source.go @@ -9,6 +9,7 @@ import ( "github.com/Azure/azure-sdk-for-go/services/preview/securityinsight/mgmt/2021-09-01-preview/securityinsight" "github.com/hashicorp/go-azure-sdk/resource-manager/operationalinsights/2020-08-01/workspaces" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/sentinel/migration" "github.com/hashicorp/terraform-provider-azurerm/internal/services/sentinel/parse" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" @@ -23,6 +24,11 @@ func dataSourceSentinelAlertRuleTemplate() *pluginsdk.Resource { Read: pluginsdk.DefaultTimeout(5 * time.Minute), }, + SchemaVersion: 1, + StateUpgraders: pluginsdk.StateUpgrades(map[int]pluginsdk.StateUpgrade{ + 0: migration.SentinelAlertRuleTemplateV0ToV1{}, + }), + Schema: map[string]*pluginsdk.Schema{ "name": { Type: pluginsdk.TypeString, diff --git a/internal/services/sentinel/sentinel_automation_rule_resource.go b/internal/services/sentinel/sentinel_automation_rule_resource.go index f6188821ab92..2a16ffc7e905 100644 --- a/internal/services/sentinel/sentinel_automation_rule_resource.go +++ b/internal/services/sentinel/sentinel_automation_rule_resource.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/helpers/azure" "github.com/hashicorp/terraform-provider-azurerm/helpers/tf" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/sentinel/migration" "github.com/hashicorp/terraform-provider-azurerm/internal/services/sentinel/parse" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/suppress" @@ -33,6 +34,11 @@ func resourceSentinelAutomationRule() *pluginsdk.Resource { return err }), + SchemaVersion: 1, + StateUpgraders: pluginsdk.StateUpgrades(map[int]pluginsdk.StateUpgrade{ + 0: migration.SentinelAutomationRuleV0ToV1{}, + }), + Timeouts: &pluginsdk.ResourceTimeout{ Create: pluginsdk.DefaultTimeout(5 * time.Minute), Read: pluginsdk.DefaultTimeout(5 * time.Minute), diff --git a/internal/services/sentinel/validate/automation_rule_id_test.go b/internal/services/sentinel/validate/automation_rule_id_test.go index cae98f30f7b4..47744c702dfa 100644 --- a/internal/services/sentinel/validate/automation_rule_id_test.go +++ b/internal/services/sentinel/validate/automation_rule_id_test.go @@ -60,13 +60,13 @@ func TestAutomationRuleID(t *testing.T) { { // missing value for Name - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/AutomationRules/", + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/automationRules/", Valid: false, }, { // valid - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/AutomationRules/rule1", + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/automationRules/rule1", Valid: true, }, diff --git a/internal/services/sentinel/validate/sentinel_alert_rule_template_id_test.go b/internal/services/sentinel/validate/sentinel_alert_rule_template_id_test.go index af1ad783a868..247112f8143e 100644 --- a/internal/services/sentinel/validate/sentinel_alert_rule_template_id_test.go +++ b/internal/services/sentinel/validate/sentinel_alert_rule_template_id_test.go @@ -60,13 +60,13 @@ func TestSentinelAlertRuleTemplateID(t *testing.T) { { // missing value for AlertRuleTemplateName - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/AlertRuleTemplates/", + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/alertRuleTemplates/", Valid: false, }, { // valid - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/AlertRuleTemplates/template1", + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/alertRuleTemplates/template1", Valid: true, }, diff --git a/website/docs/r/sentinel_automation_rule.html.markdown b/website/docs/r/sentinel_automation_rule.html.markdown index cf8fc7acb49e..e4385f045464 100644 --- a/website/docs/r/sentinel_automation_rule.html.markdown +++ b/website/docs/r/sentinel_automation_rule.html.markdown @@ -144,5 +144,5 @@ The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/l Sentinel Automation Rules can be imported using the `resource id`, e.g. ```shell -terraform import azurerm_sentinel_automation_rule.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/AutomationRules/rule1 +terraform import azurerm_sentinel_automation_rule.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.OperationalInsights/workspaces/workspace1/providers/Microsoft.SecurityInsights/automationRules/rule1 ```