diff --git a/azurerm/internal/services/network/network_interface_application_security_group_association_migration.go b/azurerm/internal/services/network/network_interface_application_security_group_association_migration.go new file mode 100644 index 000000000000..2899e7d66fc2 --- /dev/null +++ b/azurerm/internal/services/network/network_interface_application_security_group_association_migration.go @@ -0,0 +1,43 @@ +package network + +import ( + "fmt" + "log" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" +) + +func resourceNetworkInterfaceApplicationSecurityGroupAssociationUpgradeV0Schema() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "network_interface_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateResourceID, + }, + + "application_security_group_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateResourceID, + }, + }, + } +} + +func resourceNetworkInterfaceApplicationSecurityGroupAssociationUpgradeV0ToV1(rawState map[string]interface{}, _ interface{}) (map[string]interface{}, error) { + // after shipping support for this Resource Azure's since changed the behaviour to require that all IP Configurations + // are connected to the same Application Security Group + applicationSecurityGroupId := rawState["application_security_group_id"].(string) + networkInterfaceId := rawState["network_interface_id"].(string) + + oldID := rawState["id"].(string) + newID := fmt.Sprintf("%s|%s", networkInterfaceId, applicationSecurityGroupId) + log.Printf("[DEBUG] Updating ID from %q to %q", oldID, newID) + + rawState["id"] = newID + return rawState, nil +} diff --git a/azurerm/internal/services/network/resource_arm_network_interface_application_security_group_association.go b/azurerm/internal/services/network/network_interface_application_security_group_association_resource.go similarity index 61% rename from azurerm/internal/services/network/resource_arm_network_interface_application_security_group_association.go rename to azurerm/internal/services/network/network_interface_application_security_group_association_resource.go index b093079d3432..77104acd8b76 100644 --- a/azurerm/internal/services/network/resource_arm_network_interface_application_security_group_association.go +++ b/azurerm/internal/services/network/network_interface_application_security_group_association_resource.go @@ -6,13 +6,10 @@ import ( "strings" "time" - "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-09-01/network" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/locks" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" @@ -27,6 +24,15 @@ func resourceArmNetworkInterfaceApplicationSecurityGroupAssociation() *schema.Re State: schema.ImportStatePassthrough, }, + SchemaVersion: 1, + StateUpgraders: []schema.StateUpgrader{ + { + Type: resourceNetworkInterfaceApplicationSecurityGroupAssociationUpgradeV0Schema().CoreConfigSchema().ImpliedType(), + Upgrade: resourceNetworkInterfaceApplicationSecurityGroupAssociationUpgradeV0ToV1, + Version: 0, + }, + }, + Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(30 * time.Minute), Read: schema.DefaultTimeout(5 * time.Minute), @@ -42,13 +48,6 @@ func resourceArmNetworkInterfaceApplicationSecurityGroupAssociation() *schema.Re ValidateFunc: azure.ValidateResourceID, }, - "ip_configuration_name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - "application_security_group_id": { Type: schema.TypeString, Required: true, @@ -67,7 +66,6 @@ func resourceArmNetworkInterfaceApplicationSecurityGroupAssociationCreate(d *sch log.Printf("[INFO] preparing arguments for Network Interface <-> Application Security Group Association creation.") networkInterfaceId := d.Get("network_interface_id").(string) - ipConfigurationName := d.Get("ip_configuration_name").(string) applicationSecurityGroupId := d.Get("application_security_group_id").(string) id, err := azure.ParseAzureResourceID(networkInterfaceId) @@ -94,49 +92,19 @@ func resourceArmNetworkInterfaceApplicationSecurityGroupAssociationCreate(d *sch if props == nil { return fmt.Errorf("Error: `properties` was nil for Network Interface %q (Resource Group %q)", networkInterfaceName, resourceGroup) } - - ipConfigs := props.IPConfigurations - if ipConfigs == nil { - return fmt.Errorf("Error: `properties.IPConfigurations` was nil for Network Interface %q (Resource Group %q)", networkInterfaceName, resourceGroup) - } - - c := azure.FindNetworkInterfaceIPConfiguration(props.IPConfigurations, ipConfigurationName) - if c == nil { - return fmt.Errorf("Error: IP Configuration %q was not found on Network Interface %q (Resource Group %q)", ipConfigurationName, networkInterfaceName, resourceGroup) - } - - config := *c - p := config.InterfaceIPConfigurationPropertiesFormat - if p == nil { - return fmt.Errorf("Error: `IPConfiguration.properties` was nil for Network Interface %q (Resource Group %q)", networkInterfaceName, resourceGroup) + if props.IPConfigurations == nil { + return fmt.Errorf("Error: `properties.ipConfigurations` was nil for Network Interface %q (Resource Group %q)", networkInterfaceName, resourceGroup) } - applicationSecurityGroups := make([]network.ApplicationSecurityGroup, 0) - - // first double-check it doesn't exist - if p.ApplicationSecurityGroups != nil { - for _, existingGroup := range *p.ApplicationSecurityGroups { - if id := existingGroup.ID; id != nil { - if *id == applicationSecurityGroupId { - if features.ShouldResourcesBeImported() { - return tf.ImportAsExistsError("azurerm_network_interface_application_security_group_association", *id) - } - - continue - } - - applicationSecurityGroups = append(applicationSecurityGroups, existingGroup) - } - } + info := parseFieldsFromNetworkInterface(*props) + resourceId := fmt.Sprintf("%s|%s", networkInterfaceId, applicationSecurityGroupId) + if azure.SliceContainsValue(info.applicationSecurityGroupIDs, applicationSecurityGroupId) { + return tf.ImportAsExistsError("azurerm_network_interface_application_security_group_association", resourceId) } - group := network.ApplicationSecurityGroup{ - ID: utils.String(applicationSecurityGroupId), - } - applicationSecurityGroups = append(applicationSecurityGroups, group) - p.ApplicationSecurityGroups = &applicationSecurityGroups + info.applicationSecurityGroupIDs = append(info.applicationSecurityGroupIDs, applicationSecurityGroupId) - props.IPConfigurations = azure.UpdateNetworkInterfaceIPConfiguration(config, props.IPConfigurations) + read.InterfacePropertiesFormat.IPConfigurations = mapFieldsToNetworkInterface(props.IPConfigurations, info) future, err := client.CreateOrUpdate(ctx, resourceGroup, networkInterfaceName, read) if err != nil { @@ -147,7 +115,6 @@ func resourceArmNetworkInterfaceApplicationSecurityGroupAssociationCreate(d *sch return fmt.Errorf("Error waiting for completion of Application Security Group Association for NIC %q (Resource Group %q): %+v", networkInterfaceName, resourceGroup, err) } - resourceId := fmt.Sprintf("%s/ipConfigurations/%s|%s", networkInterfaceId, ipConfigurationName, applicationSecurityGroupId) d.SetId(resourceId) return resourceArmNetworkInterfaceApplicationSecurityGroupAssociationRead(d, meta) @@ -160,7 +127,7 @@ func resourceArmNetworkInterfaceApplicationSecurityGroupAssociationRead(d *schem splitId := strings.Split(d.Id(), "|") if len(splitId) != 2 { - return fmt.Errorf("Expected ID to be in the format {networkInterfaceId}/ipConfigurations/{ipConfigurationName}|{applicationSecurityGroupId} but got %q", d.Id()) + return fmt.Errorf("Expected ID to be in the format {networkInterfaceId}|{applicationSecurityGroupId} but got %q", d.Id()) } nicID, err := azure.ParseAzureResourceID(splitId[0]) @@ -168,7 +135,6 @@ func resourceArmNetworkInterfaceApplicationSecurityGroupAssociationRead(d *schem return err } - ipConfigurationName := nicID.Path["ipConfigurations"] networkInterfaceName := nicID.Path["networkInterfaces"] resourceGroup := nicID.ResourceGroup applicationSecurityGroupId := splitId[1] @@ -187,43 +153,21 @@ func resourceArmNetworkInterfaceApplicationSecurityGroupAssociationRead(d *schem return fmt.Errorf("Error: `properties` was nil for Network Interface %q (Resource Group %q)", networkInterfaceName, resourceGroup) } - ipConfigs := nicProps.IPConfigurations - if ipConfigs == nil { - return fmt.Errorf("Error: `properties.IPConfigurations` was nil for Network Interface %q (Resource Group %q)", networkInterfaceName, resourceGroup) - } - - c := azure.FindNetworkInterfaceIPConfiguration(nicProps.IPConfigurations, ipConfigurationName) - if c == nil { - log.Printf("IP Configuration %q was not found in Network Interface %q (Resource Group %q) - removing from state!", ipConfigurationName, networkInterfaceName, resourceGroup) - d.SetId("") - return nil - } - config := *c - - found := false - if props := config.InterfaceIPConfigurationPropertiesFormat; props != nil { - if groups := props.ApplicationSecurityGroups; groups != nil { - for _, group := range *groups { - if group.ID == nil { - continue - } - - if *group.ID == applicationSecurityGroupId { - found = true - break - } - } + info := parseFieldsFromNetworkInterface(*nicProps) + exists := false + for _, groupId := range info.applicationSecurityGroupIDs { + if groupId == applicationSecurityGroupId { + exists = true } } - if !found { + if !exists { log.Printf("[DEBUG] Association between Network Interface %q (Resource Group %q) and Application Security Group %q was not found - removing from state!", networkInterfaceName, resourceGroup, applicationSecurityGroupId) d.SetId("") return nil } d.Set("application_security_group_id", applicationSecurityGroupId) - d.Set("ip_configuration_name", ipConfigurationName) d.Set("network_interface_id", read.ID) return nil @@ -236,7 +180,7 @@ func resourceArmNetworkInterfaceApplicationSecurityGroupAssociationDelete(d *sch splitId := strings.Split(d.Id(), "|") if len(splitId) != 2 { - return fmt.Errorf("Expected ID to be in the format {networkInterfaceId}/ipConfigurations/{ipConfigurationName}|{applicationSecurityGroupId} but got %q", d.Id()) + return fmt.Errorf("Expected ID to be in the format {networkInterfaceId}|{applicationSecurityGroupId} but got %q", d.Id()) } nicID, err := azure.ParseAzureResourceID(splitId[0]) @@ -244,7 +188,6 @@ func resourceArmNetworkInterfaceApplicationSecurityGroupAssociationDelete(d *sch return err } - ipConfigurationName := nicID.Path["ipConfigurations"] networkInterfaceName := nicID.Path["networkInterfaces"] resourceGroup := nicID.ResourceGroup applicationSecurityGroupId := splitId[1] @@ -261,41 +204,25 @@ func resourceArmNetworkInterfaceApplicationSecurityGroupAssociationDelete(d *sch return fmt.Errorf("Error retrieving Network Interface %q (Resource Group %q): %+v", networkInterfaceName, resourceGroup, err) } - nicProps := read.InterfacePropertiesFormat - if nicProps == nil { + props := read.InterfacePropertiesFormat + if props == nil { return fmt.Errorf("Error: `properties` was nil for Network Interface %q (Resource Group %q)", networkInterfaceName, resourceGroup) } - ipConfigs := nicProps.IPConfigurations - if ipConfigs == nil { - return fmt.Errorf("Error: `properties.IPConfigurations` was nil for Network Interface %q (Resource Group %q)", networkInterfaceName, resourceGroup) - } - - c := azure.FindNetworkInterfaceIPConfiguration(nicProps.IPConfigurations, ipConfigurationName) - if c == nil { - return fmt.Errorf("Error: IP Configuration %q was not found on Network Interface %q (Resource Group %q)", ipConfigurationName, networkInterfaceName, resourceGroup) - } - config := *c - - props := config.InterfaceIPConfigurationPropertiesFormat - if props == nil { - return fmt.Errorf("Error: Properties for IPConfiguration %q was nil for Network Interface %q (Resource Group %q)", ipConfigurationName, networkInterfaceName, resourceGroup) + if props.IPConfigurations == nil { + return fmt.Errorf("Error: `properties.ipConfigurations` was nil for Network Interface %q (Resource Group %q)", networkInterfaceName, resourceGroup) } - applicationSecurityGroups := make([]network.ApplicationSecurityGroup, 0) - if groups := props.ApplicationSecurityGroups; groups != nil { - for _, pool := range *groups { - if pool.ID == nil { - continue - } + info := parseFieldsFromNetworkInterface(*props) - if *pool.ID != applicationSecurityGroupId { - applicationSecurityGroups = append(applicationSecurityGroups, pool) - } + applicationSecurityGroupIds := make([]string, 0) + for _, v := range info.applicationSecurityGroupIDs { + if v != applicationSecurityGroupId { + applicationSecurityGroupIds = append(applicationSecurityGroupIds, v) } } - props.ApplicationSecurityGroups = &applicationSecurityGroups - nicProps.IPConfigurations = azure.UpdateNetworkInterfaceIPConfiguration(config, nicProps.IPConfigurations) + info.applicationSecurityGroupIDs = applicationSecurityGroupIds + read.InterfacePropertiesFormat.IPConfigurations = mapFieldsToNetworkInterface(props.IPConfigurations, info) future, err := client.CreateOrUpdate(ctx, resourceGroup, networkInterfaceName, read) if err != nil { diff --git a/azurerm/internal/services/network/tests/resource_arm_network_interface_application_security_group_association_test.go b/azurerm/internal/services/network/tests/network_interface_application_security_group_association_resource_test.go similarity index 84% rename from azurerm/internal/services/network/tests/resource_arm_network_interface_application_security_group_association_test.go rename to azurerm/internal/services/network/tests/network_interface_application_security_group_association_resource_test.go index bd544711f3e1..b49d17802ca6 100644 --- a/azurerm/internal/services/network/tests/resource_arm_network_interface_application_security_group_association_test.go +++ b/azurerm/internal/services/network/tests/network_interface_application_security_group_association_resource_test.go @@ -32,6 +32,25 @@ func TestAccAzureRMNetworkInterfaceApplicationSecurityGroupAssociation_basic(t * }) } +func TestAccAzureRMNetworkInterfaceApplicationSecurityGroupAssociation_multipleIPConfigurations(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_network_interface_application_security_group_association", "test") + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + // intentional as this is a Virtual Resource + CheckDestroy: testCheckAzureRMNetworkInterfaceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMNetworkInterfaceApplicationSecurityGroupAssociation_multipleIPConfigurations(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMNetworkInterfaceApplicationSecurityGroupAssociationExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + func TestAccAzureRMNetworkInterfaceApplicationSecurityGroupAssociation_requiresImport(t *testing.T) { if !features.ShouldResourcesBeImported() { t.Skip("Skipping since resources aren't required to be imported") @@ -242,13 +261,43 @@ func testAccAzureRMNetworkInterfaceApplicationSecurityGroupAssociation_requiresI %s resource "azurerm_network_interface_application_security_group_association" "import" { - network_interface_id = "${azurerm_network_interface_application_security_group_association.test.network_interface_id}" - ip_configuration_name = "${azurerm_network_interface_application_security_group_association.test.ip_configuration_name}" - application_security_group_id = "${azurerm_network_interface_application_security_group_association.test.application_security_group_id}" + network_interface_id = azurerm_network_interface_application_security_group_association.test.network_interface_id + application_security_group_id = azurerm_network_interface_application_security_group_association.test.application_security_group_id } `, template) } +func testAccAzureRMNetworkInterfaceApplicationSecurityGroupAssociation_multipleIPConfigurations(data acceptance.TestData) string { + template := testAccAzureRMNetworkInterfaceApplicationSecurityGroupAssociation_template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_network_interface" "test" { + name = "acctestni-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + ip_configuration { + name = "testconfiguration1" + subnet_id = azurerm_subnet.test.id + private_ip_address_allocation = "Dynamic" + } + + ip_configuration { + name = "testconfiguration2" + subnet_id = azurerm_subnet.test.id + private_ip_address_allocation = "Dynamic" + } +} + +resource "azurerm_network_interface_application_security_group_association" "test" { + network_interface_id = azurerm_network_interface.test.id + ip_configuration_name = "testconfiguration1" + application_security_group_id = azurerm_application_security_group.test.id +} +`, template, data.RandomInteger) +} + func testAccAzureRMNetworkInterfaceApplicationSecurityGroupAssociation_updateNIC(data acceptance.TestData) string { template := testAccAzureRMNetworkInterfaceApplicationSecurityGroupAssociation_template(data) return fmt.Sprintf(` diff --git a/website/docs/guides/2.0-upgrade-guide.html.markdown b/website/docs/guides/2.0-upgrade-guide.html.markdown index ec9400b09848..7ffbb20b2e76 100644 --- a/website/docs/guides/2.0-upgrade-guide.html.markdown +++ b/website/docs/guides/2.0-upgrade-guide.html.markdown @@ -438,6 +438,10 @@ The `load_balancer_backend_address_pools_ids` field in the `ip_configuration` bl The `load_balancer_inbound_nat_rules_ids` field in the `ip_configuration` block will been removed. This has been replaced by the `azurerm_network_interface_nat_rule_association` resource. +### Resource: `azurerm_network_interface_application_security_group_association` + +The field `ip_configuration_name` has been removed since the same Application Security Group must now be assigned to all (IPv4) IP Configurations. + ### Resource: `azurerm_notification_hub_namespace` The deprecated `sku` block has been replaced by the `sku_name` field and will be removed. diff --git a/website/docs/r/network_interface_application_security_group_association.html.markdown b/website/docs/r/network_interface_application_security_group_association.html.markdown index 7d23d403342e..2a09e57ec263 100644 --- a/website/docs/r/network_interface_application_security_group_association.html.markdown +++ b/website/docs/r/network_interface_application_security_group_association.html.markdown @@ -54,7 +54,6 @@ resource "azurerm_network_interface" "example" { resource "azurerm_network_interface_application_security_group_association" "example" { network_interface_id = azurerm_network_interface.example.id - ip_configuration_name = "testconfiguration1" application_security_group_id = azurerm_application_security_group.example.id } ``` @@ -65,8 +64,6 @@ The following arguments are supported: * `network_interface_id` - (Required) The ID of the Network Interface. Changing this forces a new resource to be created. -* `ip_configuration_name` - (Required) The Name of the IP Configuration within the Network Interface which should be connected to the Application Security Group. Changing this forces a new resource to be created. - * `application_security_group_id` - (Required) The ID of the Application Security Group which this Network Interface which should be connected to. Changing this forces a new resource to be created. ## Attributes Reference @@ -88,9 +85,8 @@ The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/d Associations between Network Interfaces and Application Security Groups can be imported using the `resource id`, e.g. - ```shell -terraform import azurerm_network_interface_application_security_group_association.association1 /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/mygroup1/providers/microsoft.network/networkInterfaces/nic1/ipConfigurations/example|/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.Network/applicationSecurityGroups/securityGroup1 +terraform import azurerm_network_interface_application_security_group_association.association1 "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/mygroup1/providers/microsoft.network/networkInterfaces/nic1|/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.Network/applicationSecurityGroups/securityGroup1" ``` --> **NOTE:** This ID is specific to Terraform - and is of the format `{networkInterfaceId}/ipConfigurations/{ipConfigurationName}|{applicationSecurityGroupId}`. +-> **NOTE:** This ID is specific to Terraform - and is of the format `{networkInterfaceId}|{applicationSecurityGroupId}`.