Skip to content
Open
15 changes: 15 additions & 0 deletions .changelog/4017.txt
Original file line number Diff line number Diff line change
@@ -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
```
1 change: 1 addition & 0 deletions docs/data-sources/privatelink_endpoint.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
1 change: 1 addition & 0 deletions docs/data-sources/privatelink_endpoint_service.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
1 change: 1 addition & 0 deletions docs/resources/privatelink_endpoint.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,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.
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.


## Attributes Reference

Expand Down
1 change: 1 addition & 0 deletions docs/resources/privatelink_endpoint_service.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,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.

## 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.
Expand Down
8 changes: 8 additions & 0 deletions internal/service/privatelinkendpoint/data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ func DataSource() *schema.Resource {
Type: schema.TypeString,
},
},
"port_mapping_enabled": {
Type: schema.TypeBool,
Computed: true,
},
},
}
}
Expand Down Expand Up @@ -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,
Expand Down
13 changes: 13 additions & 0 deletions internal/service/privatelinkendpoint/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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))
Expand Down Expand Up @@ -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()))
}
Expand Down
89 changes: 89 additions & 0 deletions internal/service/privatelinkendpoint/resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,81 @@ 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,
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 :)

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"
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)

region = "us-west3"
providerName = "GCP"
portMappingEnabled = true
)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acc.PreCheckBasic(t) },
ProtoV6ProviderFactories: acc.TestAccProviderV6Factories,
CheckDestroy: checkDestroy,
Steps: []resource.TestStep{
{
Config: configWithPortMapping(orgID, projectName, providerName, region, portMappingEnabled),
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"),
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).

),
},
{
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"
portMappingEnabled = false
)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acc.PreCheckBasic(t) },
ProtoV6ProviderFactories: acc.TestAccProviderV6Factories,
CheckDestroy: checkDestroy,
Steps: []resource.TestStep{
{
Config: configWithPortMapping(orgID, projectName, providerName, region, portMappingEnabled),
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"),
),
},
{
Expand Down Expand Up @@ -210,3 +285,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)
}
8 changes: 8 additions & 0 deletions internal/service/privatelinkendpointservice/data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ func DataSource() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
"port_mapping_enabled": {
Type: schema.TypeBool,
Computed: true,
},
},
}
}
Expand Down Expand Up @@ -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{
Expand Down
8 changes: 8 additions & 0 deletions internal/service/privatelinkendpointservice/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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))
}
Expand Down
Loading