From e0b857bff0d9e72cc160c9d3134cafc0fa7d25b4 Mon Sep 17 00:00:00 2001 From: Tao <104055472+teowa@users.noreply.github.com> Date: Thu, 25 Aug 2022 11:07:50 +0800 Subject: [PATCH 1/5] fix log_analytics_linked_storage_account data_source_type case sensitivity --- .../log_analytics_linked_storage_account_resource.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/services/loganalytics/log_analytics_linked_storage_account_resource.go b/internal/services/loganalytics/log_analytics_linked_storage_account_resource.go index 261145f6ede6..c44db1f01dbd 100644 --- a/internal/services/loganalytics/log_analytics_linked_storage_account_resource.go +++ b/internal/services/loganalytics/log_analytics_linked_storage_account_resource.go @@ -134,7 +134,7 @@ func resourceLogAnalyticsLinkedStorageAccountRead(d *pluginsdk.ResourceData, met d.Set("resource_group_name", id.ResourceGroup) d.Set("workspace_resource_id", parse.NewLogAnalyticsWorkspaceID(id.SubscriptionId, id.ResourceGroup, id.WorkspaceName).ID()) if props := resp.LinkedStorageAccountsProperties; props != nil { - d.Set("data_source_type", string(props.DataSourceType)) + d.Set("data_source_type", strings.ToLower(string(props.DataSourceType))) d.Set("storage_account_ids", utils.FlattenStringSlice(props.StorageAccountIds)) } From b06c1a4117667c774c79a89b4a6de01a8c5ca337 Mon Sep 17 00:00:00 2001 From: Tao <104055472+teowa@users.noreply.github.com> Date: Wed, 7 Sep 2022 16:52:32 +0800 Subject: [PATCH 2/5] ignore case of data_source_type --- ...og_analytics_linked_storage_account_resource.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/internal/services/loganalytics/log_analytics_linked_storage_account_resource.go b/internal/services/loganalytics/log_analytics_linked_storage_account_resource.go index 8c91cea856d6..428b1304e310 100644 --- a/internal/services/loganalytics/log_analytics_linked_storage_account_resource.go +++ b/internal/services/loganalytics/log_analytics_linked_storage_account_resource.go @@ -3,7 +3,6 @@ package loganalytics import ( "fmt" "log" - "strings" "time" "github.com/hashicorp/go-azure-helpers/lang/response" @@ -42,13 +41,12 @@ func resourceLogAnalyticsLinkedStorageAccount() *pluginsdk.Resource { Required: true, ForceNew: true, ValidateFunc: validation.StringInSlice([]string{ - strings.ToLower(string(linkedstorageaccounts.DataSourceTypeCustomLogs)), - strings.ToLower(string(linkedstorageaccounts.DataSourceTypeAzureWatson)), - strings.ToLower(string(linkedstorageaccounts.DataSourceTypeQuery)), - strings.ToLower(string(linkedstorageaccounts.DataSourceTypeAlerts)), - // Value removed from enum in 2020-08-01, but effectively still works - "ingestion", - }, false), + string(linkedstorageaccounts.DataSourceTypeCustomLogs), + string(linkedstorageaccounts.DataSourceTypeAzureWatson), + string(linkedstorageaccounts.DataSourceTypeQuery), + string(linkedstorageaccounts.DataSourceTypeAlerts), + string(linkedstorageaccounts.DataSourceTypeIngestion), + }, true), }, "resource_group_name": azure.SchemaResourceGroupName(), From 82f649233e8a659fe4ce96eeb19a3b26673153b2 Mon Sep 17 00:00:00 2001 From: teowa <104055472+teowa@users.noreply.github.com> Date: Fri, 9 Sep 2022 12:40:25 +0800 Subject: [PATCH 3/5] add 4.0 flag to ignore-case --- .../log_analytics_linked_storage_account_resource.go | 4 +++- .../docs/r/log_analytics_linked_storage_account.html.markdown | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/services/loganalytics/log_analytics_linked_storage_account_resource.go b/internal/services/loganalytics/log_analytics_linked_storage_account_resource.go index 428b1304e310..ae4e9387fc6c 100644 --- a/internal/services/loganalytics/log_analytics_linked_storage_account_resource.go +++ b/internal/services/loganalytics/log_analytics_linked_storage_account_resource.go @@ -10,6 +10,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/features" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" "github.com/hashicorp/terraform-provider-azurerm/internal/timeouts" @@ -40,13 +41,14 @@ func resourceLogAnalyticsLinkedStorageAccount() *pluginsdk.Resource { Type: pluginsdk.TypeString, Required: true, ForceNew: true, + // https://github.com/Azure/azure-rest-api-specs/issues/20619 ValidateFunc: validation.StringInSlice([]string{ string(linkedstorageaccounts.DataSourceTypeCustomLogs), string(linkedstorageaccounts.DataSourceTypeAzureWatson), string(linkedstorageaccounts.DataSourceTypeQuery), string(linkedstorageaccounts.DataSourceTypeAlerts), string(linkedstorageaccounts.DataSourceTypeIngestion), - }, true), + }, !features.FourPointOhBeta()), }, "resource_group_name": azure.SchemaResourceGroupName(), diff --git a/website/docs/r/log_analytics_linked_storage_account.html.markdown b/website/docs/r/log_analytics_linked_storage_account.html.markdown index edc88f8becf5..06028846b720 100644 --- a/website/docs/r/log_analytics_linked_storage_account.html.markdown +++ b/website/docs/r/log_analytics_linked_storage_account.html.markdown @@ -45,7 +45,9 @@ resource "azurerm_log_analytics_linked_storage_account" "example" { The following arguments are supported: -* `data_source_type` - (Required) The data source type which should be used for this Log Analytics Linked Storage Account. Possible values are `customlogs`, `azurewatson`, `query`, `ingestion` and `alerts`. Changing this forces a new Log Analytics Linked Storage Account to be created. +* `data_source_type` - (Required) The data source type which should be used for this Log Analytics Linked Storage Account. Possible values are `CustomLogs`, `AzureWatson`, `Query`, `Ingestion` and `Alerts`. Changing this forces a new Log Analytics Linked Storage Account to be created. + +> **Note:** The `data_source_type` is case-insensitive due to [service API issue](https://github.com/Azure/azure-rest-api-specs/issues/20619). After the API issue is fixed, case-sensitivity will be required in 4.0 or later versions. * `resource_group_name` - (Required) The name of the Resource Group where the Log Analytics Linked Storage Account should exist. Changing this forces a new Log Analytics Linked Storage Account to be created. From 4e65e45cb5f456a3702d21c56ebca604533bcb60 Mon Sep 17 00:00:00 2001 From: Tao <104055472+teowa@users.noreply.github.com> Date: Fri, 16 Sep 2022 14:38:55 +0800 Subject: [PATCH 4/5] read using id.dataSourceType --- ...alytics_linked_storage_account_resource.go | 17 ++++---- ...cs_linked_storage_account_resource_test.go | 41 +++++++++++++++++-- ...ytics_linked_storage_account.html.markdown | 4 +- 3 files changed, 49 insertions(+), 13 deletions(-) diff --git a/internal/services/loganalytics/log_analytics_linked_storage_account_resource.go b/internal/services/loganalytics/log_analytics_linked_storage_account_resource.go index ae4e9387fc6c..94b5d131e3b4 100644 --- a/internal/services/loganalytics/log_analytics_linked_storage_account_resource.go +++ b/internal/services/loganalytics/log_analytics_linked_storage_account_resource.go @@ -12,13 +12,14 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/internal/clients" "github.com/hashicorp/terraform-provider-azurerm/internal/features" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/suppress" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" "github.com/hashicorp/terraform-provider-azurerm/internal/timeouts" "github.com/hashicorp/terraform-provider-azurerm/utils" ) func resourceLogAnalyticsLinkedStorageAccount() *pluginsdk.Resource { - return &pluginsdk.Resource{ + resource := &pluginsdk.Resource{ Create: resourceLogAnalyticsLinkedStorageAccountCreateUpdate, Read: resourceLogAnalyticsLinkedStorageAccountRead, Update: resourceLogAnalyticsLinkedStorageAccountCreateUpdate, @@ -41,7 +42,6 @@ func resourceLogAnalyticsLinkedStorageAccount() *pluginsdk.Resource { Type: pluginsdk.TypeString, Required: true, ForceNew: true, - // https://github.com/Azure/azure-rest-api-specs/issues/20619 ValidateFunc: validation.StringInSlice([]string{ string(linkedstorageaccounts.DataSourceTypeCustomLogs), string(linkedstorageaccounts.DataSourceTypeAzureWatson), @@ -71,6 +71,12 @@ func resourceLogAnalyticsLinkedStorageAccount() *pluginsdk.Resource { }, }, } + + if !features.FourPointOh() { + resource.Schema["data_source_type"].DiffSuppressFunc = suppress.CaseDifference + } + + return resource } func resourceLogAnalyticsLinkedStorageAccountCreateUpdate(d *pluginsdk.ResourceData, meta interface{}) error { @@ -131,6 +137,7 @@ func resourceLogAnalyticsLinkedStorageAccountRead(d *pluginsdk.ResourceData, met d.Set("resource_group_name", id.ResourceGroupName) d.Set("workspace_resource_id", linkedstorageaccounts.NewWorkspaceID(id.SubscriptionId, id.ResourceGroupName, id.WorkspaceName).ID()) + d.Set("data_source_type", string(id.DataSourceType)) if model := resp.Model; model != nil { props := model.Properties @@ -139,12 +146,6 @@ func resourceLogAnalyticsLinkedStorageAccountRead(d *pluginsdk.ResourceData, met storageAccountIds = *props.StorageAccountIds } d.Set("storage_account_ids", storageAccountIds) - - dataSourceType := "" - if props.DataSourceType != nil { - dataSourceType = string(*props.DataSourceType) - } - d.Set("data_source_type", dataSourceType) } return nil diff --git a/internal/services/loganalytics/log_analytics_linked_storage_account_resource_test.go b/internal/services/loganalytics/log_analytics_linked_storage_account_resource_test.go index 4c14ecf1a7e4..db060bb10be5 100644 --- a/internal/services/loganalytics/log_analytics_linked_storage_account_resource_test.go +++ b/internal/services/loganalytics/log_analytics_linked_storage_account_resource_test.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/features" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" "github.com/hashicorp/terraform-provider-azurerm/utils" ) @@ -143,7 +144,8 @@ resource "azurerm_storage_account" "test" { } func (r LogAnalyticsLinkedStorageAccountResource) basic(data acceptance.TestData) string { - return fmt.Sprintf(` + if !features.FourPointOh() { + return fmt.Sprintf(` %s resource "azurerm_log_analytics_linked_storage_account" "test" { @@ -152,6 +154,18 @@ resource "azurerm_log_analytics_linked_storage_account" "test" { workspace_resource_id = azurerm_log_analytics_workspace.test.id storage_account_ids = [azurerm_storage_account.test.id] } +`, r.template(data)) + } + + return fmt.Sprintf(` +%s + +resource "azurerm_log_analytics_linked_storage_account" "test" { + data_source_type = "CustomLogs" + resource_group_name = azurerm_resource_group.test.name + workspace_resource_id = azurerm_log_analytics_workspace.test.id + storage_account_ids = [azurerm_storage_account.test.id] +} `, r.template(data)) } @@ -169,7 +183,8 @@ resource "azurerm_log_analytics_linked_storage_account" "import" { } func (r LogAnalyticsLinkedStorageAccountResource) complete(data acceptance.TestData) string { - return fmt.Sprintf(` + if !features.FourPointOh() { + return fmt.Sprintf(` %s resource "azurerm_storage_account" "test2" { @@ -186,6 +201,26 @@ resource "azurerm_log_analytics_linked_storage_account" "test" { workspace_resource_id = azurerm_log_analytics_workspace.test.id storage_account_ids = [azurerm_storage_account.test.id, azurerm_storage_account.test2.id] } +`, r.template(data), data.RandomString) + } + + return fmt.Sprintf(` +%s + +resource "azurerm_storage_account" "test2" { + name = "acctestsas%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "GRS" +} + +resource "azurerm_log_analytics_linked_storage_account" "test" { + data_source_type = "CustomLogs" + resource_group_name = azurerm_resource_group.test.name + workspace_resource_id = azurerm_log_analytics_workspace.test.id + storage_account_ids = [azurerm_storage_account.test.id, azurerm_storage_account.test2.id] +} `, r.template(data), data.RandomString) } @@ -194,7 +229,7 @@ func (r LogAnalyticsLinkedStorageAccountResource) ingestion(data acceptance.Test %s resource "azurerm_log_analytics_linked_storage_account" "test" { - data_source_type = "ingestion" + data_source_type = "Ingestion" resource_group_name = azurerm_resource_group.test.name workspace_resource_id = azurerm_log_analytics_workspace.test.id storage_account_ids = [azurerm_storage_account.test.id] diff --git a/website/docs/r/log_analytics_linked_storage_account.html.markdown b/website/docs/r/log_analytics_linked_storage_account.html.markdown index 06028846b720..ac20a8e56e15 100644 --- a/website/docs/r/log_analytics_linked_storage_account.html.markdown +++ b/website/docs/r/log_analytics_linked_storage_account.html.markdown @@ -34,7 +34,7 @@ resource "azurerm_log_analytics_workspace" "example" { } resource "azurerm_log_analytics_linked_storage_account" "example" { - data_source_type = "customlogs" + data_source_type = "CustomLogs" resource_group_name = azurerm_resource_group.example.name workspace_resource_id = azurerm_log_analytics_workspace.example.id storage_account_ids = [azurerm_storage_account.example.id] @@ -47,7 +47,7 @@ The following arguments are supported: * `data_source_type` - (Required) The data source type which should be used for this Log Analytics Linked Storage Account. Possible values are `CustomLogs`, `AzureWatson`, `Query`, `Ingestion` and `Alerts`. Changing this forces a new Log Analytics Linked Storage Account to be created. -> **Note:** The `data_source_type` is case-insensitive due to [service API issue](https://github.com/Azure/azure-rest-api-specs/issues/20619). After the API issue is fixed, case-sensitivity will be required in 4.0 or later versions. +> **Note:** The `data_source_type` is case-insensitive in current 3.x version. And in 4.0 or later versions, Case-sensitivity will be required. * `resource_group_name` - (Required) The name of the Resource Group where the Log Analytics Linked Storage Account should exist. Changing this forces a new Log Analytics Linked Storage Account to be created. From f707d01b088a71c4eb4b57acf46029f6b4728477 Mon Sep 17 00:00:00 2001 From: Tao <104055472+teowa@users.noreply.github.com> Date: Mon, 26 Sep 2022 10:27:30 +0800 Subject: [PATCH 5/5] add state migration --- ...alytics_linked_storage_account_resource.go | 6 +- ...cs_linked_storage_account_resource_test.go | 37 +--------- .../linked_storage_account_v0_to_v1.go | 69 +++++++++++++++++++ 3 files changed, 73 insertions(+), 39 deletions(-) create mode 100644 internal/services/loganalytics/migration/linked_storage_account_v0_to_v1.go diff --git a/internal/services/loganalytics/log_analytics_linked_storage_account_resource.go b/internal/services/loganalytics/log_analytics_linked_storage_account_resource.go index 94b5d131e3b4..f5e3878dd7e4 100644 --- a/internal/services/loganalytics/log_analytics_linked_storage_account_resource.go +++ b/internal/services/loganalytics/log_analytics_linked_storage_account_resource.go @@ -33,7 +33,7 @@ func resourceLogAnalyticsLinkedStorageAccount() *pluginsdk.Resource { }, Importer: pluginsdk.ImporterValidatingResourceId(func(id string) error { - _, err := linkedstorageaccounts.ParseDataSourceTypeIDInsensitively(id) // TODO remove insensitive parsing in 4.0 + _, err := linkedstorageaccounts.ParseDataSourceTypeID(id) return err }), @@ -120,7 +120,7 @@ func resourceLogAnalyticsLinkedStorageAccountRead(d *pluginsdk.ResourceData, met ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) defer cancel() - id, err := linkedstorageaccounts.ParseDataSourceTypeIDInsensitively(d.Id()) // TODO remove insensitive parsing in 4.0 + id, err := linkedstorageaccounts.ParseDataSourceTypeID(d.Id()) if err != nil { return err } @@ -156,7 +156,7 @@ func resourceLogAnalyticsLinkedStorageAccountDelete(d *pluginsdk.ResourceData, m ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) defer cancel() - id, err := linkedstorageaccounts.ParseDataSourceTypeIDInsensitively(d.Id()) // TODO remove insensitive parsing in 4.0 + id, err := linkedstorageaccounts.ParseDataSourceTypeID(d.Id()) if err != nil { return err } diff --git a/internal/services/loganalytics/log_analytics_linked_storage_account_resource_test.go b/internal/services/loganalytics/log_analytics_linked_storage_account_resource_test.go index db060bb10be5..c97e23e4fe68 100644 --- a/internal/services/loganalytics/log_analytics_linked_storage_account_resource_test.go +++ b/internal/services/loganalytics/log_analytics_linked_storage_account_resource_test.go @@ -9,7 +9,6 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" - "github.com/hashicorp/terraform-provider-azurerm/internal/features" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" "github.com/hashicorp/terraform-provider-azurerm/utils" ) @@ -102,7 +101,7 @@ func TestAcclogAnalyticsLinkedStorageAccount_ingestion(t *testing.T) { } func (t LogAnalyticsLinkedStorageAccountResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { - id, err := linkedstorageaccounts.ParseDataSourceTypeIDInsensitively(state.ID) // TODO remove insensitive parsing in 4.0 + id, err := linkedstorageaccounts.ParseDataSourceTypeID(state.ID) if err != nil { return nil, err } @@ -144,19 +143,6 @@ resource "azurerm_storage_account" "test" { } func (r LogAnalyticsLinkedStorageAccountResource) basic(data acceptance.TestData) string { - if !features.FourPointOh() { - return fmt.Sprintf(` -%s - -resource "azurerm_log_analytics_linked_storage_account" "test" { - data_source_type = "customlogs" - resource_group_name = azurerm_resource_group.test.name - workspace_resource_id = azurerm_log_analytics_workspace.test.id - storage_account_ids = [azurerm_storage_account.test.id] -} -`, r.template(data)) - } - return fmt.Sprintf(` %s @@ -183,27 +169,6 @@ resource "azurerm_log_analytics_linked_storage_account" "import" { } func (r LogAnalyticsLinkedStorageAccountResource) complete(data acceptance.TestData) string { - if !features.FourPointOh() { - return fmt.Sprintf(` -%s - -resource "azurerm_storage_account" "test2" { - name = "acctestsas%s" - resource_group_name = azurerm_resource_group.test.name - location = azurerm_resource_group.test.location - account_tier = "Standard" - account_replication_type = "GRS" -} - -resource "azurerm_log_analytics_linked_storage_account" "test" { - data_source_type = "customlogs" - resource_group_name = azurerm_resource_group.test.name - workspace_resource_id = azurerm_log_analytics_workspace.test.id - storage_account_ids = [azurerm_storage_account.test.id, azurerm_storage_account.test2.id] -} -`, r.template(data), data.RandomString) - } - return fmt.Sprintf(` %s diff --git a/internal/services/loganalytics/migration/linked_storage_account_v0_to_v1.go b/internal/services/loganalytics/migration/linked_storage_account_v0_to_v1.go new file mode 100644 index 000000000000..fc97088d6527 --- /dev/null +++ b/internal/services/loganalytics/migration/linked_storage_account_v0_to_v1.go @@ -0,0 +1,69 @@ +package migration + +import ( + "context" + + "github.com/hashicorp/go-azure-sdk/resource-manager/operationalinsights/2020-08-01/linkedstorageaccounts" + "github.com/hashicorp/terraform-provider-azurerm/helpers/azure" + "github.com/hashicorp/terraform-provider-azurerm/internal/features" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/suppress" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" +) + +var _ pluginsdk.StateUpgrade = LinkedStorageAccountV0ToV1{} + +type LinkedStorageAccountV0ToV1 struct{} + +func (LinkedStorageAccountV0ToV1) UpgradeFunc() pluginsdk.StateUpgraderFunc { + return func(ctx context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) { + oldId, err := linkedstorageaccounts.ParseDataSourceTypeIDInsensitively(rawState["id"].(string)) + if err != nil { + return rawState, err + } + + rawState["id"] = oldId.ID() + return rawState, nil + } +} + +func (LinkedStorageAccountV0ToV1) Schema() map[string]*pluginsdk.Schema { + schema := map[string]*pluginsdk.Schema{ + "data_source_type": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + string(linkedstorageaccounts.DataSourceTypeCustomLogs), + string(linkedstorageaccounts.DataSourceTypeAzureWatson), + string(linkedstorageaccounts.DataSourceTypeQuery), + string(linkedstorageaccounts.DataSourceTypeAlerts), + string(linkedstorageaccounts.DataSourceTypeIngestion), + }, !features.FourPointOhBeta()), + }, + + "resource_group_name": azure.SchemaResourceGroupName(), + + "workspace_resource_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: linkedstorageaccounts.ValidateWorkspaceID, + }, + + "storage_account_ids": { + Type: pluginsdk.TypeSet, + Required: true, + MinItems: 1, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: azure.ValidateResourceID, + }, + }, + } + + if !features.FourPointOh() { + schema["data_source_type"].DiffSuppressFunc = suppress.CaseDifference + } + return schema +}