diff --git a/internal/services/network/private_endpoint_resource.go b/internal/services/network/private_endpoint_resource.go index decfaaa03e7a..f889bfe4c340 100644 --- a/internal/services/network/private_endpoint_resource.go +++ b/internal/services/network/private_endpoint_resource.go @@ -290,6 +290,9 @@ func resourcePrivateEndpointCreate(d *pluginsdk.ResourceData, meta interface{}) //goland:noinspection GoDeferInLoop defer locks.UnlockByName(cosmosDbResId, "azurerm_private_endpoint") } + locks.ByName(subnetId, "azurerm_private_endpoint") + defer locks.UnlockByName(subnetId, "azurerm_private_endpoint") + err = pluginsdk.Retry(d.Timeout(pluginsdk.TimeoutCreate), func() *resource.RetryError { future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.Name, parameters) if err != nil { @@ -309,7 +312,7 @@ func resourcePrivateEndpointCreate(d *pluginsdk.ResourceData, meta interface{}) if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { return &resource.RetryError{ Err: fmt.Errorf("waiting for creation of Private Endpoint %q (Resource Group %q): %+v", id.Name, id.ResourceGroup, err), - Retryable: strings.Contains(strings.ToLower(err.Error()), "retryable"), + Retryable: strings.Contains(strings.ToLower(err.Error()), "Resource is in Updating state and the last operation that updated/is updating the resource is PutSubnetOperation"), } } return nil @@ -395,6 +398,9 @@ func resourcePrivateEndpointUpdate(d *pluginsdk.ResourceData, meta interface{}) Tags: tags.Expand(d.Get("tags").(map[string]interface{})), } + locks.ByName(subnetId, "azurerm_private_endpoint") + defer locks.UnlockByName(subnetId, "azurerm_private_endpoint") + future, err := client.CreateOrUpdate(ctx, id.ResourceGroup, id.Name, parameters) if err != nil { if strings.EqualFold(err.Error(), "is missing required parameter 'group Id'") { @@ -563,6 +569,7 @@ func resourcePrivateEndpointDelete(d *pluginsdk.ResourceData, meta interface{}) } log.Printf("[DEBUG] Deleted the Private DNS Zone Group associated with Private Endpoint %q / Resource Group %q.", id.Name, id.ResourceGroup) + subnetId := d.Get("subnet_id").(string) privateServiceConnections := d.Get("private_service_connection").([]interface{}) parameters := network.PrivateEndpoint{ PrivateEndpointProperties: &network.PrivateEndpointProperties{ @@ -576,6 +583,8 @@ func resourcePrivateEndpointDelete(d *pluginsdk.ResourceData, meta interface{}) //goland:noinspection GoDeferInLoop defer locks.UnlockByName(cosmosDbResId, "azurerm_private_endpoint") } + locks.ByName(subnetId, "azurerm_private_endpoint") + defer locks.UnlockByName(subnetId, "azurerm_private_endpoint") log.Printf("[DEBUG] Deleting the Private Endpoint %q / Resource Group %q..", id.Name, id.ResourceGroup) future, err := client.Delete(ctx, id.ResourceGroup, id.Name) @@ -679,7 +688,7 @@ func flattenPrivateLinkEndpointServiceConnection(serviceConnections *[]network.P // There is a bug from service, the PE created from portal could be with the connection id for postgresql server "Microsoft.DBForPostgreSQL" instead of "Microsoft.DBforPostgreSQL" // and for Mysql and MariaDB if strings.Contains(strings.ToLower(privateConnectionId), "microsoft.dbforpostgresql") { - if serverId, err := postgresqlParse.ServerID(privateConnectionId); err == nil { + if serverId, err := servers.ParseServerID(privateConnectionId); err == nil { privateConnectionId = serverId.ID() } } @@ -741,7 +750,7 @@ func flattenPrivateLinkEndpointServiceConnection(serviceConnections *[]network.P // There is a bug from service, the PE created from portal could be with the connection id for postgresql server "Microsoft.DBForPostgreSQL" instead of "Microsoft.DBforPostgreSQL" // and for Mysql and MariaDB if strings.Contains(strings.ToLower(privateConnectionId), "microsoft.dbforpostgresql") { - if serverId, err := postgresqlParse.ServerID(privateConnectionId); err == nil { + if serverId, err := servers.ParseServerID(privateConnectionId); err == nil { privateConnectionId = serverId.ID() } } diff --git a/internal/services/network/private_endpoint_resource_test.go b/internal/services/network/private_endpoint_resource_test.go index 56bd2a674ce1..f3d1728217f9 100644 --- a/internal/services/network/private_endpoint_resource_test.go +++ b/internal/services/network/private_endpoint_resource_test.go @@ -242,6 +242,25 @@ func (t PrivateEndpointResource) Exists(ctx context.Context, clients *clients.Cl return utils.Bool(resp.ID != nil), nil } +func TestAccPrivateEndpoint_multipleInstances(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_private_endpoint", "test") + r := PrivateEndpointResource{} + + instanceCount := 5 + var checks []pluginsdk.TestCheckFunc + for i := 0; i < instanceCount; i++ { + checks = append(checks, check.That(fmt.Sprintf("%s.%d", data.ResourceName, i)).ExistsInAzure(r)) + } + + config := r.multipleInstances(data, instanceCount) + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: config, + Check: acceptance.ComposeTestCheckFunc(checks...), + }, + }) +} + func (PrivateEndpointResource) template(data acceptance.TestData, seviceCfg string) string { return fmt.Sprintf(` provider "azurerm" { @@ -748,3 +767,23 @@ resource "azurerm_private_endpoint" "test" { } `, r.template(data, r.serviceAutoApprove(data)), data.RandomInteger) } + +func (r PrivateEndpointResource) multipleInstances(data acceptance.TestData, count int) string { + return fmt.Sprintf(` +%s + +resource "azurerm_private_endpoint" "test" { + count = %d + name = "acctest-privatelink-%d-${count.index}" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + subnet_id = azurerm_subnet.endpoint.id + + private_service_connection { + name = azurerm_private_link_service.test.name + is_manual_connection = false + private_connection_resource_id = azurerm_private_link_service.test.id + } +} +`, r.template(data, r.serviceAutoApprove(data)), count, data.RandomInteger) +}