Skip to content

Conversation

@filipcirtog
Copy link
Collaborator

@filipcirtog filipcirtog commented Dec 22, 2025

Description

This PR implements support for the new GCP port-based architecture, which uses port mapping to reduce resource provisioning. Unlike the legacy architecture that requires dedicated resources for each Atlas node, the new design uses a single set of resources to support up to 1000 nodes through port mapping, enabling direct targeting of specific nodes using only one customer IP address.

Changes Overview

New Attributes

  1. port_mapping_enabled (Optional, Computed)

    • Added to mongodbatlas_privatelink_endpoint resource (Optional, settable)
    • Added to mongodbatlas_privatelink_endpoint data source (Computed, read-only)
    • Added to mongodbatlas_privatelink_endpoint_service resource (Computed, read-only)
    • Added to mongodbatlas_privatelink_endpoint_service data source (Computed, read-only)
    • When set to true on the endpoint resource, enables the new port-based architecture for GCP
    • Defaults to false (legacy architecture)
  2. gcp_endpoint_status (Computed)

    • Added to mongodbatlas_privatelink_endpoint_service resource and data source
    • Status of the individual GCP endpoint
    • Only populated for the new port-based architecture (when port_mapping_enabled = true)
    • Returns one of: INITIATING, AVAILABLE, FAILED, DELETING

Reused Attributes

  1. private_endpoint_ip_address (Optional)

    • Previously used for Azure, Now also used for the new GCP port-based architecture
    • For new architecture: Required and should be the IP address of the forwarding rule
    • For legacy architecture: Not used
    • Conflicts with endpoints attribute
  2. endpoint_service_id (Required)

    • For legacy architecture: Can be any identifier string
    • For new architecture: Should be the forwarding rule name
  • Updated documentation for all affected resources and data sources

  • Added test coverage:

    • Test case for explicitly enabled port mapping
    • Test case for explicitly disabled port mapping

Link to any related issue(s): CLOUDP-363082

Type of change:

  • Bug fix (non-breaking change which fixes an issue). Please, add the "bug" label to the PR.
  • New feature (non-breaking change which adds functionality). Please, add the "enhancement" label to the PR. A migration guide must be created or updated if the new feature will go in a major version.
  • Breaking change (fix or feature that would cause existing functionality to not work as expected). Please, add the "breaking change" label to the PR. A migration guide must be created or updated.
  • This change requires a documentation update
  • Documentation fix/enhancement

Required Checklist:

  • I have signed the MongoDB CLA
  • I have read the contributing guides
  • I have checked that this change does not generate any credentials and that they are NOT accidentally logged anywhere.
  • I have added tests that prove my fix is effective or that my feature works per HashiCorp requirements
  • I have added any necessary documentation (if appropriate)
  • I have run make fmt and formatted my code
  • If changes include deprecations or removals I have added appropriate changelog entries.
  • If changes include removal or addition of 3rd party GitHub actions, I updated our internal document. Reach out to the APIx Integration slack channel to get access to the internal document.

Further comments

@filipcirtog filipcirtog changed the title Cloudp 363082 gcp port based routing implementation feat: Add port_mapping_enabled attribute to privateEndpoint and privateEndpointService Dec 22, 2025
@filipcirtog filipcirtog changed the title feat: Add port_mapping_enabled attribute to privateEndpoint and privateEndpointService feat: Add port_mapping_enabled attribute to privatelink_endpoint and privatelink_endpoint_service Dec 22, 2025
@filipcirtog filipcirtog changed the title feat: Add port_mapping_enabled attribute to privatelink_endpoint and privatelink_endpoint_service feat: Adds port_mapping_enabled attribute to privatelink_endpoint and privatelink_endpoint_service Dec 22, 2025
@filipcirtog filipcirtog marked this pull request as ready for review December 22, 2025 15:19
@filipcirtog filipcirtog requested review from a team as code owners December 22, 2025 15:19
Copilot AI review requested due to automatic review settings December 22, 2025 15:19
@github-actions
Copy link
Contributor

github-actions bot commented Dec 22, 2025

APIx bot: a message has been sent to Docs Slack channel
APIx bot: a message has been sent to Docs Slack channel

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds support for GCP Private Service Connect (PSC) port-mapping architecture by introducing a port_mapping_enabled attribute to privatelink endpoint resources and data sources. The attribute allows users to explicitly control whether PSC port-mapping is enabled when creating GCP private endpoints.

Key Changes:

  • Added port_mapping_enabled boolean attribute to privatelink_endpoint (optional, ForceNew) and privatelink_endpoint_service (computed)
  • Implemented test coverage for both explicitly enabled and disabled port mapping scenarios
  • Updated documentation for all affected resources and data sources

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
internal/service/privatelinkendpoint/resource.go Added optional port_mapping_enabled field to resource schema and create/read logic
internal/service/privatelinkendpoint/data_source.go Added computed port_mapping_enabled field to data source schema and read logic
internal/service/privatelinkendpointservice/resource.go Added computed port_mapping_enabled field to resource schema and read logic for GCP
internal/service/privatelinkendpointservice/data_source.go Added computed port_mapping_enabled field to data source schema and read logic for GCP
internal/service/privatelinkendpoint/resource_test.go Added test coverage for port mapping enabled/disabled scenarios and updated existing test assertions
docs/resources/privatelink_endpoint.md Documented the new port_mapping_enabled attribute
docs/resources/privatelink_endpoint_service.md Documented the new port_mapping_enabled attribute
docs/data-sources/privatelink_endpoint.md Documented the new port_mapping_enabled attribute
docs/data-sources/privatelink_endpoint_service.md Documented the new port_mapping_enabled attribute
.changelog/4017.txt Added changelog entries for the enhancement

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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"
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The project name references 'feature-flag-enabled' but this test is for explicitly enabled port mapping, not feature flag testing. Consider renaming to something like 'test-acc-tf-p-gcp-port-mapping-enabled' for clarity.

Suggested change
projectName = "test-acc-tf-p-gcp-port-based-routing-feature-flag-enabled"
projectName = "test-acc-tf-p-gcp-port-mapping-enabled"

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: This will be updated to use the random name once the feature is officially released (GA) and not gated by a feature-flag (before merging to master)

Copy link

@lizo-mdb lizo-mdb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] Is there any example we can update also? I struggle a bit to understand this feature seeing only this variable description

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the description available on Atlas and unfortunately we are not able to update it. (we need DELETE + CREATE)

Copy link
Collaborator

@EspenAlbert EspenAlbert Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just wanted to know if we have an example in repo_root/examples/* .tf that we could update?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes total sense. I will add an example for this.

),
},
{
ResourceName: resourceName,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice to add the import step 👍

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's just git displaying the diff wrong as that was already there but it just got pushed back to the end :)

resource.TestCheckResourceAttrSet(resourceName, "region"),
resource.TestCheckResourceAttr(resourceName, "provider_name", providerName),
resource.TestCheckResourceAttr(resourceName, "region", region),
resource.TestCheckResourceAttr(resourceName, "port_mapping_enabled", "true"),
Copy link
Collaborator

@EspenAlbert EspenAlbert Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] not sure if it is worth testing the attribute changes:
null -> true
true -> false

Also, does this value have a default? (What is returned by the API if the field is not set?)
What is the difference between false and null? Will null -> false and ForceNew leading to an unnecessary DELETE+CREATE plan?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Attribute changes (null -> true, true -> false):
    These transitions were not tested since I thought it is not needed because of ForceNew. I will add those.

  2. Default value:
    Atlas defaults this attribute to false. Should we do the same?

  3. null vs false:
    I will check this as I do not have a response now (I personally expect yes).

Copy link
Collaborator

@oarbusi oarbusi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@github-actions
Copy link
Contributor

This PR has gone 7 days without any activity and meets the project’s definition of "stale". This will be auto-closed if there is no new activity over the next 7 days. If the issue is still relevant and active, you can simply comment with a "bump" to keep it open, or add the label "not_stale". Thanks for keeping our repository healthy!

@github-actions github-actions bot added the stale label Dec 29, 2025
@github-actions github-actions bot closed this Jan 1, 2026
@filipcirtog filipcirtog added the not_stale Not stale issue or PR label Jan 8, 2026
@filipcirtog filipcirtog requested a review from Copilot January 22, 2026 20:04
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

private_link_id = mongodbatlas_privatelink_endpoint.test.private_link_id
provider_name = "GCP"
endpoint_service_id = google_compute_network.default.name
endpoint_service_id = "the-endpoint-group-name"
Copy link

Copilot AI Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The placeholder value 'the-endpoint-group-name' is not descriptive enough for example code. Consider using a more meaningful placeholder like 'my-endpoint-group' or adding a comment explaining what value should be used here.

Copilot uses AI. Check for mistakes.

locals {
endpoint_service_id = google_compute_network.default.name
endpoint_service_id = "the-endpoint-group-name"
Copy link

Copilot AI Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same placeholder value as line 59. These should match the actual resource reference (like it was before: google_compute_network.default.name) or be clearly documented as placeholders.

Suggested change
endpoint_service_id = "the-endpoint-group-name"
endpoint_service_id = mongodbatlas_privatelink_endpoint_service.test.endpoint_service_id

Copilot uses AI. Check for mistakes.
@filipcirtog filipcirtog marked this pull request as ready for review January 23, 2026 10:52
* `private_link_id` - (Required) Unique identifier of the private endpoint service for which you want to retrieve a private endpoint.
* `endpoint_service_id` - (Required) Unique identifier of the `AWS` or `AZURE` or `GCP` resource.
* `provider_name` - (Required) Cloud provider for which you want to create a private endpoint. Atlas accepts `AWS` or `AZURE` or `GCP`.
* `endpoint_service_id` - (Required) Unique identifier of the interface endpoint you created in your VPC with the `AWS`, `AZURE`, or `GCP` resource. For GCP legacy architecture, this can be any identifier string. For GCP port-based architecture (when `port_mapping_enabled = true` on the endpoint resource), this should be the forwarding rule name.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why should? do we need any confirmation to change the wording?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you! no, this must be the forwarding rule name, I will change the wording

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated. thank you!

* `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.
* `private_endpoint_ip_address` - (Optional) Private IP address of the private endpoint network interface. **Required for `AZURE`.** For GCP port-based architecture (when `port_mapping_enabled = true` on the endpoint resource), this is required and should be the IP address of the forwarding rule. For GCP legacy architecture, this is not used.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove should if possible

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated. thank you!

@filipcirtog filipcirtog requested a review from Copilot January 23, 2026 13:29
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

resource/mongodbatlas_privatelink_endpoint_service: Adds `port_mapping_enabled` attribute
```

```release-note:enhancement
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the only other resource and ds in Private Endpoint Services group is https://registry.terraform.io/providers/mongodb/mongodbatlas/latest/docs/resources/private_endpoint_regional_mode, can you confirm nothing changes there?


## Example with GCP (Port-Based Architecture)

The new port-based architecture uses port mapping to reduce resource provisioning. Unlike the legacy architecture that requires dedicated resources for each Atlas node, the new design uses a single set of resources to support up to 1000 nodes through a port mapping network endpoint group (NEG), enabling direct targeting of specific nodes using only one customer IP address. Enable it by setting `port_mapping_enabled = true` on the endpoint resource.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similarly to calling port-based architecture to the new architecture, don't know if we can use a different name for old one instead of legacy

"port_mapping_enabled": {
Type: schema.TypeBool,
Optional: true,
ForceNew: true,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

discussed offline, to remove ForceNew and throw an error in Update func

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated. thank you!

Config: configWithPortMapping(orgID, projectName, providerName, region, true),
Check: resource.ComposeAggregateTestCheckFunc(
checkExists(resourceName),
resource.TestCheckResourceAttrSet(resourceName, "project_id"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: consider using helper functions for checks

},
"port_mapping_enabled": {
Type: schema.TypeBool,
Computed: true,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add description

return diag.FromErr(fmt.Errorf(errorEndpointSetting, "aws_connection_status", endpointServiceID, err))
}

if err := d.Set("interface_endpoint_id", serviceEndpoint.GetInterfaceEndpointId()); err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove these sets, as they're provided by customer in the TF config

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated. thank you!

Type: schema.TypeString,
Computed: true,
},
"endpoint_group_name": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't remove as it'll be a breaking change if some customers has it in the config, that will fail. I recommend to keep it, remove it from the documentation, and add a code comment saying that's it's not being used, maybe create a ticket to remove if TF 3.0.0

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated. TY!

}

func isGCPPortBasedArchitectureInput(providerName string, hasGCPProjectID, hasPrivateEndpointIP, hasEndpoints bool) bool {
return providerName == "GCP" && hasGCPProjectID && hasPrivateEndpointIP && !hasEndpoints
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: being "GCP" we already know hasGCPProjectID is true so no needed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similarly we've already checked that hasPrivateEndpointIP and hasEndpoints are the opposite in GCP so only need to check one

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated. thank you!

if !hasGCPProjectID {
return diag.FromErr(errors.New("`gcp_project_id` must be set for GCP"))
}
if (hasPrivateEndpointIP && hasEndpoints) || (!hasPrivateEndpointIP && !hasEndpoints) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (hasPrivateEndpointIP && hasEndpoints) || (!hasPrivateEndpointIP && !hasEndpoints) {
if (hasPrivateEndpointIP == hasEndpoints) {

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated. thank you!

return providerName == "GCP" && hasGCPProjectID && hasPrivateEndpointIP && !hasEndpoints
}

func isGCPLegacyArchitectureInput(providerName string, hasGCPProjectID, hasPrivateEndpointIP, hasEndpoints bool) bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

recommend to remove isGCPLegacyArchitectureInput and use !isGCPPortBasedArchitectureInput instead

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated. thank you!

Comment on lines 198 to 199
}
if isGCPLegacyArchitectureInput(providerName, hasGCPProjectID, hasPrivateEndpointIP, hasEndpoints) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}
if isGCPLegacyArchitectureInput(providerName, hasGCPProjectID, hasPrivateEndpointIP, hasEndpoints) {
} else {

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated. thank you!

return diag.FromErr(fmt.Errorf(errorEndpointSetting, "private_endpoint_resource_id", endpointServiceID, err))
}

if strings.EqualFold(providerName, "azure") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if strings.EqualFold(providerName, "azure") {
if providerName == constant.AZURE

or even better with switch(provideName) {
case constant.AZURE:
...
case constant.GCP:
.....

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated. thank you!

return diag.FromErr(fmt.Errorf(errorEndpointSetting, "gcp_status", endpointServiceID, err))
}

if privateEndpoint.GetPortMappingEnabled() && privateEndpoint.Endpoints != nil && len(*privateEndpoint.Endpoints) == 1 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if privateEndpoint.GetPortMappingEnabled() && privateEndpoint.Endpoints != nil && len(*privateEndpoint.Endpoints) == 1 {
if privateEndpoint.GetPortMappingEnabled() && len(privateEndpoint.GetEndpoints()) == 1 {

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated. thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement not_stale Not stale issue or PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants