Skip to content

Missing glue for Terraform Plugin Framework resources - #89

Merged
fernandezcuesta merged 2 commits into
mainfrom
84-cannotobserveexternalresource-when-creating-a-new-advancedcluster
May 13, 2026
Merged

Missing glue for Terraform Plugin Framework resources#89
fernandezcuesta merged 2 commits into
mainfrom
84-cannotobserveexternalresource-when-creating-a-new-advancedcluster

Conversation

@fernandezcuesta

@fernandezcuesta fernandezcuesta commented May 11, 2026

Copy link
Copy Markdown
Collaborator

TPF External Name Fixes

Problem

Last versions of MongoDB Atlas Terraform Provider migrated resources from SDKv2 to the
Terraform Plugin Framework (TPF). TPF resources do not expose id as a schema
attribute. Upjet's default GetExternalNameFn (IDAsExternalName) reads
tfstate["id"] and fails with:

cannot get external name: id does not exist in tfstate

This manifests as CannotObserveExternalResource on every reconciliation attempt.

A second category of resources used config.IdentifierFromProvider but their TF
provider actually stores IDs using Atlas's conversion.EncodeStateID format
(base64-encoded key-value pairs). These resources need encodedStateID to
correctly reconstruct the TF import ID and extract the external name.

Root Cause

Upjet intentionally skips injecting id for TPF resources that don't declare it
in their schema (external_tfpluginfw.go:147). When GetExternalNameFn calls
tfstate["id"], the key is missing and the function returns an error.

For EncodeStateID resources, config.IdentifierFromProvider assumes the raw id
string is the external name. But Atlas encodes composite keys as base64 pairs
(e.g., cHJvamVjdF9pZA==:YWJjMTIz-Y2x1c3Rlcl9uYW1l:bXlDbHVzdGVy), so the
external name must be decoded from this format.

Changes

1. TPF fallback in templatedStringAsIdentifier (config/external_name.go)

Added GetExternalNameFn that falls back to rendering the template from state
fields when id is absent:

e.GetExternalNameFn = func(tfstate map[string]any) (string, error) {
    if _, ok := tfstate["id"]; ok {
        return origGetExternalNameFn(tfstate)
    }
    return origGetIDFn(context.Background(), "", tfstate, nil)
}

Affected resources (use templatedStringAsIdentifier):

Resource Template
mongodbatlas_advanced_cluster {{ .parameters.project_id }}-{{ .parameters.name }}
mongodbatlas_flex_cluster {{ .parameters.project_id }}-{{ .parameters.name }}
mongodbatlas_search_deployment {{ .parameters.project_id }}-{{ .parameters.cluster_name }}
mongodbatlas_stream_instance {{ .parameters.project_id }}-{{ .parameters.instance_name }}
mongodbatlas_stream_connection {{ .parameters.workspace_name }}-{{ .parameters.project_id }}-{{ .parameters.connection_name }}
mongodbatlas_stream_processor {{ .parameters.instance_name }}-{{ .parameters.project_id }}-{{ .parameters.processor_name }}
mongodbatlas_encryption_at_rest {{ .parameters.project_id }}
mongodbatlas_push_based_log_export {{ .parameters.project_id }}
mongodbatlas_maintenance_window {{ .parameters.project_id }}
mongodbatlas_custom_dns_configuration_cluster_aws {{ .parameters.project_id }}
mongodbatlas_ldap_configuration {{ .parameters.project_id }}

2. Merged encodedStateIDWithExternalName into encodedStateIDMapped (config/external_name.go)

Previously two separate functions existed:

  • encodedStateIDMapped -- all encoded keys come from forProvider params
  • encodedStateIDWithExternalName -- one key (provider-assigned) comes from the external name annotation

Merged into a single encodedStateIDMapped that checks whether externalNameKey
is already covered by the field mapping. If not, it adds the external name
annotation value:

if _, ok := m[externalNameKey]; !ok && externalName != "" {
    m[externalNameKey] = externalName
}

3. Migrated EncodeStateID resources from IdentifierFromProvider to encodedStateID

These resources use Atlas's base64-encoded composite ID format but were incorrectly
configured with config.IdentifierFromProvider:

Resource Fields External Name Key Previous Config
mongodbatlas_alert_configuration project_id id IdentifierFromProvider
mongodbatlas_cloud_backup_snapshot_export_bucket project_id id IdentifierFromProvider
mongodbatlas_cloud_backup_snapshot_export_job project_id, cluster_name export_job_id IdentifierFromProvider
mongodbatlas_cloud_backup_snapshot_restore_job project_id, cluster_name snapshot_restore_job_id IdentifierFromProvider
mongodbatlas_cloud_backup_snapshot project_id, cluster_name snapshot_id IdentifierFromProvider
mongodbatlas_cloud_provider_access_setup project_id, provider_name id IdentifierFromProvider
mongodbatlas_network_container project_id container_id IdentifierFromProvider
mongodbatlas_network_peering project_id, provider_name peer_id IdentifierFromProvider
mongodbatlas_privatelink_endpoint_service project_id, private_link_id, endpoint_service_id, provider_name endpoint_service_id IdentifierFromProvider
mongodbatlas_privatelink_endpoint project_id, provider_name, region private_link_id IdentifierFromProvider
mongodbatlas_search_index project_id, cluster_name index_id IdentifierFromProvider
mongodbatlas_serverless_instance project_id, name name IdentifierFromProvider

4. Removed incorrect GetIDFn/GetExternalNameFn overrides in resource configurators

Previous overrides assumed TF IDs were simple dash-separated or double-dash-separated
strings (e.g., {project_id}-{snapshot_id}). The actual format is Atlas's
base64-encoded state ID. Since the correct encoding is now handled by
encodedStateID/encodedStateIDMapped in external_name.go, these overrides
were removed.

5. Added TPF-aware GetExternalNameFn overrides for IdentifierFromProvider resources

Resources that remain on config.IdentifierFromProvider but are now TPF-based
need custom GetExternalNameFn to read the external name from a state field
instead of id. Three helper functions were added to config/{cluster,namespaced}/common/common.go:

Helper Purpose
ExternalNameFromIDOrState(sep, skipLeft, skipRight, stateField) Parse composite id (SDKv2) or read stateField directly (TPF)
ExternalNameFromStateField(fields...) Read field(s) from state, join with -
ExternalNameFromAccessListState(scopeField) Construct {scope}-{client_id}-{ip_address} from state fields

Resources using these helpers:

Resource Helper State Field(s)
mongodbatlas_log_integration ExternalNameFromIDOrState type
mongodbatlas_service_account ExternalNameFromIDOrState client_id
mongodbatlas_service_account_secret ExternalNameFromIDOrState secret_id
mongodbatlas_project_service_account ExternalNameFromIDOrState client_id
mongodbatlas_project_service_account_secret ExternalNameFromIDOrState secret_id
mongodbatlas_service_account_access_list_entry ExternalNameFromAccessListState org_id, client_id, ip_address/cidr_block
mongodbatlas_project_service_account_access_list_entry ExternalNameFromAccessListState project_id, client_id, ip_address/cidr_block

Summary

Category Count Fix
TPF resources using templatedStringAsIdentifier 11 TPF fallback in GetExternalNameFn (automatic)
EncodeStateID resources on IdentifierFromProvider 12 Migrated to encodedStateID
Incorrect dash-separator overrides 12 Removed (handled by encodedStateID)
TPF resources on IdentifierFromProvider needing field extraction 7 Added ExternalNameFromIDOrState/ExternalNameFromAccessListState
Function simplification 1 Merged encodedStateIDWithExternalName into encodedStateIDMapped

chore: bump up go dependencies

Signed-off-by: Jesús Fernández <7312236+fernandezcuesta@users.noreply.github.com>
Signed-off-by: Jesús Fernández <7312236+fernandezcuesta@users.noreply.github.com>
@fernandezcuesta
fernandezcuesta force-pushed the 84-cannotobserveexternalresource-when-creating-a-new-advancedcluster branch from be70ed5 to 983ceb3 Compare May 11, 2026 23:21
@fernandezcuesta fernandezcuesta changed the title 84 cannotobserveexternalresource when creating a new advancedcluster Missing glue for Terraform Plugin Framework resources May 12, 2026
@fernandezcuesta

Copy link
Copy Markdown
Collaborator Author

Fix: #84

@fernandezcuesta
fernandezcuesta merged commit f8410ec into main May 13, 2026
11 checks passed
@fernandezcuesta
fernandezcuesta deleted the 84-cannotobserveexternalresource-when-creating-a-new-advancedcluster branch May 13, 2026 12:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CannotObserveExternalResource when creating a new AdvancedCluster

2 participants