diff --git a/internal/services/network/private_endpoint_resource.go b/internal/services/network/private_endpoint_resource.go index b3d1cffaa1026..71bedeadd101a 100644 --- a/internal/services/network/private_endpoint_resource.go +++ b/internal/services/network/private_endpoint_resource.go @@ -19,6 +19,7 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/helpers/azure" "github.com/hashicorp/terraform-provider-azurerm/helpers/tf" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/features" "github.com/hashicorp/terraform-provider-azurerm/internal/locks" cosmosParse "github.com/hashicorp/terraform-provider-azurerm/internal/services/cosmos/parse" mysqlParse "github.com/hashicorp/terraform-provider-azurerm/internal/services/mysql/parse" @@ -175,7 +176,6 @@ func resourcePrivateEndpoint() *pluginsdk.Resource { "ip_configuration": { Type: pluginsdk.TypeList, Optional: true, - MaxItems: 1, Elem: &pluginsdk.Resource{ Schema: map[string]*pluginsdk.Schema{ "name": { @@ -196,6 +196,14 @@ func resourcePrivateEndpoint() *pluginsdk.Resource { ForceNew: true, ValidateFunc: validation.StringIsNotEmpty, }, + "member_name": { + Type: pluginsdk.TypeString, + Required: features.FourPointOhBeta(), + Optional: !features.FourPointOhBeta(), + Computed: !features.FourPointOhBeta(), + ForceNew: true, + ValidateFunc: validation.StringIsNotEmpty, + }, }, }, }, @@ -763,13 +771,17 @@ func expandPrivateEndpointIPConfigurations(input []interface{}) *[]network.Priva v := item.(map[string]interface{}) privateIPAddress := v["private_ip_address"].(string) subResourceName := v["subresource_name"].(string) + memberName := v["member_name"].(string) + if memberName == "" { + memberName = subResourceName + } name := v["name"].(string) result := network.PrivateEndpointIPConfiguration{ Name: utils.String(name), PrivateEndpointIPConfigurationProperties: &network.PrivateEndpointIPConfigurationProperties{ PrivateIPAddress: utils.String(privateIPAddress), GroupID: utils.String(subResourceName), - MemberName: utils.String(subResourceName), + MemberName: utils.String(memberName), }, } results = append(results, result) @@ -789,6 +801,7 @@ func flattenPrivateEndpointIPConfigurations(ipConfigurations *[]network.PrivateE "name": item.Name, "private_ip_address": item.PrivateIPAddress, "subresource_name": item.GroupID, + "member_name": item.MemberName, }) } diff --git a/internal/services/network/private_endpoint_resource_test.go b/internal/services/network/private_endpoint_resource_test.go index a7bd55273e00a..d00d022ea0539 100644 --- a/internal/services/network/private_endpoint_resource_test.go +++ b/internal/services/network/private_endpoint_resource_test.go @@ -307,6 +307,21 @@ func TestAccPrivateEndpoint_multipleInstances(t *testing.T) { }) } +func TestAccPrivateEndpoint_multipleIpConfigurations(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_private_endpoint", "test") + r := PrivateEndpointResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.recoveryServiceVaultWithMultiIpConfig(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func (PrivateEndpointResource) template(data acceptance.TestData, seviceCfg string) string { return fmt.Sprintf(` provider "azurerm" { @@ -316,7 +331,7 @@ provider "azurerm" { data "azurerm_subscription" "current" {} resource "azurerm_resource_group" "test" { - name = "zjhe-acctestRG-privatelink-%d" + name = "acctestRG-privatelink-%d" location = "%s" } @@ -501,7 +516,7 @@ provider "azurerm" { } resource "azurerm_resource_group" "test" { - name = "zjhe-acctestRG-privatelink-%d" + name = "acctestRG-privatelink-%d" location = "%s" } @@ -581,7 +596,7 @@ provider "azurerm" { } resource "azurerm_resource_group" "test" { - name = "zjhe-acctestRG-privatelink-%d" + name = "acctestRG-privatelink-%d" location = "%s" } @@ -656,7 +671,7 @@ provider "azurerm" { } resource "azurerm_resource_group" "test" { - name = "zjhe-acctestRG-privatelink-%d" + name = "acctestRG-privatelink-%d" location = "%s" } @@ -741,7 +756,7 @@ provider "azurerm" { } resource "azurerm_resource_group" "test" { - name = "zjhe-acctestRG-privatelink-%d" + name = "acctestRG-privatelink-%d" location = "%s" } @@ -862,3 +877,57 @@ resource "azurerm_private_endpoint" "test" { } `, r.template(data, r.serviceAutoApprove(data)), count, data.RandomInteger) } + +func (r PrivateEndpointResource) recoveryServiceVaultWithMultiIpConfig(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +locals { + ip_configs = { + "SiteRecovery-prot2" = "10.5.2.24" + "SiteRecovery-srs1" = "10.5.2.25" + "SiteRecovery-id1" = "10.5.2.26" + "SiteRecovery-tel1" = "10.5.2.27" + "SiteRecovery-rcm1" = "10.5.2.28" + } +} + +resource "azurerm_recovery_services_vault" "test" { + name = "acctest-vault-%[2]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + sku = "Standard" + + soft_delete_enabled = false + + identity { + type = "SystemAssigned" + } +} + +resource "azurerm_private_endpoint" "test" { + name = "acctest-privatelink-%[2]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + subnet_id = azurerm_subnet.endpoint.id + + private_service_connection { + name = "acctest-privatelink-%[2]d" + is_manual_connection = false + subresource_names = ["AzureSiteRecovery"] + private_connection_resource_id = azurerm_recovery_services_vault.test.id + } + + dynamic "ip_configuration" { + for_each = local.ip_configs + + content { + name = ip_configuration.key + private_ip_address = ip_configuration.value + subresource_name = "AzureSiteRecovery" + member_name = ip_configuration.key + } + } +} +`, r.template(data, r.serviceAutoApprove(data)), data.RandomInteger) +} diff --git a/website/docs/r/private_endpoint.html.markdown b/website/docs/r/private_endpoint.html.markdown index f6292ca182a05..3e7e17bbea2cf 100644 --- a/website/docs/r/private_endpoint.html.markdown +++ b/website/docs/r/private_endpoint.html.markdown @@ -146,7 +146,7 @@ The following arguments are supported: * `private_service_connection` - (Required) A `private_service_connection` block as defined below. -* `ip_configuration` - (Optional) An `ip_configuration` block as defined below. This allows a static IP address to be set for this Private Endpoint, otherwise an address is dynamically allocated from the Subnet. At most one IP configuration is allowed. Changing this forces a new resource to be created. +* `ip_configuration` - (Optional) One or more `ip_configuration` blocks as defined below. This allows a static IP address to be set for this Private Endpoint, otherwise an address is dynamically allocated from the Subnet. Changing this forces a new resource to be created. * `tags` - (Optional) A mapping of tags to assign to the resource. @@ -176,18 +176,20 @@ A `private_service_connection` supports the following: -> Several possible values for this field are shown below, however this is not extensive: -| Resource Type | SubResource Name | Secondary SubResource Name | -| ----------------------------- | ---------------- | -------------------------- | -| Data Lake File System Gen2 | dfs | dfs_secondary | -| SQL Database / Data Warehouse | sqlServer | | -| SQL Managed Instance | managedInstance | | -| Storage Account | blob | blob_secondary | -| Storage Account | file | file_secondary | -| Storage Account | queue | queue_secondary | -| Storage Account | table | table_secondary | -| Storage Account | web | web_secondary | -| Web App / Function App | sites | | +| Resource Type | SubResource Name | Secondary SubResource Name | +|-------------------------------|------------------------|----------------------------| +| Data Lake File System Gen2 | dfs | dfs_secondary | +| SQL Database / Data Warehouse | sqlServer | | +| SQL Managed Instance | managedInstance | | +| Storage Account | blob | blob_secondary | +| Storage Account | file | file_secondary | +| Storage Account | queue | queue_secondary | +| Storage Account | table | table_secondary | +| Storage Account | web | web_secondary | +| Web App / Function App | sites | | | Web App / Function App Slots | sites-<slotName> | | +| Recovery Services Vault | AzureBackup | | +| Recovery Services Vault | AzureSiteRecovery | | Some resource types (such as Storage Account) only support 1 subresource per private endpoint. See the product [documentation](https://docs.microsoft.com/azure/private-link/private-endpoint-overview#private-link-resource) for more information. @@ -197,11 +199,15 @@ Some resource types (such as Storage Account) only support 1 subresource per pri An `ip_configuration` supports the following: -* `name` - (Required) Specifies the Name of the IP Configuration. Changing this forces a new resource to be created. +* `name` - (Required) Specifies the Name of the IP Configuration. Changing this forces a new resource to be created. -* `private_ip_address` - (Required) Specifies the static IP address within the private endpoint's subnet to be used. Changing this forces a new resource to be created. +* `private_ip_address` - (Required) Specifies the static IP address within the private endpoint's subnet to be used. Changing this forces a new resource to be created. -* `subresource_name` - (Required) Specifies the subresource this IP address applies to. `subresource_names` corresponds to `group_id` and in this context is also used for `member_name`. Changing this forces a new resource to be created. +* `subresource_name` - (Required) Specifies the subresource this IP address applies to. `subresource_names` corresponds to `group_id`. Changing this forces a new resource to be created. + +* `member_name` - (Optional) Specifies the member name this IP address applies to. If it is not specified, it will use the value of `subresource_name`. Changing this forces a new resource to be created. + +-> **NOTE:** `member_name` will be required and will not take the value of `subresource_name` in the next major version. ## Attributes Reference