Skip to content

generic: path-aware attribute name mapping for duplicate CloudFormation property names - #3226

Draft
arditti wants to merge 2 commits into
hashicorp:mainfrom
arditti:b-duplicate-id-name-map
Draft

generic: path-aware attribute name mapping for duplicate CloudFormation property names#3226
arditti wants to merge 2 commits into
hashicorp:mainfrom
arditti:b-duplicate-id-name-map

Conversation

@arditti

@arditti arditti commented Jul 7, 2026

Copy link
Copy Markdown

Community Note

  • Please vote on this pull request by adding a 👍 reaction to the original pull request comment to help the community and maintainers prioritize this request
  • Please do not leave "+1" or other comments that do not add relevant new information or questions, they generate extra noise for pull request followers and do not help prioritize the request
  • The resources and data sources in this provider are generated from the CloudFormation schema, so they can only support the actions that the underlying schema supports. For this reason submitted bugs should be limited to defects in the generation and runtime code of the provider. Customizing behavior of the resource, or noting a gap in behavior are not valid bugs and should be submitted as enhancements to AWS via the CloudFormation Open Coverage Roadmap.

Relates #2311

Rollback Plan

If a change needs to be reverted, we will publish an updated version of the library.

Changes to Security Controls

No changes to security controls.

Description

Fixes the duplicate attribute name mapping for CloudFormation property Id startup error that currently forces suppress_resource_generation on 9 resources, and un-suppresses two of them: awscc_cloudfront_distribution and awscc_cloudfront_anycast_ip_list.

Root cause

The generator renames a top-level CloudFormation Id property to <type>_id (e.g. distribution_id) because Terraform reserves id. When the same schema also has a nested property named Id (e.g. AWS::CloudFront::Distribution has top-level Id plus DistributionConfig/Origins/*/Id), the generated flat tfToCfNameMap contains both distribution_id → Id and id → Id. Inverting that map in internal/generic/resource.go / data_source.go then fails, because the reverse lookup is path-insensitive while the collision only exists across different JSON paths.

Fix

invertAttributeNameMap (new, in internal/generic/translate.go) splits the mapping into:

  • cfToTfNameMap — the naming-convention reverse map (Id → id), used at nested paths as before;
  • rootCfToTfNameMap — top-level overrides (Id → distribution_id), consulted only when translating properties at the document root;
  • rootTfToCfNameMap — the TF→CFN top-level override, so the synthetic top-level id (the Cloud Control identifier, Computed-only) can never leak into DesiredState/patch documents as the resource's own Id property.

Consumers are made root-aware at the three places with path context: toTerraform.valueFromRaw, toCloudControl.rawFromValue, and propertyPathToAttributePath. Resources without a top-level/nested collision produce empty override maps and behave exactly as before; irreconcilable duplicates (two same-named properties at the same level) still error exactly as before.

Scope

Only aws_cloudfront_distribution and aws_cloudfront_anycast_ip_list are un-suppressed here to keep the review surface small. The same fix unlocks the other 7 resources suppressed for this reason (aws_amazonmq_broker, aws_eks_nodegroup, aws_emrcontainers_security_configuration, aws_emrcontainers_virtual_cluster, aws_mediapackage_channel, aws_mediapackage_origin_endpoint, aws_vpclattice_target_group); happy to follow up.

Test evidence

  • go build ./..., gofmt, go vet, golangci-lint run (pinned version): clean.
  • New unit tests in internal/generic/translate_test.go: TestInvertAttributeNameMap and TestTranslateRoundTripWithDuplicateId prove full round-trip translation (state → Cloud Control document → state) with a colliding nested Id.
  • go test ./internal/generic/... ./internal/aws/cloudfront/... ./internal/provider/... — all pass, including a factory smoke test instantiating every registered resource/data source schema.
  • Acceptance: generated _NonExistent acctests for awscc_cloudfront_distribution pass. End-to-end against a real AWS account: created a disabled CloudFront distribution via Cloud Control, terraform import + refresh through this provider round-tripped correctly (distribution_id = distribution ID, synthetic id = Cloud Control identifier, nested origins[0].id preserved); the singular data source returns the same. AnycastIpList acceptance was skipped (the resource carries real cost); maintainers run acceptance tests pre-merge.

Known pre-existing issue (not introduced here)

The current CloudFront Distribution schema declares default: "" on several optional strings (e.g. RealtimeLogConfigArn), which the emitter turns into stringdefault.StaticString(""); CloudFront rejects the empty string on create. Verified via direct Cloud Control calls that this is independent of the name-mapping change (import/read/update are unaffected). Worth tracking separately as a schema-defaults issue.

…on property names

The generated Terraform attribute name map is flat: every attribute name
maps to a CloudFormation property name regardless of where the property
appears in the resource document. When a resource has a top-level property
named Id, the generator renames its Terraform attribute to <type>_id
(Terraform reserves id for the resource's primary identifier). If the same
resource also has a nested property named Id (mapped to id), the map ends
up with two attribute names for the same property name and inverting it for
the CloudFormation-to-Terraform direction failed with 'duplicate attribute
name mapping for CloudFormation property Id', so such resources had to be
suppressed from generation.

Resolve the collision by making the name translation aware of whether a
property sits at the top level of the resource document:

- invertAttributeNameMap builds the nested (naming-convention) reverse map
  plus small top-level override maps for both translation directions.
- toTerraform.valueFromRaw and propertyPathToAttributePath use the
  top-level override when translating root document properties, so a
  top-level Id maps to <type>_id while a nested Id maps to id.
- toCloudControl.rawFromValue maps the reserved top-level id attribute to
  the synthetic ID property so it can never collide with the real Id
  property in DesiredState documents.

Un-suppress AWS::CloudFront::Distribution and AWS::CloudFront::AnycastIpList
and generate their resources, singular data sources, and docs. The other
seven resources suppressed for the same reason can be un-suppressed as a
follow-up.

Relates hashicorp#2311.
…ning entries

The seven resources still suppressed for the duplicate-Id collision kept
their one-line comment, which stops being accurate once the path-aware
name mapping lands (the generator no longer errors on them). Expand each
comment to state the mechanism, link the tracking issue, and note they
remain suppressed only pending regeneration and validation.
@arditti

arditti commented Jul 20, 2026

Copy link
Copy Markdown
Author

Pushed a comment-only follow-up commit: the seven resources that stay suppressed for the duplicate-Id collision now document the mechanism and link #2311 in all_schemas.hcl, and note they remain suppressed only pending regeneration + validation — the previous one-line comment would have read as an unfixed generator error once this PR lands. No code changes.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant