Skip to content

tfgen: fix Import sections for GCloud IAM-style resources - #3586

Open
jkodroff wants to merge 3 commits into
mainfrom
jkodroff/fix-gcp-iam-import-docs
Open

tfgen: fix Import sections for GCloud IAM-style resources#3586
jkodroff wants to merge 3 commits into
mainfrom
jkodroff/fix-gcp-iam-import-docs

Conversation

@jkodroff

Copy link
Copy Markdown
Member

Fixes #3584. Fixes #3585.

Upstream documents a family of IAM resources on a single page, using quoted, space-delimited composite IDs. Four defects compounded there, so a page like gcp:projects/iAMMember renders no usable import guidance at all — the entire Import body is two -> notes, one of which itself contains a bare terraform import command.

What changed

1. Quoted, space-delimited IDs. parseImportCode's ID capture was ([^\s+]) — one whitespace-free token. IAM import IDs are quoted and space-delimited, so the pattern never matched and rewriteImportLines left the upstream line verbatim. The capture now also accepts a quoted string, keeping the quotes in the emitted command.

2. Sibling-resource tokens. Fixing (1) alone would have made things worse. Upstream ships one page for the whole triad, so once every example parses, every example gets stamped with the token of the page being generated — the IamMember example on the IamBinding page would silently become a wrong pulumi import. Each example's own Terraform resource type is now resolved through ProviderInfo.Resources, falling back to the page's token when the type is not a resource this provider bridges. That fallback is what keeps every non-sibling case byte-identical to today.

3. Import examples in prose. Upstream embeds terraform import in backticked spans outside any fence. Those are rewritten now too.

4. tf/hcl-fenced HCL import {} blocks (#3585, found while verifying (1)–(3) against the live page). Only ```terraform was recognized for dropping, but upstream overwhelmingly writes ```tf. Leaving one in did more than render stray HCL — isHCL treats tf/hcl as convertible, pulumi convert rejects import blocks (Unsupported block type; Blocks of type "import" are not expected here), and convertExamplesInner responds by stripping the entire enclosing subsection: heading, prose, and sibling examples. Every H3 on the gcp IAM page opens with such a block, which is why the whole body disappeared. This was invisible in local repros that set PULUMI_CONVERT=0.

Result

Full parser path over upstream google_project_iam.html.markdown generating IAMMember. Before, one wrong pulumi import line and two unrewritten terraform import lines; after:

### Importing IAM members
$ pulumi import gcp:projects/iAMMember:IAMMember default "{{project_id}} roles/viewer user:foo@example.com"
### Importing IAM bindings
$ pulumi import gcp:projects/iAMBinding:IAMBinding default "{{project_id}} roles/viewer"
### Importing IAM policies
$ pulumi import gcp:projects/iAMPolicy:IAMPolicy default {{project_id}}
### Importing Audit Configs
$ pulumi import gcp:projects/iAMAuditConfig:IAMAuditConfig default "{{project_id}} foo.googleapis.com"

Each example carries its own resource's token, no terraform import survives, and no subsection is stripped.

Tests

Written failing first, then fixed.

  • TestParseImportCode — 30 cases pinning the regex. 24 pass on unfixed code and exist as the regression guard, including 10 "must NOT parse" cases (unquoted spaces, unterminated quote, two quoted args, trailing comment, HCL import {} block, prose mentions). 6 were red: the quoted space-delimited IDs.
  • TestParseImports_SpaceDelimitedIDs — the real upstream page, added as testdata.
  • TestParseImports_UnmappedResourceFallsBackToPageToken — pins the fallback so unbridged siblings keep today's behavior.
  • TestParseImports_DropsHCLImportBlocks — one subtest per fence tag; terraform passed as the guard, tf and hcl were red.

One golden moved, and it is an improvement: random-string-full-expected.md, two prose lines from terraform import random_string.test test to pulumi import random:index/string:String test test.

pkg/tfgen's 4 remaining failures are pre-existing — verified identical on a clean checkout of main in a throwaway worktree, and unrelated (simple:resource vs simple:Resource casing in example conversion).

Notes for the reviewer

  • dynamic goldens are deliberately untouched. TestSchemaGenerationFullDocs already fails on main for both random and fortimanager from unrelated drift. I regenerated both in a worktree and diffed: this change's only effect is the same two prose lines in random, and fortimanager comes out byte-identical. Committing them would drag that unrelated drift into this PR, so I left it to the accept-goldens workflow.
  • One cosmetic leftover. Dropping a tf fence leaves its introducing sentence dangling — "An import block (Terraform v1.5.0 and later) can be used…" now leads into the pulumi import example. Fixing it needs heuristics for detecting a preamble to a dropped block, which felt like the wrong thing to fold in here. Happy to file it.

🤖 Generated with Claude Code

Upstream documents a family of IAM resources on a single page, using
quoted, space-delimited composite IDs. Four defects compounded there, so
that a page like gcp:projects/iAMMember rendered no usable import
guidance at all.

parseImportCode's ID capture was a single whitespace-free token, so a
quoted ID containing spaces never matched and the `terraform import`
line was emitted verbatim. Widen the capture to accept a quoted string,
keeping the quotes in the emitted command.

That alone would make things worse: every example on a shared page would
then parse, and each would be stamped with the token of the page being
generated - so the IamMember example on the IamBinding page would become
a silently wrong `pulumi import`. Resolve each example's own Terraform
resource type through the provider's resource map instead, falling back
to the page's token when the type is not one this provider bridges.

Upstream also embeds `terraform import` examples in prose, outside any
fence. Rewrite those code spans too.

Finally, drop Terraform-only HCL `import { ... }` blocks whichever way
their fence is tagged. Only ```terraform was recognized, but upstream
overwhelmingly writes ```tf. Leaving one in did more than render stray
HCL: isHCL treats tf/hcl as convertible, `pulumi convert` rejects import
blocks, and convertExamples then strips the entire enclosing subsection.
That is why the Import body vanished on pages whose every subsection
opens with such a block.

Fixes #3584
Fixes #3585

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@eon-pulumi

eon-pulumi Bot commented Aug 20, 2026

Copy link
Copy Markdown

Review Verdict: Approved

Fixes a real, well-documented parsing bug (space-delimited quoted IAM import IDs, sibling-resource token misattribution, prose-embedded terraform import commands, and tf/hcl-fenced import blocks that were silently deleting whole doc subsections), backed by 30+ pinned regex test cases plus a real upstream testdata fixture whose output I verified matches the PR's claims exactly. All required CI checks are green; four parallel specialist passes (correctness, security, tests, compliance) found no issues above the confidence floor. Automated low-risk assessment, not a human review.


View session · Was this review helpful? Yes · No

@jkodroff
jkodroff requested a review from corymhall August 20, 2026 02:38
Comment thread pkg/tfgen/docs.go Outdated
Comment thread pkg/tfgen/import_code_test.go Outdated

@unblocked unblocked Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ No issues found

About Unblocked

Unblocked has been set up to automatically review your team's pull requests to identify genuine bugs and issues.

📖 Documentation — Learn more in our docs.

💬 Ask questions — Mention @unblocked to request a review or summary, or ask follow-up questions.

👍 Give feedback — React to comments with 👍 or 👎 to help us improve.

⚙️ Customize — Adjust settings in your preferences.

Scope the importTokens doc comment to Google Cloud, which is the only
provider observed to share one upstream page across an IAM triad, and
correct the copyright year on the new test file.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Aug 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 70.50%. Comparing base (2d1b4ed) to head (1842b79).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3586      +/-   ##
==========================================
+ Coverage   70.47%   70.50%   +0.02%     
==========================================
  Files         358      358              
  Lines       39474    39501      +27     
==========================================
+ Hits        27820    27850      +30     
+ Misses       9705     9703       -2     
+ Partials     1949     1948       -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@eon-pulumi-agent eon-pulumi-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed the parser/rewriter changes in pkg/tfgen/docs.go plus the new test file and testdata (pkg/tfgen/import_code_test.go, test_data/parse-imports/gcp-project-iam.md, and the random-string-full-expected.md golden update).

What I checked:

  • Ran the full new test suite (TestParseImportCode, TestParseImports_SpaceDelimitedIDs, TestParseImports_DropsHCLImportBlocks, TestParseImports_UnmappedResourceFallsBackToPageToken) plus the pre-existing pkg/tfgen suite — all green, no regressions.
  • Manually ran the parser against the included gcp-project-iam.md fixture and confirmed the output matches exactly what the PR description claims: every IAM-triad example gets its own resource's token, no terraform import survives, and no subsection is stripped. The one cosmetic leftover the author called out (a dangling preamble sentence after a dropped tf HCL block) is present and exactly as described, not something introduced beyond what's disclosed.
  • Traced the regex change (importCodePattern's new quoted-ID alternation), the importTokens.tokenFor sibling-resource fallback, rewriteInlineImportCode's prose rewriting, and the isHCLFenceInfo widening (terraform/tf/hcl) against the code paths that consume them (rewriteImportFence, rewriteImportMarkdown, convertExamplesInner). Behavior for non-IAM/ordinary resources (nil or absent ProviderInfo.Resources) is unchanged, matching the existing golden fixtures.
  • Four specialist passes (correctness, security, tests, AGENTS.md compliance) turned up no Important findings. Security found nothing exploitable (build-time-only doc rewriting over provider docs, RE2 regex engine rules out ReDoS). Compliance found no rule violations. Tests and correctness each surfaced only very low-confidence coverage nits (missing test cases for an already-narrow fallback branch, multi-span-per-line inline rewriting) that don't meet the bar for a comment on a PR this thoroughly tested.
  • Confirmed the pushed follow-up commit (tfgen: address review feedback) is a no-op comment/copyright-year tweak with no functional change.

CI: the one required check (Ensure test assets build cleanly) is green. A non-required dynamic package test (TestSchemaGenerationFullDocs/random) is failing, but that's the exact, explicitly-disclosed golden-drift the author called out in the PR description — deferred on purpose to the separate accept-goldens workflow rather than folded into this diff — and it matches a diff line the author explicitly pointed to.

No inline comments to post.

@jkodroff jkodroff changed the title tfgen: fix Import sections for IAM-style resources tfgen: fix Import sections for GCloud IAM-style resources Aug 20, 2026
The Import-section fix rewrites `terraform import` code spans that appear
in prose, outside a fence. The random provider's password and string docs
contain exactly those, so TestSchemaGenerationFullDocs needs its golden
refreshed alongside the tfgen test_data fixture.

Regenerated with `make test_accept`, whose scoped .pulumi-test plugin
cache keeps converted examples identical to CI's.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@guineveresaenger guineveresaenger left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this is good, although perhaps a bit too large-brush for something that we assume affects only GCP at this point.

There's a few "more correct" tweaks in here, too, that I think other providers can benefit from; however we're trading correctness with a few other resources when we're trying to determine the correct token for each pulumi import invocation.

What's the practicality tradeoff using a docs override inside Pulumi GCP as an alternative?

Comment thread pkg/tfgen/docs.go
// <some-ID>
//
// The ID is either a single whitespace-free token or a quoted string. Quoting is what lets a
// composite ID contain spaces, as IAM-style resources do:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This may happen to non-IAM resources also. Example is good; comment should be more generic.

Comment thread pkg/tfgen/docs.go
// these fences `tf` far more often than `terraform`, and the distinction matters: an HCL
// block left in the Import section is handed to convertExamples, which strips the entire
// enclosing subsection when the conversion fails.
func isHCLFenceInfo(info string) bool {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Any reason we can't use isHCL here?

Comment thread pkg/tfgen/docs.go

// tokenFor returns the token to use for an example importing the given Terraform resource
// type, falling back to the token of the resource being documented when that type is not a
// resource this provider bridges.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This would not give the right token when Docs.Source is set to override to another location. For example, this would give the wrong token for aws_alb.

Comment on lines +377 to +379
// TestParseImports_UnmappedResourceFallsBackToPageToken pins the fallback: when an example
// names a Terraform resource this provider does not bridge, we keep stamping the token of the
// page being generated rather than dropping the example.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this comment is unnecessary

Comment on lines +310 to +317
// TestParseImports_DropsHCLImportBlocks covers
// https://github.com/pulumi/pulumi-terraform-bridge/issues/3585.
//
// A Terraform `import { ... }` block is Terraform-only config syntax with no Pulumi analogue,
// so it has to be dropped from the Import section. Upstream tags these fences ```tf far more
// often than ```terraform, and leaving one in does more than render stray HCL: isHCL treats
// tf/hcl as convertible, the conversion cannot succeed, and convertExamples then strips the
// entire enclosing subsection - heading, prose and sibling examples included.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can the test name just be TestParseImports_DropsHCLImportBlocksForAllTerraformLanguageIdentifiers, and have no comment?

Comment thread pkg/tfgen/docs.go
}

// inlineImportCodeSpan matches a backtick-delimited code span in prose.
var inlineImportCodeSpan = regexp.MustCompile("`[^`]*`")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think it would be great to find a name for this variable that doesn't conflate "code" and "CLI command". We're not looking for a thing of code; we're looking for a shell command.
I'm pointing this out because we do look for real code blocks elsewhere in this file and it would be good to differentiate.

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

Labels

None yet

Projects

None yet

2 participants