diff --git a/internal/services/loadbalancer/loadbalancer_resource.go b/internal/services/loadbalancer/loadbalancer_resource.go index 292c3112dced..85d5a5ee9491 100644 --- a/internal/services/loadbalancer/loadbalancer_resource.go +++ b/internal/services/loadbalancer/loadbalancer_resource.go @@ -1,6 +1,7 @@ package loadbalancer import ( + "context" "fmt" "log" "strings" @@ -79,7 +80,6 @@ func resourceArmLoadBalancer() *pluginsdk.Resource { Optional: true, //Default: "Zone-Redundant", Computed: true, - ForceNew: true, ValidateFunc: validation.StringInSlice([]string{ "No-Zone", "1", @@ -177,7 +177,6 @@ func resourceArmLoadBalancer() *pluginsdk.Resource { Type: pluginsdk.TypeList, Optional: true, Computed: true, - ForceNew: true, Deprecated: "This property has been deprecated in favour of `availability_zone` due to a breaking behavioural change in Azure: https://azure.microsoft.com/en-us/updates/zone-behavior-change/", MaxItems: 1, Elem: &pluginsdk.Schema{ @@ -208,6 +207,25 @@ func resourceArmLoadBalancer() *pluginsdk.Resource { "tags": tags.Schema(), }, + + CustomizeDiff: pluginsdk.CustomizeDiffShim(func(ctx context.Context, d *pluginsdk.ResourceDiff, v interface{}) error { + if ok := d.HasChange("frontend_ip_configuration"); ok { + configs := d.Get("frontend_ip_configuration").([]interface{}) + + for index := range configs { + if d.HasChange(fmt.Sprintf("frontend_ip_configuration.%d.availability_zone", index)) && !d.HasChange(fmt.Sprintf("frontend_ip_configuration.%d.name", index)) { + return fmt.Errorf("in place change of the `frontend_ip_configuration.%[1]d.availability_zone` is not allowed. It is allowed to do this while also changing `frontend_ip_configuration.%[1]d.name`", index) + } + + // TODO - Remove in 3.0 + if d.HasChange(fmt.Sprintf("frontend_ip_configuration.%d.zones", index)) && !d.HasChange(fmt.Sprintf("frontend_ip_configuration.%d.name", index)) { + return fmt.Errorf("in place change of the `frontend_ip_configuration.%[1]d.zones` is not allowed. It is allowed to do this while also changing `frontend_ip_configuration.%[1]d.name`", index) + } + } + } + + return nil + }), } } diff --git a/internal/services/loadbalancer/loadbalancer_resource_test.go b/internal/services/loadbalancer/loadbalancer_resource_test.go index eba0f7f9034d..dc0af0d64c6e 100644 --- a/internal/services/loadbalancer/loadbalancer_resource_test.go +++ b/internal/services/loadbalancer/loadbalancer_resource_test.go @@ -165,6 +165,42 @@ func TestAccAzureRMLoadBalancer_privateIP(t *testing.T) { }) } +func TestAccAzureRMLoadBalancer_updateFrontEndConfigsWithZone(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_lb", "test") + r := LoadBalancer{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.availability_zone_update1(data, "Zone-Redundant"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.availability_zone_update1(data, "No-Zone"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.availability_zone_update1(data, "Zone-Redundant"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.availability_zone_update2(data, "Zone-Redundant"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func TestAccAzureRMLoadBalancer_ZoneRedundant(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_lb", "test") r := LoadBalancer{} @@ -585,3 +621,98 @@ resource "azurerm_lb" "test" { } `, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger, zone) } + +func (r LoadBalancer) availability_zone_update1(data acceptance.TestData, zone string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-lb-%[1]d" + location = "%[2]s" +} + +resource "azurerm_virtual_network" "test" { + name = "acctvn-%[1]d" + address_space = ["10.0.0.0/16"] + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} + +resource "azurerm_subnet" "test" { + name = "acctsub-%[1]d" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test.name + address_prefix = "10.0.2.0/24" +} + +resource "azurerm_lb" "test" { + name = "acctestlb-%[1]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + sku = "Standard" + + frontend_ip_configuration { + name = "Internal-%[3]s" + private_ip_address_allocation = "Static" + private_ip_address_version = "IPv4" + private_ip_address = "10.0.2.7" + subnet_id = azurerm_subnet.test.id + availability_zone = "%[3]s" + } +} +`, data.RandomInteger, data.Locations.Primary, zone) +} + +func (r LoadBalancer) availability_zone_update2(data acceptance.TestData, zone string) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-lb-%[1]d" + location = "%[2]s" +} + +resource "azurerm_virtual_network" "test" { + name = "acctvn-%[1]d" + address_space = ["10.0.0.0/16"] + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} + +resource "azurerm_subnet" "test" { + name = "acctsub-%[1]d" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test.name + address_prefix = "10.0.2.0/24" +} + +resource "azurerm_lb" "test" { + name = "acctestlb-%[1]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + sku = "Standard" + + frontend_ip_configuration { + name = "Internal-%[3]s" + private_ip_address_allocation = "Static" + private_ip_address_version = "IPv4" + private_ip_address = "10.0.2.7" + subnet_id = azurerm_subnet.test.id + availability_zone = "%[3]s" + } + + frontend_ip_configuration { + name = "Internal2-%[3]s" + private_ip_address_allocation = "Static" + private_ip_address_version = "IPv4" + private_ip_address = "10.0.2.8" + subnet_id = azurerm_subnet.test.id + availability_zone = "%[3]s" + } +} +`, data.RandomInteger, data.Locations.Primary, zone) +} diff --git a/website/docs/r/lb.html.markdown b/website/docs/r/lb.html.markdown index 19dcd905fb7d..59a7ae27cc43 100644 --- a/website/docs/r/lb.html.markdown +++ b/website/docs/r/lb.html.markdown @@ -52,7 +52,7 @@ The following arguments are supported: `frontend_ip_configuration` supports the following: * `name` - (Required) Specifies the name of the frontend ip configuration. -* `availability_zone` - (Optional) A list of Availability Zones which the Load Balancer's IP Addresses should be created in. Possible values are `Zone-Redundant`, `1`, `2`, `3`, and `No-Zone`. Defaults to `Zone-Redundant`. +* `availability_zone` - (Optional) A list of Availability Zones which the Load Balancer's IP Addresses should be created in. Possible values are `Zone-Redundant`, `1`, `2`, `3`, and `No-Zone`. Availability Zone can only be updated whenever the name of the front end ip configuration changes. Defaults to `Zone-Redundant`. `No-Zones` - A `non-zonal` resource will be created and the resource will not be replicated or distributed to any Availability Zones. `1`, `2` or `3` (e.g. single Availability Zone) - A `zonal` resource will be created and will be replicate or distribute to a single specific Availability Zone.