diff --git a/.changelog/4017.txt b/.changelog/4017.txt new file mode 100644 index 0000000000..23d0e41502 --- /dev/null +++ b/.changelog/4017.txt @@ -0,0 +1,15 @@ +```release-note:enhancement +resource/mongodbatlas_privatelink_endpoint_service: Adds `port_mapping_enabled` attribute +``` + +```release-note:enhancement +resource/mongodbatlas_privatelink_endpoint: Adds `port_mapping_enabled` attribute +``` + +```release-note:enhancement +data-source/mongodbatlas_privatelink_endpoint_service: Adds `port_mapping_enabled` attribute +``` + +```release-note:enhancement +data-source/mongodbatlas_privatelink_endpoint: Adds `port_mapping_enabled` attribute +``` diff --git a/docs/data-sources/privatelink_endpoint.md b/docs/data-sources/privatelink_endpoint.md index 20300b9784..4ac537e1ed 100644 --- a/docs/data-sources/privatelink_endpoint.md +++ b/docs/data-sources/privatelink_endpoint.md @@ -58,5 +58,6 @@ In addition to all arguments above, the following attributes are exported: * `endpoint_group_names` - GCP network endpoint groups corresponding to the Private Service Connect endpoint service. * `region_name` - GCP region for the Private Service Connect endpoint service. * `service_attachment_names` - Unique alphanumeric and special character strings that identify the service attachments associated with the GCP Private Service Connect endpoint service. +* `port_mapping_enabled` - Flag that indicates whether this endpoint service uses PSC port-mapping. See [MongoDB Atlas API](https://docs.atlas.mongodb.com/reference/api/private-endpoints-service-get-one/) Documentation for more information. \ No newline at end of file diff --git a/docs/data-sources/privatelink_endpoint_service.md b/docs/data-sources/privatelink_endpoint_service.md index 7ee6314bd7..e09a95a9cb 100644 --- a/docs/data-sources/privatelink_endpoint_service.md +++ b/docs/data-sources/privatelink_endpoint_service.md @@ -122,5 +122,6 @@ In addition to all arguments above, the following attributes are exported: * `endpoint_name` - Forwarding rule that corresponds to the endpoint you created in GCP. * `ip_address` - Private IP address of the network endpoint group you created in GCP. * `status` - Status of the endpoint. Atlas returns one of the [values shown above](https://docs.atlas.mongodb.com/reference/api/private-endpoints-endpoint-create-one/#std-label-ref-status-field). +* `port_mapping_enabled` - Flag that indicates whether this endpoint service uses PSC port-mapping. See [MongoDB Atlas API](https://docs.atlas.mongodb.com/reference/api/private-endpoints-endpoint-get-one/) Documentation for more information. diff --git a/docs/resources/privatelink_endpoint.md b/docs/resources/privatelink_endpoint.md index f9ce741ef8..12a5c360a8 100644 --- a/docs/resources/privatelink_endpoint.md +++ b/docs/resources/privatelink_endpoint.md @@ -37,7 +37,8 @@ resource "mongodbatlas_privatelink_endpoint" "test" { ### Further Examples - [AWS PrivateLink Endpoint](https://github.com/mongodb/terraform-provider-mongodbatlas/tree/v2.2.0/examples/mongodbatlas_privatelink_endpoint/aws) - [Azure PrivateLink Endpoint](https://github.com/mongodb/terraform-provider-mongodbatlas/tree/v2.2.0/examples/mongodbatlas_privatelink_endpoint/azure) -- [GCP Private Service Connect Endpoint](https://github.com/mongodb/terraform-provider-mongodbatlas/tree/v2.2.0/examples/mongodbatlas_privatelink_endpoint/gcp) +- [GCP Private Service Connect Endpoint (Legacy Architecture)](https://github.com/mongodb/terraform-provider-mongodbatlas/tree/v2.2.0/examples/mongodbatlas_privatelink_endpoint/gcp) +- [GCP Private Service Connect Endpoint (Port-Based Architecture)](https://github.com/mongodb/terraform-provider-mongodbatlas/tree/v2.2.0/examples/mongodbatlas_privatelink_endpoint/gcp-port-based) ## Argument Reference @@ -47,6 +48,7 @@ resource "mongodbatlas_privatelink_endpoint" "test" { Accepted values are: [AWS regions](https://docs.atlas.mongodb.com/reference/amazon-aws/#amazon-aws), [AZURE regions](https://docs.atlas.mongodb.com/reference/microsoft-azure/#microsoft-azure) and [GCP regions](https://docs.atlas.mongodb.com/reference/google-gcp/#std-label-google-gcp) * `timeouts`- (Optional) The duration of time to wait for Private Endpoint to be created or deleted. The timeout value is defined by a signed sequence of decimal numbers with a time unit suffix such as: `1h45m`, `300s`, `10m`, etc. The valid time units are: `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. The default timeout for Private Endpoint create & delete is `1h`. Learn more about timeouts [here](https://www.terraform.io/plugin/sdkv2/resources/retries-and-customizable-timeouts). * `delete_on_create_timeout`- (Optional) Indicates whether to delete the resource being created if a timeout is reached when waiting for completion. When set to `true` and timeout occurs, it triggers the deletion and returns immediately without waiting for deletion to complete. When set to `false`, the timeout will not trigger resource deletion. If you suspect a transient error when the value is `true`, wait before retrying to allow resource deletion to finish. Default is `true`. +* `port_mapping_enabled` - (Optional) Flag that indicates whether this endpoint service uses PSC port-mapping. When set to `true`, enables the new PSC port-based architecture for GCP, which requires only 1 endpoint instead of 50 endpoints required by the legacy architecture. Defaults to `false`. Only applicable for GCP provider. ## Attributes Reference diff --git a/docs/resources/privatelink_endpoint_service.md b/docs/resources/privatelink_endpoint_service.md index c594b149fe..baa7d851ba 100644 --- a/docs/resources/privatelink_endpoint_service.md +++ b/docs/resources/privatelink_endpoint_service.md @@ -73,7 +73,7 @@ resource "mongodbatlas_privatelink_endpoint_service" "test" { } ``` -## Example with GCP +## Example with GCP (Legacy Architecture) ```terraform resource "mongodbatlas_privatelink_endpoint" "test" { @@ -97,7 +97,7 @@ resource "google_compute_subnetwork" "default" { network = google_compute_network.default.id } -# Create Google 50 Addresses +# Create Google 50 Addresses (required for legacy architecture) resource "google_compute_address" "default" { count = 50 project = google_compute_subnetwork.default.project @@ -110,7 +110,7 @@ resource "google_compute_address" "default" { depends_on = [mongodbatlas_privatelink_endpoint.test] } -# Create 50 Forwarding rules +# Create 50 Forwarding rules (required for legacy architecture) resource "google_compute_forwarding_rule" "default" { count = 50 target = mongodbatlas_privatelink_endpoint.test.service_attachment_names[count.index] @@ -144,20 +144,92 @@ resource "mongodbatlas_privatelink_endpoint_service" "test" { ``` +## Example with GCP (Port-Based Architecture) + +The new PSC port-based architecture simplifies setup by requiring only 1 endpoint instead of 50. Enable it by setting `port_mapping_enabled = true` on the endpoint resource. + +**Important:** For the new port-based architecture, the `endpoint_service_id` must match the `endpoint_name` in the `endpoints` block. Although the new API ignores the `endpoint_service_id` value, it is still required by the Terraform provider. + +```terraform +resource "mongodbatlas_privatelink_endpoint" "test" { + project_id = var.project_id + provider_name = "GCP" + region = var.gcp_region + port_mapping_enabled = true # Enable new PSC port-based architecture +} + +# Create a Google Network +resource "google_compute_network" "default" { + project = var.gcp_project + name = "my-network" +} + +# Create a Google Sub Network +resource "google_compute_subnetwork" "default" { + project = google_compute_network.default.project + name = "my-subnet" + ip_cidr_range = "10.0.0.0/16" + region = var.gcp_region + network = google_compute_network.default.id +} + +# Create Google Address (1 address for new PSC port-based architecture) +resource "google_compute_address" "default" { + project = google_compute_subnetwork.default.project + name = "tf-test-psc-endpoint" + subnetwork = google_compute_subnetwork.default.id + address_type = "INTERNAL" + address = "10.0.42.1" + region = google_compute_subnetwork.default.region + + depends_on = [mongodbatlas_privatelink_endpoint.test] +} + +# Create Forwarding Rule (1 rule for new PSC port-based architecture) +resource "google_compute_forwarding_rule" "default" { + target = mongodbatlas_privatelink_endpoint.test.service_attachment_names[0] + project = google_compute_address.default.project + region = google_compute_address.default.region + name = google_compute_address.default.name + ip_address = google_compute_address.default.id + network = google_compute_network.default.id + load_balancing_scheme = "" +} + +resource "mongodbatlas_privatelink_endpoint_service" "test" { + project_id = mongodbatlas_privatelink_endpoint.test.project_id + private_link_id = mongodbatlas_privatelink_endpoint.test.private_link_id + provider_name = "GCP" + # For port-based architecture, endpoint_service_id must match the endpoint_name + endpoint_service_id = google_compute_forwarding_rule.default.name + gcp_project_id = var.gcp_project + + # New PSC port-based architecture requires exactly 1 endpoint + endpoints { + ip_address = google_compute_address.default.address + endpoint_name = google_compute_forwarding_rule.default.name + } + + depends_on = [google_compute_forwarding_rule.default] +} + +``` + ### Further Examples - [AWS PrivateLink Endpoint and Service](https://github.com/mongodb/terraform-provider-mongodbatlas/tree/v2.2.0/examples/mongodbatlas_privatelink_endpoint/aws/cluster) - [Azure Private Link Endpoint and Service](https://github.com/mongodb/terraform-provider-mongodbatlas/tree/v2.2.0/examples/mongodbatlas_privatelink_endpoint/azure) -- [GCP Private Service Connect Endpoint and Service](https://github.com/mongodb/terraform-provider-mongodbatlas/tree/v2.2.0/examples/mongodbatlas_privatelink_endpoint/gcp) +- [GCP Private Service Connect Endpoint and Service (Legacy Architecture)](https://github.com/mongodb/terraform-provider-mongodbatlas/tree/v2.2.0/examples/mongodbatlas_privatelink_endpoint/gcp) +- [GCP Private Service Connect Endpoint and Service (Port-Based Architecture)](https://github.com/mongodb/terraform-provider-mongodbatlas/tree/v2.2.0/examples/mongodbatlas_privatelink_endpoint/gcp-port-based) ## Argument Reference * `project_id` - (Required) Unique identifier for the project. * `private_link_id` - (Required) Unique identifier of the `AWS` or `AZURE` PrivateLink connection which is created by `mongodbatlas_privatelink_endpoint` resource. -* `endpoint_service_id` - (Required) Unique identifier of the interface endpoint you created in your VPC with the `AWS`, `AZURE` or `GCP` resource. +* `endpoint_service_id` - (Required) Unique identifier of the interface endpoint you created in your VPC with the `AWS`, `AZURE` or `GCP` resource. **Note:** For GCP with the new port-based architecture (when `port_mapping_enabled = true` on the endpoint resource), this value must match the `endpoint_name` in the `endpoints` block. Although the new API ignores this value, it is still required by the Terraform provider. * `provider_name` - (Required) Cloud provider for which you want to create a private endpoint. Atlas accepts `AWS`, `AZURE` or `GCP`. * `private_endpoint_ip_address` - (Optional) Private IP address of the private endpoint network interface you created in your Azure VNet. Only for `AZURE`. * `gcp_project_id` - (Optional) Unique identifier of the GCP project in which you created your endpoints. Only for `GCP`. -* `endpoints` - (Optional) Collection of individual private endpoints that comprise your endpoint group. Only for `GCP`. See below. +* `endpoints` - (Optional) Collection of individual private endpoints that comprise your endpoint group. Only for `GCP`. See below. **Note:** For the legacy architecture, 50 endpoints are required. For the new port-based architecture (enabled with `port_mapping_enabled = true` on the endpoint resource), exactly 1 endpoint is required. * `timeouts`- (Optional) The duration of time to wait for Private Endpoint Service to be created or deleted. The timeout value is defined by a signed sequence of decimal numbers with a time unit suffix such as: `1h45m`, `300s`, `10m`, etc. The valid time units are: `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. The default timeout for Private Endpoint create & delete is `2h`. Learn more about timeouts [here](https://www.terraform.io/plugin/sdkv2/resources/retries-and-customizable-timeouts). * `delete_on_create_timeout`- (Optional) Indicates whether to delete the resource being created if a timeout is reached when waiting for completion. When set to `true` and timeout occurs, it triggers the deletion and returns immediately without waiting for deletion to complete. When set to `false`, the timeout will not trigger resource deletion. If you suspect a transient error when the value is `true`, wait before retrying to allow resource deletion to finish. Default is `true`. @@ -200,6 +272,7 @@ In addition to all arguments above, the following attributes are exported: * `endpoint_group_name` - (Optional) Unique identifier of the endpoint group. The endpoint group encompasses all of the endpoints that you created in GCP. * `endpoints` - Collection of individual private endpoints that comprise your network endpoint group. * `status` - Status of the endpoint. Atlas returns one of the [values shown above](https://docs.atlas.mongodb.com/reference/api/private-endpoints-endpoint-create-one/#std-label-ref-status-field). +* `port_mapping_enabled` - Flag that indicates whether this endpoint service uses PSC port-mapping. This is a read-only attribute that reflects the architecture type. When `true`, the endpoint service uses the new PSC port-based architecture (requires 1 endpoint). When `false`, it uses the legacy architecture (requires 50 endpoints). Only applicable for GCP provider. ## Import Private Endpoint Link Connection can be imported using project ID and username, in the format `{project_id}--{private_link_id}--{endpoint_service_id}--{provider_name}`, e.g. diff --git a/examples/mongodbatlas_privatelink_endpoint/gcp-port-based/README.md b/examples/mongodbatlas_privatelink_endpoint/gcp-port-based/README.md new file mode 100644 index 0000000000..e6f663fced --- /dev/null +++ b/examples/mongodbatlas_privatelink_endpoint/gcp-port-based/README.md @@ -0,0 +1,100 @@ +# Example with GCP with Port-Based architecture and MongoDB Atlas Private Endpoint + +This project demonstrates the **new PSC port-based architecture** for setting up GCP Private Service Connect with MongoDB Atlas, which requires only 1 endpoint. + +## Architecture Comparison + +| Feature | Legacy Architecture | New Port-Based Architecture | +|---------|-------------------|---------------------------| +| Endpoints Required | 50 | 1 | +| `port_mapping_enabled` | `false` (or omitted) | `true` | + +## Dependencies + +* Terraform v0.13+ +* Google Cloud account +* MongoDB Atlas account + +``` +Terraform v0.13.0 ++ provider registry.terraform.io/hashicorp/google ++ provider registry.terraform.io/terraform-providers/mongodbatlas +``` + +## Usage + +**1\. Ensure your Google credentials are set up.** + +1. Install the GCloud SDK by following the steps from the [official GCP documentation](https://cloud.google.com/sdk/docs/install). +2. Run the command `gcloud init` and authenticate with GCP. +3. Once authenticated you will need to select a project to use. After you select a project a success message will appear, see the example below. You are then ready to proceed. +``` +⇒ gcloud init +You are logged in as: [user@example.com]. + +Pick cloud project to use: + [1] project1 + [2] project2 +... + +Please enter numeric choice or text value (must exactly match list item): 1 + +Your Google Cloud SDK is configured and ready to use! + +``` +**2\. TFVARS** + +Now create **terraform.tfvars** file with all the variable values and make sure **not to commit it**. + +An existing cluster on the project can optionally be linked via the `cluster_name` variable. +If included, the gcp connection string to the cluster will be output. + +**3\. Review the Terraform plan.** + +Execute the below command and ensure you are happy with the plan. + +``` bash +$ terraform plan +``` +This project currently does the below deployments: + +- MongoDB Atlas GCP Private Endpoint (using new PSC port-based architecture with 1 endpoint) +- Google resource Compute Network, SubNetwork, Address and Forwarding Rule +- Google Private Service Connect (PSC)-MongoDB Private Link + +**4\. Execute the Terraform apply.** + +Now execute the plan to provision the GCP resources. + +``` bash +$ terraform apply +``` + +**5\. Destroy the resources.** + +Once you are finished your testing, ensure you destroy the resources to avoid unnecessary GCP and Atlas charges. + +``` bash +$ terraform destroy +``` + +## Key Differences from Legacy Architecture + +The main difference in this example is the `port_mapping_enabled = true` setting on the `mongodbatlas_privatelink_endpoint` resource: + +```hcl +resource "mongodbatlas_privatelink_endpoint" "test" { + project_id = var.project_id + provider_name = "GCP" + region = var.gcp_region + port_mapping_enabled = true # This enables the new architecture + # ... +} +``` + +With this setting: +- Only **1 Google Compute Address** is needed (instead of 50) +- Only **1 Google Compute Forwarding Rule** is needed (instead of 50) +- The `endpoints` block in `mongodbatlas_privatelink_endpoint_service` contains exactly **1 endpoint** + +For the legacy architecture example (50 endpoints), see the [`gcp/`](../gcp/) directory example. diff --git a/examples/mongodbatlas_privatelink_endpoint/gcp-port-based/main.tf b/examples/mongodbatlas_privatelink_endpoint/gcp-port-based/main.tf new file mode 100644 index 0000000000..3224b5d6bf --- /dev/null +++ b/examples/mongodbatlas_privatelink_endpoint/gcp-port-based/main.tf @@ -0,0 +1,99 @@ +# Example with GCP with Port-Based (1 endpoint) +# This example demonstrates the new PSC port-based architecture which requires only 1 endpoint. +# The new architecture is enabled by setting port_mapping_enabled = true on the endpoint resource. +# This simplifies setup and management compared to the legacy architecture which requires 50 endpoints. +resource "mongodbatlas_privatelink_endpoint" "test" { + project_id = var.project_id + provider_name = "GCP" + region = var.gcp_region + port_mapping_enabled = true # Enable new PSC port-based architecture (requires 1 endpoint instead of 50) + delete_on_create_timeout = true + timeouts { + create = "10m" + delete = "10m" + } +} + +# Create a Google Network +resource "google_compute_network" "default" { + project = var.gcp_project_id + name = "my-network" +} + +# Create a Google Sub Network +resource "google_compute_subnetwork" "default" { + project = google_compute_network.default.project + name = "my-subnet" + ip_cidr_range = "10.0.0.0/16" + region = var.gcp_region + network = google_compute_network.default.id +} + +# Create Google Address (1 address for new PSC port-based architecture) +# Note: Legacy architecture requires 50 addresses. With port_mapping_enabled = true, only 1 is needed. +resource "google_compute_address" "default" { + project = google_compute_subnetwork.default.project + name = "tf-test-psc-endpoint" + subnetwork = google_compute_subnetwork.default.id + address_type = "INTERNAL" + address = "10.0.42.1" + region = google_compute_subnetwork.default.region + + depends_on = [mongodbatlas_privatelink_endpoint.test] +} + +# Create Forwarding Rule (1 rule for new PSC port-based architecture) +# Note: Legacy architecture requires 50 forwarding rules. With port_mapping_enabled = true, only 1 is needed. +# The service_attachment_names list will contain exactly one service attachment when using the new architecture. +resource "google_compute_forwarding_rule" "default" { + target = mongodbatlas_privatelink_endpoint.test.service_attachment_names[0] + project = google_compute_address.default.project + region = google_compute_address.default.region + name = google_compute_address.default.name + ip_address = google_compute_address.default.id + network = google_compute_network.default.id + load_balancing_scheme = "" +} + +# Create MongoDB Atlas Private Endpoint Service +# With port_mapping_enabled = true on the endpoint, the endpoints list should contain exactly one endpoint. +# For the new port-based architecture, endpoint_service_id must match the endpoint_name. +# Although the new API ignores this value, it is still required by the Terraform provider. +resource "mongodbatlas_privatelink_endpoint_service" "test" { + project_id = mongodbatlas_privatelink_endpoint.test.project_id + private_link_id = mongodbatlas_privatelink_endpoint.test.private_link_id + provider_name = "GCP" + endpoint_service_id = google_compute_forwarding_rule.default.name + gcp_project_id = var.gcp_project_id + delete_on_create_timeout = true + timeouts { + create = "10m" + delete = "10m" + } + # New PSC port-based architecture requires exactly 1 endpoint + endpoints { + ip_address = google_compute_address.default.address + endpoint_name = google_compute_forwarding_rule.default.name + } + + depends_on = [google_compute_forwarding_rule.default] +} + +data "mongodbatlas_advanced_cluster" "cluster" { + count = var.cluster_name == "" ? 0 : 1 + # Use endpoint service as source of project_id to gather cluster data after endpoint changes are applied + project_id = mongodbatlas_privatelink_endpoint_service.test.project_id + name = var.cluster_name +} + +locals { + endpoint_service_id = google_compute_network.default.name + private_endpoints = try(flatten([for cs in data.mongodbatlas_advanced_cluster.cluster[0].connection_strings : cs.private_endpoint]), []) + connection_strings = [ + for pe in local.private_endpoints : pe.srv_connection_string + if contains([for e in pe.endpoints : e.endpoint_id], local.endpoint_service_id) + ] +} +output "connection_string" { + value = length(local.connection_strings) > 0 ? local.connection_strings[0] : "" +} diff --git a/examples/mongodbatlas_privatelink_endpoint/gcp-port-based/provider.tf b/examples/mongodbatlas_privatelink_endpoint/gcp-port-based/provider.tf new file mode 100644 index 0000000000..83affe4673 --- /dev/null +++ b/examples/mongodbatlas_privatelink_endpoint/gcp-port-based/provider.tf @@ -0,0 +1,11 @@ +provider "mongodbatlas" { + client_id = var.atlas_client_id + client_secret = var.atlas_client_secret +} +provider "google" { + # Credentials commented out to pass lint + # Add your own service-account & path to run example + # credentials = file("service-account.json") + project = var.gcp_project_id + region = var.gcp_region # us-central1 +} diff --git a/examples/mongodbatlas_privatelink_endpoint/gcp-port-based/variables.tf b/examples/mongodbatlas_privatelink_endpoint/gcp-port-based/variables.tf new file mode 100644 index 0000000000..d0cf7bd6f0 --- /dev/null +++ b/examples/mongodbatlas_privatelink_endpoint/gcp-port-based/variables.tf @@ -0,0 +1,27 @@ +variable "gcp_project_id" { + default = "GCP-PROJECT" + type = string +} +variable "gcp_region" { + default = "us-central1" + type = string +} +variable "project_id" { + default = "PROJECT-ID" + type = string +} +variable "atlas_client_id" { + description = "MongoDB Atlas Service Account Client ID" + type = string + default = "" +} +variable "atlas_client_secret" { + description = "MongoDB Atlas Service Account Client Secret" + type = string + sensitive = true + default = "" +} +variable "cluster_name" { + description = "(Optional) Cluster whose connection string to output" + type = string +} diff --git a/examples/mongodbatlas_privatelink_endpoint/gcp-port-based/versions.tf b/examples/mongodbatlas_privatelink_endpoint/gcp-port-based/versions.tf new file mode 100644 index 0000000000..d25452b0ec --- /dev/null +++ b/examples/mongodbatlas_privatelink_endpoint/gcp-port-based/versions.tf @@ -0,0 +1,13 @@ +terraform { + required_providers { + mongodbatlas = { + source = "mongodb/mongodbatlas" + } + + google = { + source = "hashicorp/google" + version = "~> 4.0" + } + } + required_version = ">= 1.0" +} diff --git a/examples/mongodbatlas_privatelink_endpoint/gcp/Readme.md b/examples/mongodbatlas_privatelink_endpoint/gcp/Readme.md index 4a04239c2d..79246259c8 100644 --- a/examples/mongodbatlas_privatelink_endpoint/gcp/Readme.md +++ b/examples/mongodbatlas_privatelink_endpoint/gcp/Readme.md @@ -1,6 +1,6 @@ -# Example - GCP and MongoDB Atlas Private Endpoint +# Example with GCP with legacy PSC architecture and MongoDB Atlas Private Endpoint -This project aims to provide an example of setting up GCP Private Service Connect with MongoDB Atlas. +This project demonstrates the **legacy PSC architecture** for setting up GCP Private Service Connect with MongoDB Atlas, which requires 50 endpoints. ## Dependencies @@ -52,10 +52,23 @@ $ terraform plan ``` This project currently does the below deployments: -- MongoDB Atlas GCP Private Endpoint +- MongoDB Atlas GCP Private Endpoint (legacy architecture with 50 endpoints) - Google resource Compute Network, SubNetwork, Address and Forwarding Rule - Google Private Service Connect (PSC)-MongoDB Private Link +## Architecture Options + +This example demonstrates the **legacy PSC architecture** which requires 50 endpoints. + +For the **new PSC port-based architecture** (which requires only 1 endpoint and is enabled with `port_mapping_enabled = true`), see the [`gcp-port-based`](../gcp-port-based/) example. + +### Architecture Comparison + +| Feature | Legacy Architecture (this example) | New Port-Based Architecture | +|---------|-----------------------------------|---------------------------| +| Endpoints Required | 50 | 1 | +| `port_mapping_enabled` | `false` (or omitted) | `true` | + **4\. Execute the Terraform apply.** Now execute the plan to provision the GCP resources. diff --git a/examples/mongodbatlas_privatelink_endpoint/gcp/main.tf b/examples/mongodbatlas_privatelink_endpoint/gcp/main.tf index ed8584d52b..4c423885f4 100644 --- a/examples/mongodbatlas_privatelink_endpoint/gcp/main.tf +++ b/examples/mongodbatlas_privatelink_endpoint/gcp/main.tf @@ -1,3 +1,6 @@ +# Example with GCP (Legacy Architecture - 50 endpoints) +# This example demonstrates the legacy PSC architecture which requires 50 endpoints. +# For the new port-based architecture, see the gcp-port-based directory. resource "mongodbatlas_privatelink_endpoint" "test" { project_id = var.project_id provider_name = "GCP" @@ -24,7 +27,7 @@ resource "google_compute_subnetwork" "default" { network = google_compute_network.default.id } -# Create Google 50 Addresses +# Create Google 50 Addresses (required for legacy architecture) resource "google_compute_address" "default" { count = 50 project = google_compute_subnetwork.default.project @@ -37,7 +40,7 @@ resource "google_compute_address" "default" { depends_on = [mongodbatlas_privatelink_endpoint.test] } -# Create 50 Forwarding rules +# Create 50 Forwarding rules (required for legacy architecture) resource "google_compute_forwarding_rule" "default" { count = 50 target = mongodbatlas_privatelink_endpoint.test.service_attachment_names[count.index] diff --git a/examples/mongodbatlas_privatelink_endpoint/gcp/versions.tf b/examples/mongodbatlas_privatelink_endpoint/gcp/versions.tf index 2f993e4502..d25452b0ec 100644 --- a/examples/mongodbatlas_privatelink_endpoint/gcp/versions.tf +++ b/examples/mongodbatlas_privatelink_endpoint/gcp/versions.tf @@ -1,9 +1,5 @@ terraform { required_providers { - azurerm = { - source = "hashicorp/azurerm" - version = "~> 3.0" - } mongodbatlas = { source = "mongodb/mongodbatlas" } diff --git a/internal/service/privatelinkendpoint/data_source.go b/internal/service/privatelinkendpoint/data_source.go index 5d3724d9a7..e0832ceb99 100644 --- a/internal/service/privatelinkendpoint/data_source.go +++ b/internal/service/privatelinkendpoint/data_source.go @@ -81,6 +81,10 @@ func DataSource() *schema.Resource { Type: schema.TypeString, }, }, + "port_mapping_enabled": { + Type: schema.TypeBool, + Computed: true, + }, }, } } @@ -143,6 +147,10 @@ func dataSourceRead(ctx context.Context, d *schema.ResourceData, meta any) diag. return diag.FromErr(fmt.Errorf(ErrorPrivateLinkEndpointsSetting, "service_attachment_names", privateLinkID, err)) } + if err := d.Set("port_mapping_enabled", privateEndpoint.GetPortMappingEnabled()); err != nil { + return diag.FromErr(fmt.Errorf(ErrorPrivateLinkEndpointsSetting, "port_mapping_enabled", privateLinkID, err)) + } + d.SetId(conversion.EncodeStateID(map[string]string{ "private_link_id": privateEndpoint.GetId(), "project_id": projectID, diff --git a/internal/service/privatelinkendpoint/resource.go b/internal/service/privatelinkendpoint/resource.go index a236cb3740..231797172f 100644 --- a/internal/service/privatelinkendpoint/resource.go +++ b/internal/service/privatelinkendpoint/resource.go @@ -110,6 +110,11 @@ func Resource() *schema.Resource { Type: schema.TypeString, }, }, + "port_mapping_enabled": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, "delete_on_create_timeout": { // Don't use Default: true to avoid unplanned changes when upgrading from previous versions. Type: schema.TypeBool, Optional: true, @@ -136,6 +141,10 @@ func resourceCreate(ctx context.Context, d *schema.ResourceData, meta any) diag. Region: region, } + if portMappingEnabled, ok := d.GetOk("port_mapping_enabled"); ok { + request.PortMappingEnabled = conversion.Pointer(portMappingEnabled.(bool)) + } + privateEndpoint, _, err := connV2.PrivateEndpointServicesApi.CreatePrivateEndpointService(ctx, projectID, request).Execute() if err != nil { return diag.FromErr(fmt.Errorf(errorPrivateLinkEndpointsCreate, err)) @@ -236,6 +245,10 @@ func resourceRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Di return diag.FromErr(fmt.Errorf(ErrorPrivateLinkEndpointsSetting, "service_attachment_names", privateLinkID, err)) } + if err := d.Set("port_mapping_enabled", privateEndpoint.GetPortMappingEnabled()); err != nil { + return diag.FromErr(fmt.Errorf(ErrorPrivateLinkEndpointsSetting, "port_mapping_enabled", privateLinkID, err)) + } + if privateEndpoint.GetErrorMessage() != "" { return diag.FromErr(fmt.Errorf("privatelink endpoint is in a failed state: %s", privateEndpoint.GetErrorMessage())) } diff --git a/internal/service/privatelinkendpoint/resource_test.go b/internal/service/privatelinkendpoint/resource_test.go index cd2c26472c..4cdfe9dac0 100644 --- a/internal/service/privatelinkendpoint/resource_test.go +++ b/internal/service/privatelinkendpoint/resource_test.go @@ -106,6 +106,103 @@ func TestAccNetworkRSPrivateLinkEndpointGCP_basic(t *testing.T) { resource.TestCheckResourceAttrSet(resourceName, "region"), resource.TestCheckResourceAttr(resourceName, "provider_name", providerName), resource.TestCheckResourceAttr(resourceName, "region", region), + resource.TestCheckResourceAttr(resourceName, "port_mapping_enabled", "false"), + ), + }, + { + ResourceName: resourceName, + ImportStateIdFunc: importStateIDFunc(resourceName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccNetworkRSPrivateLinkEndpointGCP_basic_with_new_architecture_explicitly_enabled(t *testing.T) { + var ( + resourceName = "mongodbatlas_privatelink_endpoint.test" + orgID = os.Getenv("MONGODB_ATLAS_ORG_ID") + projectName = "test-acc-tf-p-gcp-port-based-routing-feature-flag-enabled" + region = "us-west3" + providerName = "GCP" + ) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acc.PreCheckBasic(t) }, + ProtoV6ProviderFactories: acc.TestAccProviderV6Factories, + CheckDestroy: checkDestroy, + Steps: []resource.TestStep{ + { + Config: configWithPortMapping(orgID, projectName, providerName, region, true), + Check: resource.ComposeAggregateTestCheckFunc( + checkExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "project_id"), + resource.TestCheckResourceAttrSet(resourceName, "provider_name"), + resource.TestCheckResourceAttrSet(resourceName, "region"), + resource.TestCheckResourceAttr(resourceName, "provider_name", providerName), + resource.TestCheckResourceAttr(resourceName, "region", region), + resource.TestCheckResourceAttr(resourceName, "port_mapping_enabled", "true"), + ), + }, + { + Config: configWithPortMapping(orgID, projectName, providerName, region, false), + Check: resource.ComposeAggregateTestCheckFunc( + checkExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "project_id"), + resource.TestCheckResourceAttrSet(resourceName, "provider_name"), + resource.TestCheckResourceAttrSet(resourceName, "region"), + resource.TestCheckResourceAttr(resourceName, "provider_name", providerName), + resource.TestCheckResourceAttr(resourceName, "region", region), + resource.TestCheckResourceAttr(resourceName, "port_mapping_enabled", "false"), + ), + }, + { + ResourceName: resourceName, + ImportStateIdFunc: importStateIDFunc(resourceName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccNetworkRSPrivateLinkEndpointGCP_basic_with_new_architecture_explicitly_disabled(t *testing.T) { + var ( + resourceName = "mongodbatlas_privatelink_endpoint.test" + orgID = os.Getenv("MONGODB_ATLAS_ORG_ID") + projectName = "test-acc-tf-p-gcp-port-based-routing-feature-flag-enabled" + region = "us-west4" + providerName = "GCP" + ) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acc.PreCheckBasic(t) }, + ProtoV6ProviderFactories: acc.TestAccProviderV6Factories, + CheckDestroy: checkDestroy, + Steps: []resource.TestStep{ + { + Config: configWithPortMapping(orgID, projectName, providerName, region, false), + Check: resource.ComposeAggregateTestCheckFunc( + checkExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "project_id"), + resource.TestCheckResourceAttrSet(resourceName, "provider_name"), + resource.TestCheckResourceAttrSet(resourceName, "region"), + resource.TestCheckResourceAttr(resourceName, "provider_name", providerName), + resource.TestCheckResourceAttr(resourceName, "region", region), + resource.TestCheckResourceAttr(resourceName, "port_mapping_enabled", "false"), + ), + }, + { + Config: configWithPortMapping(orgID, projectName, providerName, region, true), + Check: resource.ComposeAggregateTestCheckFunc( + checkExists(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "project_id"), + resource.TestCheckResourceAttrSet(resourceName, "provider_name"), + resource.TestCheckResourceAttrSet(resourceName, "region"), + resource.TestCheckResourceAttr(resourceName, "provider_name", providerName), + resource.TestCheckResourceAttr(resourceName, "region", region), + resource.TestCheckResourceAttr(resourceName, "port_mapping_enabled", "true"), ), }, { @@ -210,3 +307,17 @@ func configBasic(orgID, projectName, providerName, region string) string { } `, orgID, projectName, providerName, region) } + +func configWithPortMapping(orgID, projectName, providerName, region string, portMappingEnabled bool) string { + return fmt.Sprintf(` + data "mongodbatlas_project" "test" { + name = %[2]q + } + resource "mongodbatlas_privatelink_endpoint" "test" { + project_id = data.mongodbatlas_project.test.id + provider_name = %[3]q + region = %[4]q + port_mapping_enabled = %[5]t + } + `, orgID, projectName, providerName, region, portMappingEnabled) +} diff --git a/internal/service/privatelinkendpointservice/data_source.go b/internal/service/privatelinkendpointservice/data_source.go index ae55e04931..9d259a7b4c 100644 --- a/internal/service/privatelinkendpointservice/data_source.go +++ b/internal/service/privatelinkendpointservice/data_source.go @@ -92,6 +92,10 @@ func DataSource() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "port_mapping_enabled": { + Type: schema.TypeBool, + Computed: true, + }, }, } } @@ -137,6 +141,10 @@ func dataSourceRead(ctx context.Context, d *schema.ResourceData, meta any) diag. if err := d.Set("gcp_status", serviceEndpoint.GetStatus()); err != nil { return diag.FromErr(fmt.Errorf(errorEndpointSetting, "gcp_status", endpointServiceID, err)) } + + if err := d.Set("port_mapping_enabled", serviceEndpoint.GetPortMappingEnabled()); err != nil { + return diag.FromErr(fmt.Errorf(errorEndpointSetting, "port_mapping_enabled", privateLinkID, err)) + } } d.SetId(conversion.EncodeStateID(map[string]string{ diff --git a/internal/service/privatelinkendpointservice/resource.go b/internal/service/privatelinkendpointservice/resource.go index 7ea37731a7..53a105c4bd 100644 --- a/internal/service/privatelinkendpointservice/resource.go +++ b/internal/service/privatelinkendpointservice/resource.go @@ -137,6 +137,10 @@ func Resource() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "port_mapping_enabled": { + Type: schema.TypeBool, + Computed: true, + }, }, Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(2 * time.Hour), @@ -290,6 +294,10 @@ func resourceRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Di } if providerName == "GCP" { + if err := d.Set("port_mapping_enabled", privateEndpoint.GetPortMappingEnabled()); err != nil { + return diag.FromErr(fmt.Errorf(errorEndpointSetting, "port_mapping_enabled", privateLinkID, err)) + } + if err := d.Set("gcp_status", privateEndpoint.GetStatus()); err != nil { return diag.FromErr(fmt.Errorf(errorEndpointSetting, "gcp_status", endpointServiceID, err)) }