diff --git a/internal/services/databricks/databricks_workspace_resource.go b/internal/services/databricks/databricks_workspace_resource.go index 392581543742..5fa0fbd9e072 100644 --- a/internal/services/databricks/databricks_workspace_resource.go +++ b/internal/services/databricks/databricks_workspace_resource.go @@ -599,7 +599,7 @@ func resourceDatabricksWorkspaceRead(d *pluginsdk.ResourceData, meta interface{} d.Set("sku", sku.Name) } - managedResourceGroupID, err := resourcesParse.ResourceGroupID(model.Properties.ManagedResourceGroupId) + managedResourceGroupID, err := resourcesParse.ResourceGroupIDInsensitively(model.Properties.ManagedResourceGroupId) if err != nil { return err } @@ -820,7 +820,7 @@ func flattenWorkspaceCustomParameters(input *workspaces.WorkspaceCustomParameter parameters["virtual_network_id"] = v.Value } - lbId, err := loadBalancerParse.LoadBalancerID(loadBalancerId) + lbId, err := loadBalancerParse.LoadBalancerIDInsensitively(loadBalancerId) if err == nil { backendId := loadBalancerParse.NewLoadBalancerBackendAddressPoolID(lbId.SubscriptionId, lbId.ResourceGroup, lbId.Name, backendName) diff --git a/internal/services/dataprotection/data_protection_backup_instance_disk_resource.go b/internal/services/dataprotection/data_protection_backup_instance_disk_resource.go index 46e185d9687f..367df1990a63 100644 --- a/internal/services/dataprotection/data_protection_backup_instance_disk_resource.go +++ b/internal/services/dataprotection/data_protection_backup_instance_disk_resource.go @@ -185,7 +185,7 @@ func resourceDataProtectionBackupInstanceDiskRead(d *schema.ResourceData, meta i parameter := (*props.PolicyInfo.PolicyParameters.DataStoreParametersList)[0].(backupinstances.AzureOperationalStoreParameters) if parameter.ResourceGroupId != nil { - resourceGroupId, err := resourceParse.ResourceGroupID(*parameter.ResourceGroupId) + resourceGroupId, err := resourceParse.ResourceGroupIDInsensitively(*parameter.ResourceGroupId) if err != nil { return err } diff --git a/internal/services/loadbalancer/nat_pool_resource.go b/internal/services/loadbalancer/nat_pool_resource.go index 8f8190f8fb7d..9ad8fefef9f5 100644 --- a/internal/services/loadbalancer/nat_pool_resource.go +++ b/internal/services/loadbalancer/nat_pool_resource.go @@ -222,7 +222,7 @@ func resourceArmLoadBalancerNatPoolRead(d *pluginsdk.ResourceData, meta interfac frontendIPConfigName := "" frontendIPConfigID := "" if props.FrontendIPConfiguration != nil && props.FrontendIPConfiguration.ID != nil { - feid, err := parse.LoadBalancerFrontendIpConfigurationID(*props.FrontendIPConfiguration.ID) + feid, err := parse.LoadBalancerFrontendIpConfigurationIDInsensitively(*props.FrontendIPConfiguration.ID) if err != nil { return err } diff --git a/internal/services/loadbalancer/nat_rule_resource.go b/internal/services/loadbalancer/nat_rule_resource.go index 4458de23ae68..3183885285db 100644 --- a/internal/services/loadbalancer/nat_rule_resource.go +++ b/internal/services/loadbalancer/nat_rule_resource.go @@ -256,7 +256,7 @@ func resourceArmLoadBalancerNatRuleRead(d *pluginsdk.ResourceData, meta interfac frontendIPConfigName := "" frontendIPConfigID := "" if props.FrontendIPConfiguration != nil && props.FrontendIPConfiguration.ID != nil { - feid, err := parse.LoadBalancerFrontendIpConfigurationID(*props.FrontendIPConfiguration.ID) + feid, err := parse.LoadBalancerFrontendIpConfigurationIDInsensitively(*props.FrontendIPConfiguration.ID) if err != nil { return err } diff --git a/internal/services/loadbalancer/outbound_rule_resource.go b/internal/services/loadbalancer/outbound_rule_resource.go index dd6d7d7a31bb..7a05825b9847 100644 --- a/internal/services/loadbalancer/outbound_rule_resource.go +++ b/internal/services/loadbalancer/outbound_rule_resource.go @@ -218,7 +218,7 @@ func resourceArmLoadBalancerOutboundRuleRead(d *pluginsdk.ResourceData, meta int backendAddressPoolId := "" if props.BackendAddressPool != nil && props.BackendAddressPool.ID != nil { - bapid, err := parse.LoadBalancerBackendAddressPoolID(*props.BackendAddressPool.ID) + bapid, err := parse.LoadBalancerBackendAddressPoolIDInsensitively(*props.BackendAddressPool.ID) if err != nil { return err } @@ -234,7 +234,7 @@ func resourceArmLoadBalancerOutboundRuleRead(d *pluginsdk.ResourceData, meta int if feConfig.ID == nil { continue } - feid, err := parse.LoadBalancerFrontendIpConfigurationID(*feConfig.ID) + feid, err := parse.LoadBalancerFrontendIpConfigurationIDInsensitively(*feConfig.ID) if err != nil { return err } diff --git a/internal/services/loadbalancer/parse/load_balancer.go b/internal/services/loadbalancer/parse/load_balancer.go index c02aa6a28ab4..6c2128cee2b4 100644 --- a/internal/services/loadbalancer/parse/load_balancer.go +++ b/internal/services/loadbalancer/parse/load_balancer.go @@ -67,3 +67,47 @@ func LoadBalancerID(input string) (*LoadBalancerId, error) { return &resourceId, nil } + +// LoadBalancerIDInsensitively parses an LoadBalancer ID into an LoadBalancerId struct, insensitively +// This should only be used to parse an ID for rewriting, the LoadBalancerID +// 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 LoadBalancerIDInsensitively(input string) (*LoadBalancerId, error) { + id, err := resourceids.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + resourceId := LoadBalancerId{ + 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 'loadBalancers' segment + loadBalancersKey := "loadBalancers" + for key := range id.Path { + if strings.EqualFold(key, loadBalancersKey) { + loadBalancersKey = key + break + } + } + if resourceId.Name, err = id.PopSegment(loadBalancersKey); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &resourceId, nil +} diff --git a/internal/services/loadbalancer/parse/load_balancer_backend_address_pool.go b/internal/services/loadbalancer/parse/load_balancer_backend_address_pool.go index c04e65842713..50e86c62adaf 100644 --- a/internal/services/loadbalancer/parse/load_balancer_backend_address_pool.go +++ b/internal/services/loadbalancer/parse/load_balancer_backend_address_pool.go @@ -73,3 +73,59 @@ func LoadBalancerBackendAddressPoolID(input string) (*LoadBalancerBackendAddress return &resourceId, nil } + +// LoadBalancerBackendAddressPoolIDInsensitively parses an LoadBalancerBackendAddressPool ID into an LoadBalancerBackendAddressPoolId struct, insensitively +// This should only be used to parse an ID for rewriting, the LoadBalancerBackendAddressPoolID +// 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 LoadBalancerBackendAddressPoolIDInsensitively(input string) (*LoadBalancerBackendAddressPoolId, error) { + id, err := resourceids.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + resourceId := LoadBalancerBackendAddressPoolId{ + 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 'loadBalancers' segment + loadBalancersKey := "loadBalancers" + for key := range id.Path { + if strings.EqualFold(key, loadBalancersKey) { + loadBalancersKey = key + break + } + } + if resourceId.LoadBalancerName, err = id.PopSegment(loadBalancersKey); err != nil { + return nil, err + } + + // find the correct casing for the 'backendAddressPools' segment + backendAddressPoolsKey := "backendAddressPools" + for key := range id.Path { + if strings.EqualFold(key, backendAddressPoolsKey) { + backendAddressPoolsKey = key + break + } + } + if resourceId.BackendAddressPoolName, err = id.PopSegment(backendAddressPoolsKey); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &resourceId, nil +} diff --git a/internal/services/loadbalancer/parse/load_balancer_backend_address_pool_test.go b/internal/services/loadbalancer/parse/load_balancer_backend_address_pool_test.go index e17fbe84f2b1..3d72b4a61155 100644 --- a/internal/services/loadbalancer/parse/load_balancer_backend_address_pool_test.go +++ b/internal/services/loadbalancer/parse/load_balancer_backend_address_pool_test.go @@ -126,3 +126,139 @@ func TestLoadBalancerBackendAddressPoolID(t *testing.T) { } } } + +func TestLoadBalancerBackendAddressPoolIDInsensitively(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *LoadBalancerBackendAddressPoolId + }{ + + { + // 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 LoadBalancerName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/", + Error: true, + }, + + { + // missing value for LoadBalancerName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/loadBalancers/", + Error: true, + }, + + { + // missing BackendAddressPoolName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/loadBalancers/loadBalancer1/", + Error: true, + }, + + { + // missing value for BackendAddressPoolName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/loadBalancers/loadBalancer1/backendAddressPools/", + Error: true, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/loadBalancers/loadBalancer1/backendAddressPools/backendAddressPool1", + Expected: &LoadBalancerBackendAddressPoolId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + LoadBalancerName: "loadBalancer1", + BackendAddressPoolName: "backendAddressPool1", + }, + }, + + { + // lower-cased segment names + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/loadbalancers/loadBalancer1/backendaddresspools/backendAddressPool1", + Expected: &LoadBalancerBackendAddressPoolId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + LoadBalancerName: "loadBalancer1", + BackendAddressPoolName: "backendAddressPool1", + }, + }, + + { + // upper-cased segment names + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/LOADBALANCERS/loadBalancer1/BACKENDADDRESSPOOLS/backendAddressPool1", + Expected: &LoadBalancerBackendAddressPoolId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + LoadBalancerName: "loadBalancer1", + BackendAddressPoolName: "backendAddressPool1", + }, + }, + + { + // mixed-cased segment names + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/LoAdBaLaNcErS/loadBalancer1/BaCkEnDaDdReSsPoOlS/backendAddressPool1", + Expected: &LoadBalancerBackendAddressPoolId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + LoadBalancerName: "loadBalancer1", + BackendAddressPoolName: "backendAddressPool1", + }, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := LoadBalancerBackendAddressPoolIDInsensitively(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.LoadBalancerName != v.Expected.LoadBalancerName { + t.Fatalf("Expected %q but got %q for LoadBalancerName", v.Expected.LoadBalancerName, actual.LoadBalancerName) + } + if actual.BackendAddressPoolName != v.Expected.BackendAddressPoolName { + t.Fatalf("Expected %q but got %q for BackendAddressPoolName", v.Expected.BackendAddressPoolName, actual.BackendAddressPoolName) + } + } +} diff --git a/internal/services/loadbalancer/parse/load_balancer_frontend_ip_configuration.go b/internal/services/loadbalancer/parse/load_balancer_frontend_ip_configuration.go index f69cce579cb5..8536e0dcc3fc 100644 --- a/internal/services/loadbalancer/parse/load_balancer_frontend_ip_configuration.go +++ b/internal/services/loadbalancer/parse/load_balancer_frontend_ip_configuration.go @@ -73,3 +73,59 @@ func LoadBalancerFrontendIpConfigurationID(input string) (*LoadBalancerFrontendI return &resourceId, nil } + +// LoadBalancerFrontendIpConfigurationIDInsensitively parses an LoadBalancerFrontendIpConfiguration ID into an LoadBalancerFrontendIpConfigurationId struct, insensitively +// This should only be used to parse an ID for rewriting, the LoadBalancerFrontendIpConfigurationID +// 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 LoadBalancerFrontendIpConfigurationIDInsensitively(input string) (*LoadBalancerFrontendIpConfigurationId, error) { + id, err := resourceids.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + resourceId := LoadBalancerFrontendIpConfigurationId{ + 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 'loadBalancers' segment + loadBalancersKey := "loadBalancers" + for key := range id.Path { + if strings.EqualFold(key, loadBalancersKey) { + loadBalancersKey = key + break + } + } + if resourceId.LoadBalancerName, err = id.PopSegment(loadBalancersKey); err != nil { + return nil, err + } + + // find the correct casing for the 'frontendIPConfigurations' segment + frontendIPConfigurationsKey := "frontendIPConfigurations" + for key := range id.Path { + if strings.EqualFold(key, frontendIPConfigurationsKey) { + frontendIPConfigurationsKey = key + break + } + } + if resourceId.FrontendIPConfigurationName, err = id.PopSegment(frontendIPConfigurationsKey); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &resourceId, nil +} diff --git a/internal/services/loadbalancer/parse/load_balancer_frontend_ip_configuration_test.go b/internal/services/loadbalancer/parse/load_balancer_frontend_ip_configuration_test.go index e51a6be40c5f..b73929b0f5f4 100644 --- a/internal/services/loadbalancer/parse/load_balancer_frontend_ip_configuration_test.go +++ b/internal/services/loadbalancer/parse/load_balancer_frontend_ip_configuration_test.go @@ -126,3 +126,139 @@ func TestLoadBalancerFrontendIpConfigurationID(t *testing.T) { } } } + +func TestLoadBalancerFrontendIpConfigurationIDInsensitively(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *LoadBalancerFrontendIpConfigurationId + }{ + + { + // 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 LoadBalancerName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/", + Error: true, + }, + + { + // missing value for LoadBalancerName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/loadBalancers/", + Error: true, + }, + + { + // missing FrontendIPConfigurationName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/loadBalancers/loadBalancer1/", + Error: true, + }, + + { + // missing value for FrontendIPConfigurationName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/loadBalancers/loadBalancer1/frontendIPConfigurations/", + Error: true, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/loadBalancers/loadBalancer1/frontendIPConfigurations/frontendIPConfig1", + Expected: &LoadBalancerFrontendIpConfigurationId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + LoadBalancerName: "loadBalancer1", + FrontendIPConfigurationName: "frontendIPConfig1", + }, + }, + + { + // lower-cased segment names + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/loadbalancers/loadBalancer1/frontendipconfigurations/frontendIPConfig1", + Expected: &LoadBalancerFrontendIpConfigurationId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + LoadBalancerName: "loadBalancer1", + FrontendIPConfigurationName: "frontendIPConfig1", + }, + }, + + { + // upper-cased segment names + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/LOADBALANCERS/loadBalancer1/FRONTENDIPCONFIGURATIONS/frontendIPConfig1", + Expected: &LoadBalancerFrontendIpConfigurationId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + LoadBalancerName: "loadBalancer1", + FrontendIPConfigurationName: "frontendIPConfig1", + }, + }, + + { + // mixed-cased segment names + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/LoAdBaLaNcErS/loadBalancer1/FrOnTeNdIpCoNfIgUrAtIoNs/frontendIPConfig1", + Expected: &LoadBalancerFrontendIpConfigurationId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + LoadBalancerName: "loadBalancer1", + FrontendIPConfigurationName: "frontendIPConfig1", + }, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := LoadBalancerFrontendIpConfigurationIDInsensitively(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.LoadBalancerName != v.Expected.LoadBalancerName { + t.Fatalf("Expected %q but got %q for LoadBalancerName", v.Expected.LoadBalancerName, actual.LoadBalancerName) + } + if actual.FrontendIPConfigurationName != v.Expected.FrontendIPConfigurationName { + t.Fatalf("Expected %q but got %q for FrontendIPConfigurationName", v.Expected.FrontendIPConfigurationName, actual.FrontendIPConfigurationName) + } + } +} diff --git a/internal/services/loadbalancer/parse/load_balancer_test.go b/internal/services/loadbalancer/parse/load_balancer_test.go index 3e0308dfeb1c..aa80ba1a6529 100644 --- a/internal/services/loadbalancer/parse/load_balancer_test.go +++ b/internal/services/loadbalancer/parse/load_balancer_test.go @@ -110,3 +110,120 @@ func TestLoadBalancerID(t *testing.T) { } } } + +func TestLoadBalancerIDInsensitively(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *LoadBalancerId + }{ + + { + // 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 Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/", + Error: true, + }, + + { + // missing value for Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/loadBalancers/", + Error: true, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/loadBalancers/loadBalancer1", + Expected: &LoadBalancerId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + Name: "loadBalancer1", + }, + }, + + { + // lower-cased segment names + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/loadbalancers/loadBalancer1", + Expected: &LoadBalancerId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + Name: "loadBalancer1", + }, + }, + + { + // upper-cased segment names + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/LOADBALANCERS/loadBalancer1", + Expected: &LoadBalancerId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + Name: "loadBalancer1", + }, + }, + + { + // mixed-cased segment names + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/LoAdBaLaNcErS/loadBalancer1", + Expected: &LoadBalancerId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + Name: "loadBalancer1", + }, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := LoadBalancerIDInsensitively(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.Name != v.Expected.Name { + t.Fatalf("Expected %q but got %q for Name", v.Expected.Name, actual.Name) + } + } +} diff --git a/internal/services/loadbalancer/resourceids.go b/internal/services/loadbalancer/resourceids.go index 50c74cff5037..7882cd4eb98e 100644 --- a/internal/services/loadbalancer/resourceids.go +++ b/internal/services/loadbalancer/resourceids.go @@ -1,10 +1,10 @@ package loadbalancer // Load Balancers -//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=LoadBalancer -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/loadBalancers/loadBalancer1 +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=LoadBalancer -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/loadBalancers/loadBalancer1 -rewrite=true //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=BackendAddressPoolAddress -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/loadBalancers/loadBalancer1/backendAddressPools/backendAddressPool1/addresses/address1 -//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=LoadBalancerBackendAddressPool -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/loadBalancers/loadBalancer1/backendAddressPools/backendAddressPool1 -//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=LoadBalancerFrontendIpConfiguration -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/loadBalancers/loadBalancer1/frontendIPConfigurations/frontendIPConfig1 +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=LoadBalancerBackendAddressPool -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/loadBalancers/loadBalancer1/backendAddressPools/backendAddressPool1 -rewrite=true +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=LoadBalancerFrontendIpConfiguration -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/loadBalancers/loadBalancer1/frontendIPConfigurations/frontendIPConfig1 -rewrite=true //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=LoadBalancerInboundNatPool -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/loadBalancers/loadBalancer1/inboundNatPools/pool1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=LoadBalancerInboundNatRule -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/loadBalancers/loadBalancer1/inboundNatRules/rule1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=LoadBalancerOutboundRule -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Network/loadBalancers/loadBalancer1/outboundRules/rule1 diff --git a/internal/services/loadbalancer/rule_resource.go b/internal/services/loadbalancer/rule_resource.go index a4c23da8f51a..1281c2ef8ea4 100644 --- a/internal/services/loadbalancer/rule_resource.go +++ b/internal/services/loadbalancer/rule_resource.go @@ -182,7 +182,7 @@ func resourceArmLoadBalancerRuleRead(d *pluginsdk.ResourceData, meta interface{} frontendIPConfigName := "" frontendIPConfigID := "" if props.FrontendIPConfiguration != nil && props.FrontendIPConfiguration.ID != nil { - feid, err := parse.LoadBalancerFrontendIpConfigurationID(*props.FrontendIPConfiguration.ID) + feid, err := parse.LoadBalancerFrontendIpConfigurationIDInsensitively(*props.FrontendIPConfiguration.ID) if err != nil { return err } diff --git a/internal/services/managedapplications/managed_application_resource.go b/internal/services/managedapplications/managed_application_resource.go index 8ea51586068a..dcd5d1759e13 100644 --- a/internal/services/managedapplications/managed_application_resource.go +++ b/internal/services/managedapplications/managed_application_resource.go @@ -227,7 +227,7 @@ func resourceManagedApplicationRead(d *pluginsdk.ResourceData, meta interface{}) return fmt.Errorf("setting `plan`: %+v", err) } if props := resp.ApplicationProperties; props != nil { - id, err := resourcesParse.ResourceGroupID(*props.ManagedResourceGroupID) + id, err := resourcesParse.ResourceGroupIDInsensitively(*props.ManagedResourceGroupID) if err != nil { return err } diff --git a/internal/services/resource/parse/resource_group.go b/internal/services/resource/parse/resource_group.go index 955329323f02..d8e0235fad52 100644 --- a/internal/services/resource/parse/resource_group.go +++ b/internal/services/resource/parse/resource_group.go @@ -60,3 +60,35 @@ func ResourceGroupID(input string) (*ResourceGroupId, error) { return &resourceId, nil } + +// ResourceGroupIDInsensitively parses an ResourceGroup ID into an ResourceGroupId struct, insensitively +// This should only be used to parse an ID for rewriting, the ResourceGroupID +// 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 ResourceGroupIDInsensitively(input string) (*ResourceGroupId, error) { + id, err := resourceids.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + resourceId := ResourceGroupId{ + 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") + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &resourceId, nil +} diff --git a/internal/services/resource/parse/resource_group_test.go b/internal/services/resource/parse/resource_group_test.go index 0139c401af3e..1399e603154d 100644 --- a/internal/services/resource/parse/resource_group_test.go +++ b/internal/services/resource/parse/resource_group_test.go @@ -94,3 +94,101 @@ func TestResourceGroupID(t *testing.T) { } } } + +func TestResourceGroupIDInsensitively(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *ResourceGroupId + }{ + + { + // 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, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1", + Expected: &ResourceGroupId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "group1", + }, + }, + + { + // lower-cased segment names + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1", + Expected: &ResourceGroupId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "group1", + }, + }, + + { + // upper-cased segment names + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1", + Expected: &ResourceGroupId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "group1", + }, + }, + + { + // mixed-cased segment names + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1", + Expected: &ResourceGroupId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "group1", + }, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := ResourceGroupIDInsensitively(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) + } + } +} diff --git a/internal/services/resource/resourceids.go b/internal/services/resource/resourceids.go index 19394977544a..7ad6fcf446a9 100644 --- a/internal/services/resource/resourceids.go +++ b/internal/services/resource/resourceids.go @@ -1,6 +1,6 @@ package resource -//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=ResourceGroup -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1 +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=ResourceGroup -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1 -rewrite=true //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -rewrite=true -name=ResourceGroupTemplateDeployment -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.Resources/deployments/deploy1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=SubscriptionTemplateDeployment -id=/subscriptions/12345678-1234-9876-4563-123456789012/providers/Microsoft.Resources/deployments/deploy1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=TemplateSpecVersion -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/templateSpecRG/providers/Microsoft.Resources/templateSpecs/templateSpec1/versions/v1.0