From 6e807e3d97b9a85234dc9b418f2cfc5f549572c0 Mon Sep 17 00:00:00 2001 From: Matthew Frahry Date: Tue, 14 May 2019 17:46:29 -0600 Subject: [PATCH] [WIP] Bug Fix: `azurerm_application_gateway` - updatable gateway subnet (#3437) --- azurerm/resource_arm_application_gateway.go | 58 ++++++++- .../resource_arm_application_gateway_test.go | 114 ++++++++++++++++++ 2 files changed, 169 insertions(+), 3 deletions(-) diff --git a/azurerm/resource_arm_application_gateway.go b/azurerm/resource_arm_application_gateway.go index 4651499c1348..aa5e9fa4ff59 100644 --- a/azurerm/resource_arm_application_gateway.go +++ b/azurerm/resource_arm_application_gateway.go @@ -302,6 +302,7 @@ func resourceArmApplicationGateway() *schema.Resource { "gateway_ip_configuration": { Type: schema.TypeList, Required: true, + MaxItems: 2, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "name": { @@ -1094,7 +1095,7 @@ func resourceArmApplicationGatewayCreateUpdate(d *schema.ResourceData, meta inte backendHTTPSettingsCollection := expandApplicationGatewayBackendHTTPSettings(d, gatewayID) frontendIPConfigurations := expandApplicationGatewayFrontendIPConfigurations(d) frontendPorts := expandApplicationGatewayFrontendPorts(d) - gatewayIPConfigurations := expandApplicationGatewayIPConfigurations(d) + gatewayIPConfigurations, stopApplicationGateway := expandApplicationGatewayIPConfigurations(d) httpListeners := expandApplicationGatewayHTTPListeners(d, gatewayID) probes := expandApplicationGatewayProbes(d) sku := expandApplicationGatewaySku(d) @@ -1175,6 +1176,17 @@ func resourceArmApplicationGatewayCreateUpdate(d *schema.ResourceData, meta inte gateway.ApplicationGatewayPropertiesFormat.WebApplicationFirewallConfiguration = expandApplicationGatewayWafConfig(d) } + if stopApplicationGateway { + future, err := client.Stop(ctx, resGroup, name) + if err != nil { + return fmt.Errorf("Error Stopping Application Gateway %q (Resource Group %q): %+v", name, resGroup, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for the Application Gateway %q (Resource Group %q) to stop: %+v", name, resGroup, err) + } + } + future, err := client.CreateOrUpdate(ctx, resGroup, name, gateway) if err != nil { return fmt.Errorf("Error Creating/Updating Application Gateway %q (Resource Group %q): %+v", name, resGroup, err) @@ -1184,6 +1196,17 @@ func resourceArmApplicationGatewayCreateUpdate(d *schema.ResourceData, meta inte return fmt.Errorf("Error waiting for the create/update of Application Gateway %q (Resource Group %q): %+v", name, resGroup, err) } + if stopApplicationGateway { + future, err := client.Start(ctx, resGroup, name) + if err != nil { + return fmt.Errorf("Error Starting Application Gateway %q (Resource Group %q): %+v", name, resGroup, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for the Application Gateway %q (Resource Group %q) to start: %+v", name, resGroup, err) + } + } + read, err := client.Get(ctx, resGroup, name) if err != nil { return fmt.Errorf("Error retrieving Application Gateway %q (Resource Group %q): %+v", name, resGroup, err) @@ -1839,9 +1862,10 @@ func flattenApplicationGatewayHTTPListeners(input *[]network.ApplicationGatewayH return results, nil } -func expandApplicationGatewayIPConfigurations(d *schema.ResourceData) *[]network.ApplicationGatewayIPConfiguration { +func expandApplicationGatewayIPConfigurations(d *schema.ResourceData) (*[]network.ApplicationGatewayIPConfiguration, bool) { vs := d.Get("gateway_ip_configuration").([]interface{}) results := make([]network.ApplicationGatewayIPConfiguration, 0) + stopApplicationGateway := false for _, configRaw := range vs { data := configRaw.(map[string]interface{}) @@ -1860,7 +1884,35 @@ func expandApplicationGatewayIPConfigurations(d *schema.ResourceData) *[]network results = append(results, output) } - return &results + if d.HasChange("gateway_ip_configuration") { + oldRaw, newRaw := d.GetChange("gateway_ip_configuration") + oldVS := oldRaw.([]interface{}) + newVS := newRaw.([]interface{}) + + // If we're creating the application gateway return the current gateway ip configuration. + if len(oldVS) == 0 { + return &results, stopApplicationGateway + } + + // The application gateway needs to be stopped if a gateway ip configuration is added or removed + if len(oldVS) != len(newVS) { + stopApplicationGateway = true + } + + for i, configRaw := range newVS { + newData := configRaw.(map[string]interface{}) + oldData := oldVS[i].(map[string]interface{}) + + newSubnetID := newData["subnet_id"].(string) + oldSubnetID := oldData["subnet_id"].(string) + // The application gateway needs to be shutdown if the subnet ids don't match + if newSubnetID != oldSubnetID { + stopApplicationGateway = true + } + } + } + + return &results, stopApplicationGateway } func flattenApplicationGatewayIPConfigurations(input *[]network.ApplicationGatewayIPConfiguration) []interface{} { diff --git a/azurerm/resource_arm_application_gateway_test.go b/azurerm/resource_arm_application_gateway_test.go index e632d07b87df..e32ce027d42a 100644 --- a/azurerm/resource_arm_application_gateway_test.go +++ b/azurerm/resource_arm_application_gateway_test.go @@ -778,6 +778,41 @@ func TestAccAzureRMApplicationGateway_cookieAffinity(t *testing.T) { }) } +func TestAccAzureRMApplicationGateway_gatewayIP(t *testing.T) { + resourceName := "azurerm_application_gateway.test" + ri := tf.AccRandTimeInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMApplicationGatewayDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMApplicationGateway_basic(ri, testLocation()), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMApplicationGatewayExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAzureRMApplicationGateway_gatewayIPUpdated(ri, testLocation()), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMApplicationGatewayExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testCheckAzureRMApplicationGatewayExists(resourceName string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[resourceName] @@ -3260,3 +3295,82 @@ resource "azurerm_application_gateway" "test" { } `, template, rInt) } + +func testAccAzureRMApplicationGateway_gatewayIPUpdated(rInt int, location string) string { + template := testAccAzureRMApplicationGateway_template(rInt, location) + return fmt.Sprintf(` +%s + +resource "azurerm_subnet" "test1" { + name = "subnet1-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + virtual_network_name = "${azurerm_virtual_network.test.name}" + address_prefix = "10.0.1.0/24" +} + + +# since these variables are re-used - a locals block makes this more maintainable +locals { + backend_address_pool_name = "${azurerm_virtual_network.test.name}-beap" + frontend_port_name = "${azurerm_virtual_network.test.name}-feport" + frontend_ip_configuration_name = "${azurerm_virtual_network.test.name}-feip" + http_setting_name = "${azurerm_virtual_network.test.name}-be-htst" + listener_name = "${azurerm_virtual_network.test.name}-httplstn" + request_routing_rule_name = "${azurerm_virtual_network.test.name}-rqrt" +} + +resource "azurerm_application_gateway" "test" { + name = "acctestag-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + + sku { + name = "Standard_Small" + tier = "Standard" + capacity = 2 + } + + gateway_ip_configuration { + name = "my-gateway-ip-configuration" + subnet_id = "${azurerm_subnet.test1.id}" + } + + frontend_port { + name = "${local.frontend_port_name}" + port = 80 + } + + frontend_ip_configuration { + name = "${local.frontend_ip_configuration_name}" + public_ip_address_id = "${azurerm_public_ip.test.id}" + } + + backend_address_pool { + name = "${local.backend_address_pool_name}" + } + + backend_http_settings { + name = "${local.http_setting_name}" + cookie_based_affinity = "Disabled" + port = 80 + protocol = "Http" + request_timeout = 1 + } + + http_listener { + name = "${local.listener_name}" + frontend_ip_configuration_name = "${local.frontend_ip_configuration_name}" + frontend_port_name = "${local.frontend_port_name}" + protocol = "Http" + } + + request_routing_rule { + name = "${local.request_routing_rule_name}" + rule_type = "Basic" + http_listener_name = "${local.listener_name}" + backend_address_pool_name = "${local.backend_address_pool_name}" + backend_http_settings_name = "${local.http_setting_name}" + } +} +`, template, rInt, rInt) +}