Skip to content

[docs] Document Terraform modules in the Pulumi Cloud registry - #19453

Merged
cnunciato merged 14 commits into
masterfrom
fnune/registry/tf-modules-docs
Aug 3, 2026
Merged

[docs] Document Terraform modules in the Pulumi Cloud registry#19453
cnunciato merged 14 commits into
masterfrom
fnune/registry/tf-modules-docs

Conversation

@fnune

@fnune fnune commented Jun 1, 2026

Copy link
Copy Markdown
Member

Docs for hosting Terraform modules in the Pulumi Cloud registry, and for the Pulumi package every published version converts into.

  • Concept page (docs/idp/concepts/terraform-modules.md, new): authenticating with a Pulumi access token, publishing via go-tfe or the hashicorp/tfe provider, the module layout read at publish, migrating from HCP Terraform, what publishing produces, and consuming from a Pulumi program or from OpenTofu / Terraform.
  • Terraform module guide: its "Using a module from Pulumi Cloud" section installs the converted package by name.

pulumi package add <name>-<system> is the documented path for a Pulumi program on both pages. pulumi package add hcl module <address> is the fallback for a version that has no package. Usage tracking follows the package, so a consumer reaching the module over the Terraform protocol reports no dependency; both pages say so.

The launch blog post was split out into #20648, which depends on this PR for its links and canonical_url.

Notes:

  • The registry host is tf.pulumi.com (per pulumi/pulumi-service#44889), not app.pulumi.com, which is the console only.
  • Module names reject underscores, unlike HCP Terraform. Covered under migration.
  • Installing a converted package needs Pulumi CLI 3.248.0 or newer, verified by bisect: 3.247.0 fails the plugin handshake, 3.248.0 works.
  • The UI these pages describe is the package detail page, not the Terraform module detail page being removed from the console.

Design doc: https://app.notion.com/p/Terraform-module-hosting-in-the-Pulumi-Cloud-registry-372fdbdf1cce801ea7a3f4946c1e4154

Fixes https://github.com/pulumi/pulumi-service/issues/44080
Fixes #20555

Test plan

  1. Automated checks
    • ./scripts/format.sh, node scripts/lint/lint-markdown.js, and vale --config=.vale.ini: 0 errors
  2. Manual checks
    • make serve, confirm the "Terraform Modules" entry renders under IDP Concepts
    • Click through the internal links on both pages

@pulumi-bot

pulumi-bot commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator

@fnune fnune changed the title [registry] Document Terraform module hosting in the Pulumi Cloud registry [registry] Docs and launch blog for Terraform modules in the Pulumi Cloud registry Jun 18, 2026
@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Social Media Review

No blog post social copy required review in this run — .social-check-output.txt listed no posts (already-posted and too-old posts are filtered out before this review runs).

Note: the check did surface a build warning unrelated to copy content — the social card image for terraform-modules-pulumi-cloud-registry isn't reachable (https://www.pulumi.com/images/generated/blog/terraform-modules-pulumi-cloud-registry/index.png), so LinkedIn will fall back to a link unfurl instead of the generated card. Worth a look, but out of scope for a copy review.

Updated for commit 3722c8ce80f578a4c30e214948dc8b6ae164754b (short: 3722c8c) at 2026-07-31 13:02 UTC.

fnune and others added 7 commits July 30, 2026 18:26
…stry

Add a new concept page under IDP describing how Pulumi Cloud hosts Terraform modules: authentication via `terraform login`, the three HCP-compatible publish paths (go-tfe, hashicorp/tfe provider, tfc-workflows-github Action), the standard module layout we extract from at publish, consumption from OpenTofu / Terraform via the Module Registry Protocol, and consumption from a Pulumi program via `pulumi package add terraform-module`.

Cross-link from the existing "Use a Terraform Module in Pulumi" guide so consumers landing on the IaC guide can discover the Pulumi-Cloud-hosted path.

Design doc: https://app.notion.com/p/Terraform-module-hosting-in-the-Pulumi-Cloud-registry-372fdbdf1cce801ea7a3f4946c1e4154

Fixes pulumi/pulumi-service#44080

## Test plan

- Render the IDP concepts section locally and confirm the new "Terraform Modules" entry appears under the Concepts menu
- Click through every internal link in the new page to confirm targets exist
Add the announcement blog post for hosting Terraform modules in the
Pulumi Cloud registry, and correct the registry host across the docs and
blog from app.pulumi.com to the canonical tf.pulumi.com (per
pulumi/pulumi-service#44889; app.pulumi.com is the console only).

Move the migration guidance out of the blog and into the concept page,
and document the one naming rule that differs from HCP Terraform: module
names reject underscores.
The TFE discovery document (cmd/service/api/tfe_discover.go) advertises
tfe.v2, state.v2, and modules.v1 but no login.v1, so terraform login
against tf.pulumi.com is unsupported.

Document the real auth instead: a Pulumi access token is the bearer for
publish (go-tfe, the tfe provider, the GitHub Action), pulumi login for
pulumi package add, and TF_TOKEN_tf_pulumi_com for plain OpenTofu or
Terraform.
- Remove the tfc-workflows-github Action from the publish and delete
  paths and from the auth list. create-run triggers a run; it does not
  touch the module registry. The testbed only exercised go-tfe and the
  tfe provider.
- Consume from Pulumi: drop the rejected "CLI injects TF_TOKEN_<host>"
  detail. After pulumi login the provider resolves the module with your
  Pulumi credentials. Note that terraform-module is a parameterized
  provider and name its parameters.
- Module layout: parse all root .tf files (any filenames); examples and
  README are captured at publish, not rendered (console rendering is not
  built yet).
- Say packages, not components.
…ckend

Apply the Pulumi brand writing-style rules: sentence case for the
concept page title_tag/title/h1 and no over-capitalized concepts, so
"Terraform modules in the Pulumi Cloud registry" rather than title case.
Drop the "just" minimizer from the migration line.

Link "keep Terraform state in Pulumi Cloud" in the blog to the state
backend guide.
The Pulumi-program consume path now uses the forthcoming hcl package:
pulumi package add hcl module <source> [version], with the version
optional (omit for latest, pass to pin). Drops the terraform-module
alias parameter, since the hcl module form derives the package name
from the module.
@fnune
fnune force-pushed the fnune/registry/tf-modules-docs branch from bbd2a42 to 34dbab8 Compare July 30, 2026 16:26
@fnune
fnune force-pushed the fnune/registry/tf-modules-docs branch from 34dbab8 to 3722c8c Compare July 31, 2026 12:58
…and console URLs

Local conversion runs the same `hcl` provider the registry runs, so a module the
registry could not convert fails locally for the same reason. All three pages
offered it as the answer for a module using Terraform features Pulumi cannot
express, which it is not. It is now described as what it is: the same
conversion, run at the moment you run it, useful while a version is still
converting. The concept page's section is renamed to match and says outright
that it is not a way around a failed conversion.

The CLI version requirement links to Download & Install Pulumi on all three
pages, so a reader on an older CLI has somewhere to go.

The concept page picks up aliases for the two docs URLs the console links to,
both of which 404 today:

- `/docs/terraform-modules/`, from the package install card
- `/docs/iac/using-pulumi/pulumi-cloud/registry/terraform-modules/`, from the
  Terraform modules list page

The concept page's deletion section is removed while we decide what deleting a
converted package should do to the module it came from
(pulumi/pulumi-service#47170). Documenting module deletion now would describe
behavior we expect to change.

The post is dated 2026-08-04.
@github-actions

Copy link
Copy Markdown
Contributor

Pre-merge Review — Last updated 2026-07-31T14:13:01Z

Tip

Summary: This PR launches Terraform-module support in the Pulumi Cloud registry with three pieces: a new IDP concept page (content/docs/idp/concepts/terraform-modules.md, which closely parallels the existing private-registry.md — same "Before you begin" plan-gating wording, same API-reference and usage-tracking anchors), a new "Using a module from Pulumi Cloud" section appended to the existing use-terraform-module.md guide, and a launch blog post. The content is unusually well cross-referenced and the three files agree with each other, so the wrongness that would actually cost a reader here is not structural — it is a wrong literal string. Everything in all three files hangs on the registry host tf.pulumi.com and the token variable derived from it, and the one place a public source disagrees is exactly that name (see [L116]). Two other blockers are mechanical and will fail make lint as-is: the blog post has no category: and its meta_desc is 182 characters. This review ran a fact-check pass over the claims in all three files, read all 5 sibling pages under content/docs/idp/concepts/, checked every internal and external link target, and validated the blog front matter against the repo's enforced blog taxonomy. Because the implementing service change is not in a public repo, most feature-behavior claims could not be confirmed against source and are listed under ⚠️ for an author confirmation rather than treated as errors.

Review confidence:

Dimension Level Notes
mechanics HIGH Front matter, links, anchors, and blog taxonomy all checked against the enforcing linter and the live link targets.
facts LOW The feature ships from a repo that isn't public, so most behavior claims have no confirmable source; 23 are listed as unconfirmed.
coherence HIGH
cross-sibling consistency HIGH All 5 siblings under content/docs/idp/concepts/ read; the new page matches private-registry.md conventions.
code correctness MEDIUM Snippets are short and idiomatic, but the go-tfe and .tf samples embed the host string that [L116] puts in doubt.
Investigation log
  • Cross-sibling reads: 5 of 5 siblings
  • External claim verification: 67 of 96 claims verified (19 unverifiable, 8 contradicted) · 4 specialists (numerical, cross-reference, capability, framing); 0 cross-specialist corroborations · routed: 0 inline, 67 Pass 1, 10 Pass 2 (verified 2, contradicted 6, unverifiable 2), 19 Pass 3 (verified 5, contradicted 1, unverifiable 13).
  • Cited-claim spot-checks: 10 of 10 cited claims fetched and compared
  • Frontmatter sweep: ran on body + meta_desc + social.{linkedin, twitter}
  • Temporal-trigger sweep: ran (recency words present in diff; spot-check in-review)
  • Code execution: not run (no static/programs/ change)
  • Code-examples checks: ran (3 specialists: structural, existence, body-code-coverage); 0 findings
  • Editorial-balance pass: ran (single-subject, N/A)
🚨 Outstanding ⚠️ Low-confidence 💡 Pre-existing ✅ Resolved
5 29 0 0

🔍 Verification trail

103 claims extracted · 67 verified · 23 unverifiable · 10 contradicted
  • L2 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "Converting a Terraform module to a Pulumi package does not require rewriting the module." (also L27) → ✅ verified (evidence: The pulumi-hcl docs confirm an HCL module becomes a Multi-Language Component just by having a PulumiPlugin.yaml with runtime: hcl and its existing variable/output blocks become the typed inputs/outputs -- no rewrite of the module's o…; source: gh api repos/pulumi-labs/pulumi-hcl/contents/docs/mlc.md (redirects to pulumi/pulumi-hcl master))
  • L3 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "date: 2026-06-18" → ➖ not-a-claim (evidence: This is blog post frontmatter metadata (publish date) set by the PR author, not a falsifiable third-party assertion that can be verified against external sources.; source: content/blog/terraform-modules-pulumi-cloud-registry/index.md)
  • L3 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "date: 2026-08-04" → ➖ not-a-claim (evidence: This is a blog post frontmatter publish date field, not a falsifiable factual assertion. It's a scheduled future publish date (Aug 4, 2026, just days after the current date of July 31, 2026), consistent with normal blog scheduling practice.; source: content/blog/terraform-modules-pulumi-cloud-registry/index.md (frontmatter))
  • L5 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "When a Terraform module is converted to a Pulumi package, it becomes a typed component with a generated SDK in the consuming language, an API reference, and us…" (also L18, L24, L27, L73, L75) → ✅ verified (evidence: The pulumi/pulumi-hcl repo's docs/mlc.md confirms the underlying mechanism: "Multi-Language Components allow you to author reusable Pulumi components in HCL that can be consumed from any Pulumi language (TypeScript, Python, Go, C#, Java, Y…; source: gh api repos/pulumi/pulumi-hcl/contents/docs/mlc.md)
  • L5 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "Pulumi Cloud registry lets you publish private Terraform modules and get a typed Pulumi package for every version." → 🤷 unverifiable (evidence: The cited implementing change pulumi/pulumi-service#44889 lives in a private/inaccessible repo, so the feature cannot be confirmed against source. However, the claim is a direct, faithful summary of the blog post's own body content: "The r…; source: the implementing service change is not in a public repository, so it could not be read; compared instead against repo:content/docs/idp/concepts/terraform-modules.md)
  • L15 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "This blog post's canonical URL is https://www.pulumi.com/docs/idp/concepts/terraform-modules/." → ❌ contradicted (evidence: The cited canonical URL https://www.pulumi.com/docs/idp/concepts/terraform-modules/ returns HTTP 404 Not Found, so it cannot serve as the canonical URL for this blog post.; source: https://www.pulumi.com/docs/idp/concepts/terraform-modules/)
  • L18 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "Publishing a Terraform module to the Pulumi Cloud registry does two things: the version becomes available over the Terraform module protocol as it would on HCP…" (also L20, L27, L63) → ✅ verified (evidence: The blog's canonical docs page (content/docs/idp/concepts/terraform-modules.md) states: "Publishing a module version also converts it into a Pulumi package, with no extra step on your part" and separately describes the registry as "wire-co…; source: repo:content/docs/idp/concepts/terraform-modules.md)
  • L18 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "The Pulumi Cloud registry is wire-compatible with HCP Terraform's private module registry, so tooling that already works with HCP Terraform's publish and consu…" (also L22, L35, L37, L40) → 🤷 unverifiable (evidence: (escalated from pass1) Web search found no public documentation or announcement confirming that the Pulumi Cloud registry is wire-compatible with HCP Terraform's private module registry API. The implementing change (pulumi/pulumi-service#4…; source: WebSearch ran query "Pulumi Cloud registry wire-compatible HCP Terraform private module registry"; top results didn't address the claim; intuition: Claim asserts a specific technical compatibility (wire-compatibility with HCP Terraform's registry API) that would be a…)
  • L31 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "Storing Terraform modules in the Pulumi Cloud registry puts them in the same place and behind the same login as Terraform state already kept in Pulumi Cloud." → ✅ verified (evidence: The linked terraform-state-backend doc confirms Pulumi Cloud state backend auth uses Pulumi Cloud login/access tokens (terraform login api.pulumi.com, TF_TOKEN_api_pulumi_com), and the same blog post states the module registry uses "a…; source: repo:content/docs/iac/get-started/terraform/terraform-state-backend.md; repo:content/blog/terraform-modules-pulumi-cloud-registry/index.md)
  • L37 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "For plain OpenTofu or Terraform, the Pulumi access token must be set as the host token via the TF_TOKEN_tf_pulumi_com environment variable." (also L40) → ✅ verified (evidence: Sibling doc content/docs/iac/get-started/terraform/terraform-state-backend.md documents the same Terraform/OpenTofu convention: "For non-interactive environments like CI/CD, set the TF_TOKEN_api_pulumi_com environment variable" for host ap…; source: repo:content/docs/iac/get-started/terraform/terraform-state-backend.md)
  • L37 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "When publishing a module, you supply the Pulumi access token to go-tfe (or the tfe provider) in exactly the place an HCP token goes today." → ✅ verified (evidence: The blog text itself states: "When you publish, you hand it to go-tfe (or the tfe provider) exactly where an HCP token goes today," and the accompanying code sample shows Token: os.Getenv(\"PULUMI_ACCESS_TOKEN\") passed to `tfe.NewClient…; source: repo:content/blog/terraform-modules-pulumi-cloud-registry/index.md)
  • L37 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "When consuming a module from a Pulumi program, running pulumi login is sufficient because the CLI passes the access token through automatically." → ✅ verified (evidence: (escalated from pass1) Pulumi docs confirm the token obtained via pulumi login is used automatically by the CLI for authenticated operations: "The token you use for pulumi login also authorizes the pulumi api command, which calls any RES…; source: https://www.pulumi.com/docs/administration/access-identity/access-tokens/)
  • L43 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "This writes a bearer token to ~/.terraform.d/credentials.tfrc.json. Self-hosted installations follow the same flow against their own host." → ✅ verified (evidence: HashiCorp's docs confirm: "Terraform will obtain an API token and save it in plain text in a local CLI configuration file called credentials.tfrc.json" and that "terraform login command works with any server supporting the login protocol,…; source: https://www.terraform.io/cli/commands/login)
  • L45 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "Publishing Terraform modules to the Pulumi Cloud registry is available on the Enterprise and Business Critical plans." → ✅ verified (framing: strengthened — the docs page confirms Enterprise/Business Critical gating for private registry publishing generally (components and templates); the claim narro…; evidence: (escalated from pass1) Pulumi's Private Registry docs state the same plan gating pattern: "You need a Pulumi Cloud account on the Enterprise or Business Critical plan" to publish components/templates to the private registry, which is the s…; source: https://www.pulumi.com/docs/idp/concepts/private-registry/)
  • L47-56 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "A go-tfe client can be configured with tfe.NewClient(&tfe.Config{Address: 'https://tf.pulumi.com', Token: os.Getenv('PULUMI_ACCESS_TOKEN')}) to publish to th…" → 🤷 unverifiable (evidence: The cited source_hint is the go-tfe GitHub repo, not https://tf.pulumi.com directly; the claim is about API usage of go-tfe's Config struct, which is a code-level claim not directly verifiable against the endpoint. The pre-fetched https://…; source: https://tf.pulumi.com; intuition: A 404 on the bare domain doesn't confirm/deny that the Terraform-compatible API endpoint (used by go-tfe for authentica…)
  • L47 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "The go-tfe library can be retargeted to the Pulumi Cloud registry by setting Address to https://tf.pulumi.com." → ❌ contradicted (evidence: Cited URL https://tf.pulumi.com returns HTTP 404 Not Found with an empty body, so it cannot be confirmed as a valid retargeting address for go-tfe.; source: https://tf.pulumi.com)
  • L57 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "Address: 'https://tf.pulumi.com'," → ❌ contradicted (evidence: The cited URL https://tf.pulumi.com returns HTTP 404 Not Found with an empty body, so the claimed address cannot be confirmed as a live/valid endpoint.; source: https://tf.pulumi.com)
  • L59 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "At publish time, the Pulumi Cloud registry reads the standard Terraform module layout, where root .tf files describe the module's inputs and outputs, `module…" → 🤝 matches (evidence: The sibling reference doc content/docs/idp/concepts/terraform-modules.md states nearly verbatim: "At publish time Pulumi Cloud reads the standard Terraform module structure: Root .tf files ... define the module's inputs, outputs...; module…; source: repo:content/docs/idp/concepts/terraform-modules.md)
  • L62 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "At publish time the registry reads the standard Terraform module layout. Root `…" → ✅ verified (evidence: The cited HashiCorp page "Standard Module Structure" is the authoritative doc describing root .tf files, modules// subdirectories for submodules, and examples// subdirectories for examples—matching the claim's description, thou…; source: https://developer.hashicorp.com/terraform/language/modules/develop/structure)
  • L65 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "A converted Pulumi package takes its name from the module using the pattern <name>-<system>, in the same namespace as the module." (also L67-68) → 🤷 unverifiable (evidence: The claim's naming pattern is internally consistent with the worked example in the same doc ("A module published as acme-corp/vpc/aws becomes a package called vpc-aws"), but the implementing repo pulumi/pulumi-service is private (404 v…; source: gh api repos/pulumi/pulumi-service (404 Not Found); repo:content/blog/terraform-modules-pulumi-cloud-registry/index.md)
  • L65 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "A module published as acme-corp/vpc/aws becomes a package called vpc-aws." → ✅ verified (evidence: The doc states the naming formula one line earlier: "The package takes its name from the module: <name>-<system>, in the same namespace." Applying this to a module source acme-corp/vpc/aws (namespace=acme-corp, name=vpc, system=aws) co…; source: repo:content/blog/terraform-modules-pulumi-cloud-registry/index.md L65; gh api repos/pulumi/pulumi-hcl/contents/registry/_index.md)
  • L71 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "Installing a converted Terraform-module-derived package requires Pulumi CLI 3.248.0 or newer." → 🤷 unverifiable (evidence: (escalated from pass1) I confirmed Pulumi CLI 3.248.0 is a real release (published June 24, 2026, per npm/PyPI), so the version number is plausible, but no public source (Pulumi docs, blog, or changelog) specifically ties CLI 3.248.0 to th…; source: WebSearch ran query "Pulumi Cloud Registry Terraform module conversion blog CLI version requirement"; top results didn't address the specific version claim)
  • L73 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "In a converted package, a misspelled input fails at compile time rather than partway through a plan." → ✅ verified (evidence: The pulumi-hcl MLC docs confirm converted modules become typed components with generated SDKs per language (TypeScript, Python, Go, C#, Java, YAML/HCL) and validated component/package identity. Generated typed SDKs in Pulumi expose inputs…; source: gh api repos/pulumi/pulumi-hcl/contents/docs/mlc.md (MLC doc: "Multi-Language Components allow you to author reusable Pulumi components in HCL that can be consumed from any Pulumi language (TypeScript, Python, Go, C#, Java, YAML, or HCL)"))
  • L73 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "Resources created by a converted module appear individually in previews and in the resource graph." → ✅ verified (framing: strengthened — claim states a specific consequence ("resources appear individually in previews/resource graph") of the general, well-established Pulumi Compone…; evidence: The pulumi-hcl mlc.md doc confirms a converted Terraform module "becomes an MLC" (Multi-Language Component) which is declared as "a component resource" with a standard resource token ({package.name}:{component.module}:{component.name}).…; source: gh api repos/pulumi-labs/pulumi-hcl/contents/docs/mlc.md)
  • L73 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "The module becomes a multi-language component. Its variable blocks become typed inputs and…" → ❌ contradicted (evidence: The cited URL https://github.com/pulumi-labs/pulumi-hcl/blob/main/docs/mlc.md returns HTTP 404 Not Found, so the claim's source cannot be verified as it does not exist.; source: https://github.com/pulumi-labs/pulumi-hcl/blob/main/docs/mlc.md)
  • L75 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "Pulumi Cloud treats it as it would any other package. The package page carries an API reference generated from the module's variables and outputs, and records…" → ✅ verified (evidence: The claim matches the blog post's own linkedin summary in the same file: "Pulumi Cloud gives it an API reference, tracks which stacks depend on it, and shows which of them have fallen behind the latest version." The implementing change (pu…; source: repo:content/blog/terraform-modules-pulumi-cloud-registry/index.md (L24, L75))
  • L77 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "Conversion of a Terraform module into a Pulumi package runs per version, so a module can have some versions with corresponding packages and some without." → 🤝 matches (evidence: The canonical docs page (content/docs/idp/concepts/terraform-modules.md), which this blog post's front matter links to as canonical_url, states verbatim: "Conversion runs per version, so a module can have some versions with packages and so…; source: repo:content/docs/idp/concepts/terraform-modules.md)
  • L81 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "tofu init (or terraform init) resolves a module reference against Pulumi Cloud using the token from the TF_TOKEN_tf_pulumi_com environment variable." → ✅ verified (evidence: The same blog post establishes tf.pulumi.com as the registry host and instructs users to export TF_TOKEN_tf_pulumi_com=$PULUMI_ACCESS_TOKEN for plain OpenTofu/Terraform use (line 37-40). This follows Terraform/OpenTofu's standard `TF_T…; source: repo:content/blog/terraform-modules-pulumi-cloud-registry/index.md (lines 37-41, 79-81); the implementing service change is not in a public repository and could not be read)
  • L81 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "A .tf file references a module hosted on the Pulumi Cloud registry the same way it would reference any private module." → ✅ verified (evidence: The blog itself states: "The registry is wire-compatible with HCP Terraform's private module registry... Nothing about the module changes for the people already consuming it. A .tf file references it as it would any private module" and s…; source: repo:content/blog/terraform-modules-pulumi-cloud-registry/index.md (L79-87); gh api repos/pulumi/pulumi-service returned 404 (private repo, inaccessible))
  • L82 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "terraform-module is a parameterized provider, and its parameters are the module address, the version, and a local name for the generated package. After `pulu…" → ✅ verified (evidence: Source (pulumi/pulumi-terraform-module pkg/modprovider/params.go) defines ParameterizeArgs with exactly TFModuleSource (json "module"), TFModuleVersion (json "version"), and PackageName (json "packageName") — the module address, version, a…; source: gh api repos/pulumi/pulumi-terraform-module/contents/pkg/modprovider/registry_token.go and params.go (main branch))
  • L83-88 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "A module hosted on the Pulumi Cloud registry can be referenced in Terraform HCL with source = 'tf.pulumi.com/<namespace>/<name>/<system>' and a version att…" → ✅ verified (evidence: The blog post itself documents this exact syntax at lines 83-88: source = "tf.pulumi.com/<namespace>/<name>/<system>" with a version attribute, consistent with the rest of the document which repeatedly states the registry is "wire-comp…; source: repo:content/blog/terraform-modules-pulumi-cloud-registry/index.md)
  • L85 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "The CLI authenticates against your configured Pulumi Cloud URL, so there is no separate login step. From there the module behaves like any other Pulumi package…" → ✅ verified (evidence: The blog itself states earlier: "When you consume from a Pulumi program, pulumi login is enough and the CLI passes it through," matching the claim that the CLI authenticates via the configured Pulumi Cloud login with no separate step. Th…; source: gh api repos/pulumi/docs/contents/content/docs/iac/guides/building-extending/using-existing-tools/use-terraform-module.md)
  • L89-90 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "Submodules of a Terraform module hosted on Pulumi Cloud are referenced using the usual //modules/<name> syntax." → ✅ verified (evidence: The cited page content/docs/idp/concepts/terraform-modules.md states: "Submodules are referenced with the standard //modules/<name> source syntax:" followed by an example `source = "tf.pulumi.com/////modules/<sub…; source: repo:content/docs/idp/concepts/terraform-modules.md)
  • L92 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "The same conversion of a Terraform module to a Pulumi package can be run locally with pulumi package add hcl module tf.pulumi.com/<namespace>/<name>/<system>." → ✅ verified (evidence: The cited docs page (use-terraform-module.md) itself contains the identical command: "pulumi package add hcl module tf.pulumi.com/// []" with surrounding text explaining "The same conversion can also be ru…; source: repo:content/docs/iac/guides/building-extending/using-existing-tools/use-terraform-module.md; gh api repos/pulumi/pulumi-hcl/contents/pkg/server/parameterize.go)
  • L96 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "The Terraform Modules in the Pulumi Cloud Registry documentation page (/docs/idp/concepts/terraform-modules/) covers the full publish and consume reference, in…" → ✅ verified (evidence: The page at /docs/idp/concepts/terraform-modules/ contains a "Migrating from HCP Terraform" section, a "Module names" section with naming restrictions ("the module name must be lowercase letters, digits, and hyphens... so underscores are r…; source: repo:content/docs/idp/concepts/terraform-modules.md)
  • L87-98 in content/docs/iac/guides/building-extending/using-existing-tools/use-terraform-module.md "A package installed from the Pulumi Cloud registry conversion is the same as any other Pulumi package: it provides a generated SDK in the user's language, an A…" → 🤝 matches (evidence: Sibling page content/docs/idp/concepts/private-registry.md confirms both features cited: "API documentation is automatically generated listing the component's inputs and outputs" and "Usage tracking... display usage columns for each packag…; source: repo:content/docs/idp/concepts/private-registry.md)
  • L89 in content/docs/iac/guides/building-extending/using-existing-tools/use-terraform-module.md "A Pulumi Cloud registry package converted from a Terraform module is installed by package name, which is the module's name and system joined with a hyphen." → ✅ verified (evidence: The same doc explicitly demonstrates this exact convention: "A module published as acme-corp/vpc/aws installs as vpc-aws" and states "Install it by package name, which is the module's name and system joined with a hyphen" — directly ma…; source: repo:content/docs/iac/guides/building-extending/using-existing-tools/use-terraform-module.md (L89, L95))
  • L92 in content/docs/iac/guides/building-extending/using-existing-tools/use-terraform-module.md "The command pulumi package add <name>-<system> [<version>] installs a Pulumi Cloud registry package converted from a Terraform module." → ✅ verified (evidence: The doc itself states: "Install it by package name, which is the module's name and system joined with a hyphen: pulumi package add <name>-<system> [<version>]" with surrounding context describing the Pulumi Cloud registry's Terraform-mod…; source: repo:content/docs/iac/guides/building-extending/using-existing-tools/use-terraform-module.md)
  • L95 in content/docs/iac/guides/building-extending/using-existing-tools/use-terraform-module.md "Instructions to download and install Pulumi are available at /docs/install/." → ✅ verified (evidence: content/docs/install/_index.md exists at the /docs/install/ route and provides "detailed instructions for downloading and installing Pulumi" (meta_desc), including OS-specific install methods (Homebrew, install script, MSI, etc.), matching…; source: repo:content/docs/install/_index.md)
  • L95 in content/docs/iac/guides/building-extending/using-existing-tools/use-terraform-module.md "The version is optional; omit it to resolve the latest published version. Self-hosted Pulumi Cloud installations use their own host (<your-pulumi-host>/<names…" → ✅ verified (evidence: pulumi-hcl's parameterize.go confirms the CLI syntax pulumi package add hcl module [version] with version as an optional trailing argument (Version string `json:"version,omitempty"``, and the code accepts either 1 arg (source…; source: gh api repos/pulumi/pulumi-hcl/contents/pkg/server/parameterize.go)
  • L95 in content/docs/iac/guides/building-extending/using-existing-tools/use-terraform-module.md "A package converted from a Terraform module and installed via the Pulumi Cloud registry is the same as any other Pulumi package in that you get a generated SDK…" → ✅ verified (evidence: The source doc (use-terraform-module.md L95) itself states: "This is the same as any other Pulumi package: you get a generated SDK in your language, an API reference on the package's page, and usage tracking..." — matching the claim exactl…; source: repo:content/docs/iac/guides/building-extending/using-existing-tools/use-terraform-module.md;repo:content/docs/idp/concepts/private-registry.md)
  • L103 in content/docs/iac/guides/building-extending/using-existing-tools/use-terraform-module.md "Running pulumi package add hcl module tf.pulumi.com/<namespace>/<name>/<system> [<version>] performs the conversion at the moment the command is run, using w…" → ✅ verified (evidence: The pulumi/pulumi-hcl source (pkg/server/parameterize.go) confirms the CLI path pulumi package add hcl module <source> [version] "downloads the module tree and bundles it" live at package add time, in contrast to the "usage path" which…; source: gh api repos/pulumi/pulumi-hcl/contents/pkg/server/parameterize.go)
  • L103 in content/docs/iac/guides/building-extending/using-existing-tools/use-terraform-module.md "Running pulumi package add hcl module tf.pulumi.com/<namespace>/<name>/<system> [<version>] performs the module conversion at the moment the command is run,…" → ✅ verified (evidence: pulumi/pulumi-hcl's pkg/server/parameterize.go source comment states: "parameterize configures the provider for a specific module source. Args is the // CLI path (pulumi package add hcl module <source> [version]): it downloads the // mod…; source: gh api repos/pulumi/pulumi-hcl/contents/pkg/server/parameterize.go)
  • L105 in content/docs/iac/guides/building-extending/using-existing-tools/use-terraform-module.md "The publishing side of Terraform modules and the broader module workflow are documented at 'Terraform Modules in the Pulumi Cloud Registry' (/docs/idp/concepts…" → ✅ verified (evidence: The linked page content/docs/idp/concepts/terraform-modules.md ("Terraform modules in the Pulumi Cloud registry") contains a "Publish a module" section, "Migrating from HCP Terraform", and "What happens when you publish" sections, confirmi…; source: repo:content/docs/idp/concepts/terraform-modules.md)
  • L13 in content/docs/idp/concepts/terraform-modules.md "Existing .tf consumers of a Terraform module are unaffected by the module's conversion to a Pulumi package and keep resolving the module over the Terraform pro…" → ✅ verified (evidence: The doc itself states: "Every module version you publish is also converted into a Pulumi package... Existing .tf consumers are unaffected and keep resolving the module over the Terraform protocol." This is corroborated later: "Usage trac…; source: repo:content/docs/idp/concepts/terraform-modules.md)
  • L13 in content/docs/idp/concepts/terraform-modules.md "Teams migrating from HCP Terraform can publish their existing modules to Pulumi Cloud using the same tooling they already use (the go-tfe library or the hashic…" → 🤷 unverifiable (evidence: (escalated from pass1) Pulumi Cloud does expose a Terraform module registry API (e.g. "GET /api/registry/terraform-modules/{source}/{publisher}/{name}/versions · Lists all versions of a Terraform module"), confirming module-registry hostin…; source: https://www.pulumi.com/docs/reference/cloud-rest-api/registry/; intuition: Claim asserts a specific, narrow drop-in-compatibility detail (hostname + exact tool names) that reads like insider/imp… (WebSearch dispatched but verification did not converge within the turn budget))
  • L17-19 in content/docs/idp/concepts/terraform-modules.md "Reading and listing Terraform modules in Pulumi Cloud is available on any plan, so users always keep access to modules they have already published." → 🤷 unverifiable (evidence: The cited URL (https://app.pulumi.com) is the Pulumi Cloud app login/landing page, which returns only a generic 'Pulumi Cloud' body with no content about plan tiers or Terraform module read/list permissions. This is not an authoritative so…; source: https://app.pulumi.com)
  • L23 in content/docs/idp/concepts/terraform-modules.md "Every surface of Pulumi Cloud's Terraform module functionality authenticates with a Pulumi access token, which serves as the bearer token for the publish API,…" → 🤷 unverifiable (evidence: The claim paraphrases the doc's own "Authenticate" section verbatim ("Every surface authenticates with a Pulumi access token. It is the bearer token for everything Pulumi Cloud exposes over the HashiCorp protocol: the publish API, the stat…; source: gh api repos/pulumi/pulumi-service (404 Not Found - private repo, inaccessible); intuition: Claim is a faithful restatement of the doc's own text describing a brand-new feature (Terraform module registry) whose…)
  • L25 in content/docs/idp/concepts/terraform-modules.md "The go-tfe client and the tfe provider accept a Pulumi access token wherever they expect a TFE token today." → 🤷 unverifiable (evidence: (escalated from pass1) The claim describes an internal implementation detail of pulumi-service (PR #44889) about accepting Pulumi access tokens in place of TFE tokens for go-tfe/tfe provider auth. pulumi-service is a private/internal repo…; source: WebSearch ran query "pulumi-service#44889 TFE token compatibility"; top results didn't address the claim)
  • L26 in content/docs/idp/concepts/terraform-modules.md "When consuming a Terraform module from a Pulumi program, running pulumi package add terraform-module passes the login token through to the provider, so there i…" → ✅ verified (evidence: Source code in pulumi/pulumi-terraform-module (pkg/modprovider/registry_token.go) implements injectRegistryToken, commented: "injectRegistryToken lets tofu init authenticate to the Pulumi Cloud module registry without a separate `terra…; source: gh api repos/pulumi/pulumi-terraform-module/contents/pkg/modprovider/registry_token.go)
  • L26 in content/docs/idp/concepts/terraform-modules.md "The pulumi package add terraform-module command passes the authentication token through to the provider, so there is no separate registry login required." → ✅ verified (evidence: The same doc's "Authenticate" section states verbatim: "Consuming from a Pulumi program: run pulumi login. pulumi package add terraform-module passes the token through to the provider, so there is no separate registry login." The claim…; source: repo:content/docs/idp/concepts/terraform-modules.md)
  • L27 in content/docs/idp/concepts/terraform-modules.md "OpenTofu and Terraform derive the host token environment variable name from the host by replacing dots with underscores and dashes with double underscores, so…" (also L30) → ✅ verified (evidence: pulumi-terraform-module's registry_token.go has a function tfTokenEnvKey with comment "builds the TF_TOKEN_ variable name using OpenTofu's host-encoding convention", and its test asserts `tfTokenEnvKey(svchost.Hostname("tfe.pulumi.…; source: gh search code --owner pulumi "TF_TOKEN_" (pulumi/pulumi-terraform-module:pkg/modprovider/registry_token.go, registry_token_test.go))
  • L33 in content/docs/idp/concepts/terraform-modules.md "The Terraform CLI credentials file is located at ~/.terraform.d/credentials.tfrc.json and can be used to store the host token instead of an environment variabl…" → ✅ verified (evidence: HashiCorp source/scripts confirm the file: hashicorp/tfc-getting-started's setup.sh sets CREDENTIALS_FILE="$HOME/.terraform.d/credentials.tfrc.json" (Unix) and hashicorp/vscode-terraform notes ~/.terraform.d/credentials.tfrc.json as th…; source: gh search code credentials.tfrc.json --owner hashicorp (hashicorp/tfc-getting-started scripts/setup.sh, hashicorp/vscode-terraform authenticationProvider.ts))
  • L33 in content/docs/idp/concepts/terraform-modules.md "Self-hosted installations of Pulumi Cloud use the same token-naming scheme with their own host." → ✅ verified (evidence: A sibling docs page (terraform-state-backend.md) confirms the same pattern: "If you are using a self-hosted Pulumi Cloud instance, replace api.pulumi.com with your instance's API URL in: The TF_TOKEN_* variable name (e.g., TF_TOKEN_your_pu…; source: repo:content/docs/iac/get-started/terraform/terraform-state-backend.md)
  • L35 in content/docs/idp/concepts/terraform-modules.md "Pulumi Cloud's publish API is wire-compatible with HCP Terraform's private registry. Existing HCP migration tooling works unmodified, just pointed at the new h…" → ✅ verified (framing: Minor drift: claim text says "three most common paths" and "just pointed at the new host"; live file says "two most common paths" and "pointed at the new host"…; evidence: The live doc file itself states: "Pulumi Cloud's publish API is wire-compatible with HCP Terraform's private registry. Existing HCP migration tooling works unmodified, pointed at the new host." This confirms the substantive claim, though t…; source: repo:content/docs/idp/concepts/terraform-modules.md)
  • L37 in content/docs/idp/concepts/terraform-modules.md "Existing HCP Terraform migration tooling works unmodified against Pulumi Cloud when pointed at the new host." → ❌ contradicted (framing: shifted — claim says HCP Terraform migration tooling "works unmodified" but Pulumi's own docs say HCP Terraform migration requires manual state export/push, no…; evidence: (escalated from pass1) Pulumi's own docs state the opposite for HCP Terraform specifically: "HCP Terraform does not support automatic state migration using terraform init -migrate-state. You need to export the state m…; source: https://www.pulumi.com/docs/iac/get-started/terraform/terraform-state-backend/)
  • L41-46 in content/docs/idp/concepts/terraform-modules.md "The go-tfe client can be configured with tfe.NewClient using Address 'https://tf.pulumi.com' and a Token sourced from the PULUMI_ACCESS_TOKEN environment varia…" → ❌ contradicted (evidence: The cited URL https://tf.pulumi.com returns HTTP 404 Not Found with an empty body, so it cannot be confirmed as a valid endpoint for the go-tfe client configuration as claimed.; source: https://tf.pulumi.com)
  • L48 in content/docs/idp/concepts/terraform-modules.md "The go-tfe RegistryModules surface, including client.RegistryModules.Create, CreateVersion, and UploadTarGzip, accepts the same payloads against tf.pulumi.com…" → 🤷 unverifiable (evidence: (escalated from pass1) Public web search surfaced general Pulumi docs on consuming Terraform modules and go-tfe's public API reference, but nothing addresses the specific internal implementation claim that tf.pulumi.com's RegistryModules e…; source: WebSearch ran query "Pulumi IDP terraform module registry go-tfe compatible tf.pulumi.com private registry API"; top results didn't address the claim)
  • L52-62 in content/docs/idp/concepts/terraform-modules.md "The hashicorp/tfe Terraform provider can publish a registry module to Pulumi Cloud using a provider block with hostname 'tf.pulumi.com' and a token variable, t…" → ✅ verified (evidence: The doc shows the exact snippet: provider \"tfe\" { hostname = \"tf.pulumi.com\" token = var.pulumi_access_token } followed by resource \"tfe_registry_module\" \"vpc\" { organization = \"acme\" ... }, consistent with the standard hashi…; source: repo:content/docs/idp/concepts/terraform-modules.md (L37-62); pulumi/pulumi-service#44889 (private, not directly accessible))
  • L58-61 in content/docs/idp/concepts/terraform-modules.md "The tfe_registry_module Terraform resource type can be used to publish a registry module with an organization attribute such as 'acme'." → ✅ verified (evidence: HashiCorp's official terraform-provider-tfe repo shows resource "tfe_registry_module" "petstore" { organization = tfe_organization.test-organization.name ... } — confirming the tfe_registry_module resource type takes an organization attr…; source: gh search code --owner hashicorp terraform-provider-tfe tfe_registry_module organization; content/docs/idp/concepts/terraform-modules.md L58-61)
  • L66 in content/docs/idp/concepts/terraform-modules.md "At publish time, Pulumi Cloud reads the standard Terraform module structure as documented by HashiCorp." → ✅ verified (evidence: The cited HashiCorp page (200 OK) is titled "Standard Module Structure" and documents the standard Terraform module layout, confirming this is the structure HashiCorp documents that Pulumi Cloud would parse; page content is truncated but t…; source: https://developer.hashicorp.com/terraform/language/modules/develop/structure)
  • L68 in content/docs/idp/concepts/terraform-modules.md "Root .tf files (any filenames) in a Terraform module define the module's inputs, outputs, and required providers." → ✅ verified (evidence: The doc states Pulumi Cloud reads the "standard Terraform module structure" (linking to HashiCorp's module structure docs), and per that convention, root .tf files (any filenames, typically main.tf/variables.tf/outputs.tf) collectively def…; source: content/docs/idp/concepts/terraform-modules.md:66-68; https://developer.hashicorp.com/terraform/language/modules/develop/structure)
  • L69-70 in content/docs/idp/concepts/terraform-modules.md "modules// subdirectories in a Terraform module are parsed the same way as the root by Pulumi Cloud and can be consumed as submodules." → 🤷 unverifiable (evidence: The doc text at L68-70 describes Pulumi Cloud parsing the standard Terraform module structure (linking to HashiCorp's docs on modules// subdirectories), which is consistent with standard Terraform module conventions, but the implemen…; source: gh pr view 44889 -R pulumi/pulumi-service (repository inaccessible))
  • L70 in content/docs/idp/concepts/terraform-modules.md "examples// subdirectories and the README.md file are captured at publish time by Pulumi Cloud." → 🤷 unverifiable (evidence: (escalated from pass1) Related Pulumi IDP Private Registry docs confirm README capture behavior for components generally: "A README is required when publishing a component... By default, the Pulumi CLI looks for a README in the component's…; source: WebSearch ran query "Pulumi IDP publish Terraform module registry examples directory README pulumi-service"; top results didn't specifically address Terraform-module examples/README capture behavior)
  • L73 in content/docs/idp/concepts/terraform-modules.md "At publish time Pulumi Cloud extracts the following from the standard [Terraform module structure](https://developer.hashicorp.com/terraform/language/modules/d…" → ✅ verified (evidence: (escalated from pass1) The linked page is titled "Standard Module Structure | Terraform | HashiCorp Developer" and describes the root module, README, examples/ directory conventions referenced by the claim's anchor text: "This is the only…; source: https://developer.hashicorp.com/terraform/language/modules/develop/structure)
  • L74 in content/docs/idp/concepts/terraform-modules.md "Migrating a CI-based Terraform module publish pipeline to Pulumi Cloud is mostly a host change: pointing existing pipelines at tf.pulumi.com and supplying a Pu…" → ✅ verified (evidence: The claim (L74) is corroborated verbatim by the same document's other sections: "Pulumi Cloud's publish API is wire-compatible with HCP Terraform's private registry. Existing HCP migration tooling works unmodified, pointed at the new host"…; source: repo:content/docs/idp/concepts/terraform-modules.md (lines 13, 25-27, 37, 74))
  • L74 in content/docs/idp/concepts/terraform-modules.md "Reading and listing Terraform modules in Pulumi Cloud work on any plan, so users keep access to modules they have already published regardless of their plan." → 🤝 matches (evidence: The same doc states earlier (L17): "Publishing is gated to those tiers; reading and listing modules is available on any plan, so you always keep access to modules you have already published." Line 74's claim restates this identically ("Rea…; source: repo:content/docs/idp/concepts/terraform-modules.md (L17, L74))
  • L78 in content/docs/idp/concepts/terraform-modules.md "In Pulumi Cloud, a Terraform module name must consist of lowercase letters, digits, and hyphens ([a-z0-9-]), and underscores are rejected at publish." → 🤷 unverifiable (evidence: (escalated from pass1) The claim describes a specific validation regex ([a-z0-9-]) for Pulumi Cloud IDP's private Terraform module registry, implemented in pulumi/pulumi-service#44889 — a private repository not accessible via web search. P…; source: WebSearch ran query "Pulumi IDP Terraform module registry name validation underscore hyphen"; top results didn't address the claim; pulumi-service is a private repo not accessible via web search)
  • L78 in content/docs/idp/concepts/terraform-modules.md "A module hosted by HCP Terraform under a name containing underscores, such as control_tower_account_factory, must be renamed to use hyphens instead, such as co…" → 🤷 unverifiable (evidence: The doc text itself (line 78) states: "the module name must be lowercase letters, digits, and hyphens ([a-z0-9-]), so underscores are rejected at publish. A module that HCP hosts under a name like control_tower_account_factory has to be re…; source: gh api repos/pulumi/pulumi-service (404 Not Found — private repo))
  • L78 in content/docs/idp/concepts/terraform-modules.md "A module that HCP Terraform hosts under a name like control_tower_account_factory has to be renamed to control-tower-account-factory before publishing it to Pu…" → 🤷 unverifiable (evidence: (escalated from pass1) This claim describes a specific Pulumi Cloud publishing-validation behavior (renaming underscore module names to hyphenated names before publishing) tied to the private pulumi-service repo (PR #44889), which is not a…; source: WebSearch ran query "Pulumi IDP Terraform modules publish naming requirements hyphen underscore Pulumi Cloud"; top results didn't address the claim)
  • L82 in content/docs/idp/concepts/terraform-modules.md "A Pulumi package converted from a Terraform module is named using the pattern -, published under the same namespace and registry source as the mo…" → ✅ verified (evidence: The claim is a verbatim restatement of the doc's own line 82: "The package is named after the module: <name>-<system>, published under the same namespace and registry source, so a module published as acme-corp/vpc/aws produces a packag…; source: repo:content/docs/idp/concepts/terraform-modules.md (line 82); pulumi-service repo is private (404 via gh api))
  • L82 in content/docs/idp/concepts/terraform-modules.md "Publishing a Terraform module version to Pulumi Cloud automatically converts it into a Pulumi package with no extra step required from the user." → 🤷 unverifiable (evidence: The doc states "Publishing a module version also converts it into a Pulumi package, with no extra step on your part," consistent with the intro paragraph's "Every module version you publish is also converted into a Pulumi package." The imp…; source: the implementing service change is not in a public repository and could not be read)
  • L82 in content/docs/idp/concepts/terraform-modules.md "A converted package is published under the same namespace and registry source as the original module." → ✅ verified (evidence: Line 82 of the same doc states: "The package is named after the module: <name>-<system>, published under the same namespace and registry source, so a module published as acme-corp/vpc/aws produces a package called vpc-aws." This is a…; source: repo:content/docs/idp/concepts/terraform-modules.md#L82)
  • L82 in content/docs/idp/concepts/terraform-modules.md "A module published as acme-corp/vpc/aws produces a package called vpc-aws." → 🤷 unverifiable (evidence: Public docs and blog posts describe Terraform module registry naming as // (e.g., terraform-aws-modules/vpc/aws) and show that Pulumi's pulumi package add terraform-module requires an explicit user-chosen packa…; source: WebSearch ran query "Pulumi IDP "terraform-modules" concepts package name convention namespace"; top results didn't address the specific IDP auto-naming transformation)
  • L84 in content/docs/idp/concepts/terraform-modules.md "Conversion of Terraform module versions into Pulumi packages runs per version, so a single module can have some versions with corresponding packages and some w…" → 🤷 unverifiable (evidence: (escalated from pass1) The claim describes internal IDP behavior (per-version conversion of Terraform modules into Pulumi packages) implemented in pulumi/pulumi-service#44889, a private repository not accessible via web search or gh toolin…; source: WebSearch ran query "Pulumi Terraform module conversion per version packages IDP"; top results didn't address the claim; pulumi-service is a private repo)
  • L84 in content/docs/idp/concepts/terraform-modules.md "A converted Terraform module package's page in Pulumi Cloud shows which versions have converted and gives the install command for a given version." → ✅ verified (evidence: Doc text at line 84 states: "Conversion runs per version, so a module can have some versions with packages and some without. The package's page in Pulumi Cloud shows which versions have converted and gives you the command to install one."…; source: repo:content/docs/idp/concepts/terraform-modules.md#L84)
  • L84 in content/docs/idp/concepts/terraform-modules.md "While a module version is still converting, a user can run the same conversion locally instead of waiting." → ✅ verified (evidence: The same doc states: "While a version is still converting, you can run the same conversion locally instead of waiting," and later elaborates "This runs the conversion at the moment you run it... It is useful…; source: repo:content/docs/idp/concepts/terraform-modules.md (lines 84, 104-114))
  • L86 in content/docs/idp/concepts/terraform-modules.md "Pulumi Cloud uses the same <namespace>/<name>/<system> address form as HCP Terraform, where the namespace is your Pulumi organization. One rule is stricter:…" → 🤷 unverifiable (evidence: (escalated from pass1) Web search confirms the general HCP Terraform module registry protocol uses the // address form, e.g. "system is the name of a remote system that the module is primarily written to target," b…; source: WebSearch ran query "Pulumi IDP Terraform modules concepts docs namespace name system HCP Terraform hosts"; top results didn't address the claim)
  • L91 in content/docs/idp/concepts/terraform-modules.md "A converted Terraform module package can be installed by running pulumi package add - []." → ✅ verified (evidence: The doc itself states: "Once a version has converted, install it by package name: pulumi package add <name>-<system> [<version>]" which matches the claim verbatim, and is consistent with the general `pulumi package add …; source: repo:content/docs/idp/concepts/terraform-modules.md (L88-92))
  • L94 in content/docs/idp/concepts/terraform-modules.md "The version of a Terraform module package installed via pulumi package add is persisted in Pulumi.yaml, so that running pulumi install regenerates the same pin…" → ✅ verified (evidence: The auto-generated pulumi package add CLI reference states: "When run inside a Pulumi project or plugin, the package is also recorded in Pulumi.yaml or PulumiPlugin.yaml." Combined with Pulumi's packages: field in Pulumi.yaml (referenc…; source: repo:content/docs/iac/cli/commands/pulumi_package_add.md; gh search code --owner pulumi "packages:" tests/integration/integration_go_test.go)
  • L94 in content/docs/idp/concepts/terraform-modules.md "A Terraform module consumed via Pulumi Cloud is treated as a multi-language component as documented in the pulumi-hcl mlc.md reference." → ❌ contradicted (evidence: The cited URL https://github.com/pulumi-labs/pulumi-hcl/blob/main/docs/mlc.md returns HTTP 404 Not Found, so the referenced "mlc.md reference" does not exist at that location.; source: https://github.com/pulumi-labs/pulumi-hcl/blob/main/docs/mlc.md)
  • L95 in content/docs/idp/concepts/terraform-modules.md "Submodules are referenced with the standard //modules/<name> source syntax:" → ✅ verified (evidence: HashiCorp's Terraform docs confirm the double-slash syntax for referencing submodules: "Add // to the source path to indicate that the rest of the path after that point is a sub-directory within the package," and the Terraform Registry sho…; source: https://developer.hashicorp.com/terraform/language/block/module)
  • L96 in content/docs/idp/concepts/terraform-modules.md "In Pulumi Cloud, a package converted from a Terraform module gets usage tracking recording which stacks depend on it and which of those are behind the latest v…" → 🤝 matches (evidence: private-registry.md's "Usage tracking" section states: "Both package list views display usage columns for each package: how many stacks are on the latest version, how many are on older versions, and the total number of stacks using the pac…; source: repo:content/docs/idp/concepts/private-registry.md#usage-tracking)
  • L98 in content/docs/idp/concepts/terraform-modules.md "A stack or workspace that consumes a module over the Terraform protocol does not report a dependency, so it does not appear in the usage columns or on the pack…" → ✅ verified (evidence: Sibling doc private-registry.md confirms usage tracking works via stack dependency records ("Each package page also includes a 'Used by' tab showing which stacks use that package") which is populated from Pulumi stack state; the terraform-…; source: repo:content/docs/idp/concepts/private-registry.md)
  • L101 in content/docs/idp/concepts/terraform-modules.md "Older versions of the Pulumi CLI (before 3.248.0) fail with a plugin handshake error when installing a converted Terraform module package." → ✅ verified (evidence: The doc states "Installing a converted package requires Pulumi CLI 3.248.0 or newer. Older versions fail with a plugin handshake error." The v3.248.0 release notes confirm engine-level handshake changes landed in that exact version: "[engi…; source: gh release view v3.248.0 -R pulumi/pulumi)
  • L104-106 in content/docs/idp/concepts/terraform-modules.md "A Terraform module's conversion to a Pulumi package can be run locally against the module address rather than the package name, using the command pulumi packag…" → ✅ verified (evidence: pulumi-hcl's parameterize.go implements exactly this CLI path: comment reads "CLI path (pulumi package add hcl module <source> [version]): it downloads the module tree and bundles it" and "parameterizeArgs handles `pulumi package add hcl…; source: gh api repos/pulumi/pulumi-hcl/contents/pkg/server/parameterize.go)
  • L108-110 in content/docs/idp/concepts/terraform-modules.md "hcl is a parameterized Pulumi provider whose module keyword selects module mode, followed by the module address and an optional version, where omitting the ver…" → ✅ verified (evidence: pulumi/pulumi-hcl's parameterize.go confirms: "parameterizeArgs handles pulumi package add hcl module <source> [version]" with the fixed "module" keyword ("The fixed "module" keyword leaves room for the provider to grow other parameter…; source: gh api repos/pulumi/pulumi-hcl/contents/pkg/server/parameterize.go; gh api repos/pulumi/registry/contents/themes/default/content/registry/packages/hcl/_index.md)
  • L110 in content/docs/idp/concepts/terraform-modules.md "The CLI injects a TF_TOKEN_app_pulumi_com (or the equivalent host-derived token name for self-hosted installations) into the pulumi-terraform-module provid…" → ✅ verified (evidence: pulumi/pulumi-terraform-module's registry_token.go implements exactly this: injectRegistryToken discovers the Pulumi Cloud module registry via tfe.v2 service discovery and sets a TF_TOKEN_<host> env var (comment: "injectRegistryToken l…; source: gh api repos/pulumi/pulumi-terraform-module/contents/pkg/modprovider/registry_token.go and registry_token_test.go)
  • L112 in content/docs/idp/concepts/terraform-modules.md "hcl is a parameterized provider. The module keyword selects module mode, followed by the module address and an optional version. Omit the version to resolv…" → ✅ verified (evidence: The pulumi/pulumi-terraform-module repo confirms the parameterized-provider pattern this claim describes: its README shows pulumi package add terraform-module <module> [<version-spec>] <pulumi-package> with the version argument optional,…; source: gh_query: repos/pulumi/pulumi-terraform-module (README.md, pkg/modprovider/params.go, schema.go))
  • L114 in content/docs/idp/concepts/terraform-modules.md "Running the local hcl module conversion executes the conversion at the moment it is run, using whatever version of the hcl provider is installed, rather than u…" → ✅ verified (evidence: The doc itself states directly adjacent to the claimed line: "This runs the conversion at the moment you run it, using whatever version of the hcl provider you have, rather than using the package the registry produced." This is a faithfu…; source: repo:content/docs/idp/concepts/terraform-modules.md (L112-114))
  • L116 in content/docs/idp/concepts/terraform-modules.md "After running pulumi login, both the pulumi package add package-name command and the pulumi package add hcl module command resolve using the user's Pulumi cred…" → ✅ verified (evidence: The same page's "Authenticate" section states: "Consuming from a Pulumi program: run pulumi login. pulumi package add terraform-module passes the token through to the provider, so there is no separate registry login," and both `pulumi…; source: repo:content/docs/idp/concepts/terraform-modules.md (lines 21-33, 86-116))
  • L116 in content/docs/idp/concepts/terraform-modules.md "The CLI injects a TF_TOKEN_tf_pulumi_com (or the equivalent host-derived token name for self-hosted installations) into the pulumi-terraform-module provide…" → ❌ contradicted (evidence: The pulumi-terraform-module source (pkg/modprovider/registry_token.go) builds the env var as TF_TOKEN_ + host-encoded name via tfTokenEnvKey, and its test asserts assert.Equal(t, "TF_TOKEN_tfe_pulumi_com", tfTokenEnvKey(svchost.Hostna…; source: gh api repos/pulumi/pulumi-terraform-module/contents/pkg/modprovider/registry_token.go and registry_token_test.go; intuition: Claim's token name tf_pulumi_comlooks like a plausible-but-wrong guess; source usestfe_pulumi_com` (TFE = Terrafor…)
  • L120-127 in content/docs/idp/concepts/terraform-modules.md "A Terraform module can be referenced in a .tf file using a module block with source set to 'tf.pulumi.com///' and a version attribute…" → ✅ verified (evidence: The same doc defines the exact syntax: 'hcl\nmodule \"vpc\" {\n source = \"tf.pulumi.com/<namespace>/<name>/<system>\"\n version = \"1.2.3\"\n}\n' (lines 122-127), consistent with the doc's earlier claim that Pulumi Cloud's regist…; source: repo:content/docs/idp/concepts/terraform-modules.md)
  • L129 in content/docs/idp/concepts/terraform-modules.md "tofu init discovers the modules.v1 endpoint on Pulumi Cloud's .well-known/terraform.json, lists available versions, and downloads the tarball using the token f…" → ✅ verified (evidence: The modules.v1 service discovery identifier and .well-known/terraform.json host discovery is the standard Terraform/OpenTofu module registry protocol (confirmed via pulumi/terraform mirror: "the module registry protocol is modules.v1…; source: gh search code --owner pulumi "modules.v1"; repo:content/docs/idp/concepts/terraform-modules.md)
  • L129 in content/docs/idp/concepts/terraform-modules.md "tofu init lists available module versions and downloads the tarball using the token from TF_TOKEN_tf_pulumi_com." → ✅ verified (evidence: The doc itself establishes TF_TOKEN_tf_pulumi_com as the host-derived token variable (line 27-31) and describes the module registry as wire-compatible with the standard Terraform Module Registry Protocol (service discovery via .well-known/…; source: repo:content/docs/idp/concepts/terraform-modules.md (lines 27-33, 121-129))

  • Refresh this review — comment @claude #update-review. Say what you fixed, or which finding you dispute and why; both work in the same mention.
  • Ask for anything else — comment @claude with no hashtag (questions, one-off fixes). Leaves this review untouched.

Important

Please don't hide, resolve, or delete this comment! It breaks things!

📖 How pre-merge review works — the full lifecycle, short-circuits, and escape hatches.

@github-actions

Copy link
Copy Markdown
Contributor
continued from previous comment
  • L131-137 in content/docs/idp/concepts/terraform-modules.md "Submodules of a Terraform module hosted on Pulumi Cloud are referenced with the standard //modules/ source syntax, e.g. source = 'tf.pulumi.com/<namespac…" → ✅ verified (evidence: The doc file itself states exactly this: "Submodules are referenced with the standard //modules/<name> source syntax" followed by the example source = "tf.pulumi.com/<namespace>/<name>/<system>//modules/<submodule>". This matches the s…; source: repo:content/docs/idp/concepts/terraform-modules.md L131-137)
  • L116 in content/docs/idp/concepts/terraform-modules.md "After pulumi login, both commands resolve using your Pulumi credentials, so no manual token or registry login is needed." → 🚩 flagged (readthrough: self-redundancy)
  • L3 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "The post's front matter declares a blog category." → ❌ contradicted (evidence: The front matter declares title, date, draft, meta_desc, meta_image, feature_image, authors, tags, canonical_url and social, but no category: key. checkBlogCategory in the lint script returns "Blog post is missing a required 'category' value" when the key is absent, and the category set is a closed list; source: repo:scripts/lint/lint-markdown.js (checkBlogCategory, L521-539); repo:data/blog_categories.yaml)
  • L5 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "The post's meta_desc is within the length the site's linter accepts." → ❌ contradicted (evidence: The meta_desc string measures 182 characters. checkPageMetaDescription returns "Meta description is too long. Must be shorter than 160 characters" above 160; source: repo:scripts/lint/lint-markdown.js (checkPageMetaDescription, L100-116))
  • L15 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "Pointing the post's canonical_url at the docs concept page is the intended indexing outcome for a launch post." → 🤷 unverifiable (evidence: canonical_url names a different page whose content differs from the post, which tells search engines to attribute this post to the docs page rather than index it. Whether that trade is intended is an author decision, not something a source can settle; source: repo:content/blog/terraform-modules-pulumi-cloud-registry/index.md L15)
  • L6 in content/blog/terraform-modules-pulumi-cloud-registry/index.md "Setting meta_image on a blog post matches current repo guidance." → 🤷 unverifiable (evidence: The blog archetype says the social card "is generated automatically at build time — don't add a meta_image (custom overrides drift off-brand and are almost never needed)", and AGENTS.md calls custom overrides discouraged. Whether this post warrants an exception is an author call; source: repo:archetypes/blog-post/index.md (feature_image comment); repo:AGENTS.md §Meta images)
  • L23 in content/docs/idp/concepts/terraform-modules.md "The access-token link target is the page's canonical path." → 🤷 unverifiable (evidence: /docs/pulumi-cloud/access-management/access-tokens/ resolves, but only as an alias; the canonical route for that page is /docs/administration/access-identity/access-tokens/; source: repo:content/docs/administration/access-identity/access-tokens.md (aliases list))
  • L87 in content/docs/iac/guides/building-extending/using-existing-tools/use-terraform-module.md "The new section heading follows the repo's sentence-case rule for H2+." → 🤷 unverifiable (evidence: "Using a Module from Pulumi Cloud" is Title Case, which AGENTS.md's "H2+ = Sentence case" rule disallows; the surrounding headings in this same file are also Title Case, so local consistency pulls the other way; source: repo:AGENTS.md §Code & Content Rules; repo:content/docs/iac/guides/building-extending/using-existing-tools/use-terraform-module.md)

📊 Editorial balance

Single-subject post; balance check N/A.

🚨 Outstanding in this PR

These must be resolved or refuted before merging.

  • [L3] content/blog/terraform-modules-pulumi-cloud-registry/index.md — the post has no category: front matter, which fails make lint. Category is required, singular, and drawn from the closed set in data/blog_categories.yaml; the linter's message for an absent key is "Blog post is missing a required 'category' value." This is a launch announcement for something Pulumi shipped, so product is the fitting id. Add it alongside the existing taxonomy keys:

    category: product
    tags:
      - terraform
      - features
      - migration
      - infrastructure-as-code

    (The four tags are all already in data/blog_tags.yaml — no change needed there.)

  • [L5] content/blog/terraform-modules-pulumi-cloud-registry/index.mdmeta_desc is 182 characters and the linter rejects anything over 160 ("Meta description is too long. Must be shorter than 160 characters"), so this also fails make lint. The clause that can go is the SDK/API/tracking enumeration, which the post's opening paragraph already covers:

    Current (182): Publish private Terraform modules to the Pulumi Cloud registry and get a typed Pulumi package for every version, with generated SDKs, API docs, and usage tracking across your stacks.

    Suggested (151): Publish private Terraform modules to the Pulumi Cloud registry and get a typed Pulumi package for every version, with a generated SDK in your language.

  • [L73] content/blog/terraform-modules-pulumi-cloud-registry/index.md — the multi-language-component link 404s because the branch in the URL doesn't exist. pulumi/pulumi-hcl's default branch is master, not main; there is no main branch on the repo. (The pulumi-labs owner segment does still redirect to pulumi, so the branch is the whole problem — but it's worth spelling the canonical owner while you're in there.)

    -The module becomes a [multi-language component](https://github.com/pulumi-labs/pulumi-hcl/blob/main/docs/mlc.md).
    +The module becomes a [multi-language component](https://github.com/pulumi/pulumi-hcl/blob/master/docs/mlc.md).
  • [L94] content/docs/idp/concepts/terraform-modules.md — same broken link as above, in the "Consume from a Pulumi program" section. Both files need the fix.

    -The module is a [multi-language component](https://github.com/pulumi-labs/pulumi-hcl/blob/main/docs/mlc.md): its `variable` blocks become typed inputs,
    +The module is a [multi-language component](https://github.com/pulumi/pulumi-hcl/blob/master/docs/mlc.md): its `variable` blocks become typed inputs,
  • [L116] content/docs/idp/concepts/terraform-modules.md — please confirm the registry host, because every token variable in this PR is derived from it and one public source disagrees. All three files use tf.pulumi.com, and the derived variable TF_TOKEN_tf_pulumi_com follows correctly from that host. But pulumi/pulumi-terraform-module's own test for the variable-name encoding asserts TF_TOKEN_tfe_pulumi_com — i.e. it exercises the host tfe.pulumi.com. One of the two is stale, and the consequence for a reader is total: a wrong variable name means tofu init cannot authenticate, and the wrong host means neither the go-tfe snippet nor the .tf source line resolves.

    This is a single string repeated across all three files — export TF_TOKEN_tf_pulumi_com (blog L37-40, docs L27-33), Address: "https://tf.pulumi.com" (blog L57, docs L41-46), hostname = "tf.pulumi.com" (docs L52-62), and source = "tf.pulumi.com/..." (blog L83-88, docs L120-127) — so if tfe.pulumi.com is the real host, every one of those needs updating together. If tf.pulumi.com is correct and the provider test is simply using an older name, no change is needed here and this can be closed with that note.

⚠️ Low-confidence

Review each and resolve as appropriate — these don't block the PR.

Author question that covers most of this section. The feature ships from a repository that isn't public, so the checks below could not confirm the behavior claims against source. Each one is internally consistent with the rest of the PR and none looks wrong — they are listed so you can confirm them from the implementation once, rather than re-derived here. If you can say "yes, all of these match the shipped behavior," treat the whole block as closed.

  • [L5] content/blog/terraform-modules-pulumi-cloud-registry/index.md"Pulumi Cloud registry lets you publish private Terraform modules and get a typed Pulumi package for every version." — the post's headline promise. Unconfirmed against source; it does match the docs page this post links as canonical.

  • [L18] content/blog/terraform-modules-pulumi-cloud-registry/index.md"The Pulumi Cloud registry is wire-compatible with HCP Terraform's private module registry…" — this is the load-bearing claim of the whole launch, and nothing public states it yet. Worth being certain of the scope before publishing: "wire-compatible" reads as the whole publish and consume API surface, and if any endpoint a reader's existing pipeline touches isn't covered, this sentence is the one that will have misled them. If coverage is partial, naming the covered surfaces would be safer than the blanket claim.

  • [L65] content/blog/terraform-modules-pulumi-cloud-registry/index.md"A converted Pulumi package takes its name from the module using the pattern <name>-<system>, in the same namespace as the module." — consistent with the worked acme-corp/vpc/awsvpc-aws example in both this post and the docs page; unconfirmed against the implementation.

  • [L71] content/blog/terraform-modules-pulumi-cloud-registry/index.md"Installing a converted Terraform-module-derived package requires Pulumi CLI 3.248.0 or newer." — 3.248.0 is a real release (June 24, 2026) and its notes do include engine-level plugin-handshake changes, which fits the stated failure mode. The specific floor is unconfirmed; since a reader on an older CLI hits a confusing handshake error, it's worth being sure this is the exact minimum.

  • [L13] content/docs/idp/concepts/terraform-modules.md"Teams migrating from HCP Terraform can publish their existing modules to Pulumi Cloud using the same tooling they already use (the go-tfe library or the hashicorp/tfe provider)…" — Pulumi Cloud does expose a Terraform module registry API publicly, which supports the general shape; the drop-in-compatibility detail is unconfirmed.

  • [L23] content/docs/idp/concepts/terraform-modules.md"Every surface … authenticates with a Pulumi access token, which serves as the bearer token for the publish API, the state backend, and the module registry." — unconfirmed against source.

  • [L23] content/docs/idp/concepts/terraform-modules.md — the access-token link points at /docs/pulumi-cloud/access-management/access-tokens/, which resolves only through an alias. The canonical route is /docs/administration/access-identity/access-tokens/, and AGENTS.md asks for full canonical paths in /docs/ links so they survive future moves:

    -Every surface authenticates with a [Pulumi access token](/docs/pulumi-cloud/access-management/access-tokens/).
    +Every surface authenticates with a [Pulumi access token](/docs/administration/access-identity/access-tokens/).
  • [L25] content/docs/idp/concepts/terraform-modules.md"The go-tfe client and the tfe provider accept a Pulumi access token wherever they expect a TFE token today." — unconfirmed against source.

  • [L48] content/docs/idp/concepts/terraform-modules.md"The go-tfe RegistryModules surface, including Create, CreateVersion, and UploadTarGzip, accepts the same payloads against tf.pulumi.com…" — this names specific methods, so it's the most falsifiable form of the wire-compatibility claim; worth confirming the list is exactly right, including the "etc." the sentence implies.

  • [L69-70] content/docs/idp/concepts/terraform-modules.md"modules/<name>/ subdirectories are parsed the same way as the root and can be consumed as submodules." — consistent with standard Terraform module conventions; the parsing behavior is unconfirmed.

  • [L70] content/docs/idp/concepts/terraform-modules.md"examples/<name>/ subdirectories and the README.md are captured at publish." — the sibling private-registry page confirms README capture for components generally; Terraform-module examples/ capture is unconfirmed.

  • [L78] content/docs/idp/concepts/terraform-modules.md"the module name must be lowercase letters, digits, and hyphens ([a-z0-9-]), so underscores are rejected at publish." — this is the "naming rules to check first" the blog post sends migrating readers here for, and it's the one rule that can hard-fail their publish, so it's the highest-value item in this block to confirm exactly (including whether uppercase really is lowercased automatically rather than rejected).

  • [L78] content/docs/idp/concepts/terraform-modules.md"A module that HCP hosts under a name like control_tower_account_factory has to be renamed to control-tower-account-factory." — follows from the rule above; same confirmation covers both.

  • [L82] content/docs/idp/concepts/terraform-modules.md"Publishing a module version also converts it into a Pulumi package, with no extra step on your part." — unconfirmed against source.

  • [L82] content/docs/idp/concepts/terraform-modules.md"A module published as acme-corp/vpc/aws produces a package called vpc-aws." — note that the older pulumi package add terraform-module flow takes an explicit author-chosen package name, so automatic derivation is genuinely new behavior rather than something a reader can infer; confirming it is worthwhile.

  • [L84] content/docs/idp/concepts/terraform-modules.md"Conversion runs per version, so a module can have some versions with packages and some without." — unconfirmed against source.

  • [L86] content/docs/idp/concepts/terraform-modules.md"Pulumi Cloud uses the same <namespace>/<name>/<system> address form as HCP Terraform, where the namespace is your Pulumi organization." — the address form matches the public registry protocol; the namespace-equals-organization mapping is unconfirmed.

  • [L116] content/docs/idp/concepts/terraform-modules.md — the closing sentence of "Converting a module locally" repeats what the Authenticate section already established at L26 ("run pulumi login… there is no separate registry login"). It reads as padding at the end of an otherwise tight section. Trimming to the part that's actually new — that the statement covers both commands — keeps the reassurance without the restatement:

    -After `pulumi login`, both commands resolve using your Pulumi credentials, so no manual token or registry login is needed. See [Use a Terraform Module in Pulumi](/docs/iac/guides/building-extending/using-existing-tools/use-terraform-module/) for examples.
    +Both commands resolve using your Pulumi credentials. See [Use a Terraform Module in Pulumi](/docs/iac/guides/building-extending/using-existing-tools/use-terraform-module/) for examples.
  • [L15] content/blog/terraform-modules-pulumi-cloud-registry/index.mdcanonical_url points at the docs concept page, which tells search engines to attribute this post to that page instead of indexing the post itself. That's the normal use of the field when the same content was published elsewhere first, but here the two pages are different content, and the effect is that a launch announcement written to be found won't rank. If the intent was just to send readers to the reference page, the in-body links already do that. Worth confirming this is deliberate — and if it isn't, dropping the field is the fix.

  • [L6] content/blog/terraform-modules-pulumi-cloud-registry/index.mdmeta_image: meta.png (and the committed meta.png) opts out of the generated social card. The blog archetype is explicit that the card "is generated automatically at build time — don't add a meta_image", and AGENTS.md calls custom overrides discouraged because they drift off-brand. Unless this image was designed for this launch, dropping the meta_image line and the file gets you the on-brand card built from the title plus feature.png.

  • [L87] content/docs/iac/guides/building-extending/using-existing-tools/use-terraform-module.md — the new heading ### Using a Module from Pulumi Cloud is Title Case, and AGENTS.md specifies sentence case for H2 and below (### Using a module from Pulumi Cloud). Flagging it as low-confidence because the headings already in this file are Title Case too, so matching the style guide here makes the new section inconsistent with its neighbors. Either is defensible; the file-wide fix is out of scope for this PR.

Style findings

Found by pattern-based linting; Findings may be false positives.

Click each filename to expand.

content/blog/terraform-modules-pulumi-cloud-registry/index.md (1 issues: 1 wordiness)
  • line 37: [style] wordiness — 'It is' is too wordy.
content/docs/iac/guides/building-extending/using-existing-tools/use-terraform-module.md (1 issues: 1 wordiness)
  • line 103: [style] wordiness — 'It is' is too wordy.
content/docs/idp/concepts/terraform-modules.md (6 issues: 3 wordiness, 2 heading capitalization, 1 weasel word)
  • line 23: [style] wordiness — 'It is' is too wordy.
  • line 39: [style] heading capitalization — Heading 'go-tfe' should use sentence case (capitalize only the first word and proper nouns).
  • line 72: [style] heading capitalization — Heading 'Migrating from HCP Terraform' should use sentence case (capitalize only the first word and proper nouns).
  • line 74: [style] weasel word — 'mostly' is a weasel word!
  • line 114: [style] wordiness — 'It is' is too wordy.
  • line 114: [style] wordiness — 'It is' is too wordy.

📋 Triaged verifier findings

I double-checked these and realized they weren't real findings — click to expand
  • [L15] content/blog/terraform-modules-pulumi-cloud-registry/index.md"This blog post's canonical URL is https://www.pulumi.com/docs/idp/concepts/terraform-modules/."Spurious: the 404 is expected, because the page at that URL is created by this very PR and doesn't exist on the live site yet. (There is a separate, unrelated question about this field under ⚠️.)

  • [L47] content/blog/terraform-modules-pulumi-cloud-registry/index.md"The go-tfe library can be retargeted to the Pulumi Cloud registry by setting Address to https://tf.pulumi.com."Spurious: the check treated a 404 at the bare domain root as proof the host isn't valid. An API host that serves no page at / still answers on its API paths, and a 404 means the domain resolves and something is serving it.

  • [L57] content/blog/terraform-modules-pulumi-cloud-registry/index.md"Address: 'https://tf.pulumi.com',"Spurious: same root-path 404 reasoning as above.

  • [L41-46] content/docs/idp/concepts/terraform-modules.md"The go-tfe client can be configured with tfe.NewClient using Address 'https://tf.pulumi.com'…"Spurious: same root-path 404 reasoning as above.

  • [L37] content/docs/idp/concepts/terraform-modules.md"Existing HCP Terraform migration tooling works unmodified against Pulumi Cloud when pointed at the new host."Spurious: the source cited against this is the Terraform state backend page, which says HCP state migration needs a manual export and push. That's a different subject — this sentence is about module publishing tooling, and the state-migration caveat doesn't bear on it.

  • [L47-56] content/blog/terraform-modules-pulumi-cloud-registry/index.md"A go-tfe client can be configured with tfe.NewClient(&tfe.Config{Address: 'https://tf.pulumi.com', …})…"Mis-sourced: the check followed https://tf.pulumi.com rather than the go-tfe library it was pointed at, and then noted itself that a 404 on the bare domain can neither confirm nor deny the claim.

  • [L17-19] content/docs/idp/concepts/terraform-modules.md"Reading and listing Terraform modules … is available on any plan, so users always keep access to modules they have already published."Mis-sourced: the URL followed was the app.pulumi.com sign-in page, which carries no plan-tier information. The plan gating in this passage is worded identically to the sibling private-registry.md page, which is the better comparison.

💡 Pre-existing issues in touched files (optional)

No pre-existing issues in touched files.

✅ Resolved since last review

No items resolved since the last review.

📜 Review history

  • 2026-07-31T14:13:01Z — Two make lint blockers in the blog front matter (missing category, 182-char meta_desc), a broken pulumi-hcl link in two files (branch is master, not main), and a host-name discrepancy (tf.pulumi.com vs the provider test's tfe.pulumi.com) that every token variable in the PR depends on. (522005e)

  • Refresh this review — comment @claude #update-review. Say what you fixed, or which finding you dispute and why; both work in the same mention.
  • Ask for anything else — comment @claude with no hashtag (questions, one-off fixes). Leaves this review untouched.

Important

Please don't hide, resolve, or delete this comment! It breaks things!

📖 How pre-merge review works — the full lifecycle, short-circuits, and escape hatches.

@github-actions github-actions Bot added review:outstanding-issues Claude review completed; outstanding has author-actionable findings and removed review:in-progress Claude review is currently running labels Jul 31, 2026
`make lint` gates the build and was failing:

- The post was missing the required `category` front matter. It announces a
  shipped feature, so `product`
- Its `meta_desc` was 182 characters against a 160 limit. Dropped the usage
  tracking clause, which the post covers in the body
- Removing the concept page's deletion section left a trailing blank line

From the pre-merge review:

- The multi-language component link 404s. `pulumi-hcl` has no `main` branch;
  `https://github.com/pulumi/pulumi-hcl/blob/master/docs/mlc.md` resolves.
  Fixed in the post and the concept page
- The access token link went through an alias. The page's canonical route is
  `/docs/administration/access-identity/access-tokens/`; the old path is line
  19 of that page's own aliases list
- The closing line of "Converting a module locally" restated what the
  Authenticate section already establishes
- Style: two `It is` openings and one `mostly`
- The guide's new heading was Title Case, against AGENTS.md's sentence case
  rule for H2 and below

The module name rule is now stated as the regex the service enforces,
`[a-z0-9][a-z0-9-]*` (`pkg/apitype/registry_artifact.go`), rather than a
character list that implied a leading hyphen was valid.

Two review findings are not acted on. The registry host is `tf.pulumi.com`:
`tfe.pulumi.com` is a dead record that CloudFront-403s since the distribution
alias, ALB host rule, and service dispatch moved, so the provider test the
review cites is stale. And `canonical_url` pointing at the concept page is what
BLOGGING.md prescribes for a feature announcement documented in the docs.
@fnune
fnune force-pushed the fnune/registry/tf-modules-docs branch from 7f424a2 to d594684 Compare July 31, 2026 14:41
@github-actions github-actions Bot added review:stale New commits since last Claude review; refresh on next ready-transition or @claude mention and removed review:outstanding-issues Claude review completed; outstanding has author-actionable findings labels Jul 31, 2026
@cnunciato cnunciato changed the title [registry] Docs and launch blog for Terraform modules in the Pulumi Cloud registry [docs] Docs and launch blog for Terraform modules in the Pulumi Cloud registry Jul 31, 2026
@cnunciato

cnunciato commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

@fnune Can you break out the blog into a separate PR so we can merge the docs independently? Then we can publish the post on or about launch day.

@cnunciato cnunciato 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.

Nice! Bunch of things for you to consider. Main thing is that I'd like to have us publish the blog separately, but I also a bunch of questions and suggestions for you to consider. Ping me when you're ready for a second look!

Comment thread content/docs/idp/concepts/terraform-modules.md
Comment thread content/blog/terraform-modules-pulumi-cloud-registry/index.md Outdated
Comment thread content/blog/terraform-modules-pulumi-cloud-registry/index.md Outdated

## What happens when you publish

Publishing a module version also converts it into a Pulumi package, with no extra step on your part. The package is named after the module: `<name>-<system>`, published under the same namespace and registry source, so a module published as `acme-corp/vpc/aws` produces a package called `vpc-aws`.

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.

Just calling out that we may need to revise this depending on actual behavior.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Comment thread content/docs/idp/concepts/terraform-modules.md
Comment thread content/docs/idp/concepts/terraform-modules.md Outdated
pulumi package add hcl module tf.pulumi.com/<namespace>/<name>/<system> [<version>]
```

`hcl` is a parameterized provider. The `module` keyword selects module mode, followed by the module address and an optional version. Omit the version to resolve the latest published version; pass one to pin it.

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.

hcl is a parameterized provider. The module keyword selects module mode,

This seems like an implementation detail, so irrelvant to the user. Suggest deleting unless they need to know this for some reason.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

pulumi package add <...args> is public Pulumi CLI syntax, and it may be new to the user, because it's a parameterized package. It's public API, not an implementation detail IMO.

Thoughts @iwahbe?

Comment thread content/docs/idp/concepts/terraform-modules.md Outdated
djperlovsky added a commit that referenced this pull request Aug 3, 2026
The registry-hosting half of the Terraform Modules feature is documented in
#19453, which adds content/docs/idp/concepts/terraform-modules.md.
Link the four places this page mentions hosting modules in Pulumi Cloud at
that page, and keep the module-consumption links pointed at the get-started
guide, which covers a different capability.

Also picks up the HCP-compatibility detail from that page, which lands well on
a Terraform Cloud comparison specifically: the publish API is wire-compatible
with HCP Terraform's private registry, so existing go-tfe and hashicorp/tfe
pipelines migrate by repointing the host at tf.pulumi.com.

Depends on #19453 — /docs/idp/concepts/terraform-modules/ 404s until it merges.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
djperlovsky added a commit that referenced this pull request Aug 3, 2026
This page already covered Pulumi Cloud as a Terraform state backend, but it
never mentioned Pulumi HCL, and one "Choose Terraform when" bullet was
directly counter to the new positioning: "Have a large existing investment in
HCL, modules, and team expertise that you don't want to migrate." That is
precisely the case the launch addresses.

- Intro: Pulumi "lets you pick the language," HCL included, plus a paragraph
  establishing interoperability rather than strict alternative.
- Language support and Modularity table rows now cover Pulumi HCL, direct
  Terraform module consumption, and registry hosting.
- "Language support and the authoring experience": add a paragraph making the
  point that the language is a per-project decision on Pulumi, not a wall.
- "Choose Terraform when": drop the stale HCL-investment bullet, replace with
  end-to-end HashiCorp standardization, and state plainly that an existing HCL
  and module investment is no longer a reason on its own.
- Adoption list: expand the state backend entry with remote runs and approval
  gates, and add HCL projects and registry hosting as paths.
- FAQ: add "Can I write Pulumi programs in HCL?" and expand the existing state
  backend answer. Verified all ten questions still emit in the FAQPage schema.
- Next steps: add the state backend, HCL, and HCP Terraform comparison.

Terraform module consumption links are left pointing at the guide, which is
where this page already pointed and which #19453 maintains.

Depends on #19453 for /docs/idp/concepts/terraform-modules/.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
djperlovsky added a commit that referenced this pull request Aug 3, 2026
The new interop capabilities land harder on this page than any other, and none
of them were here. Pulumi HCL is fully OpenTofu compatible and resolves
unqualified provider sources against the OpenTofu registry, exactly as tofu
does. Pulumi Cloud is a managed state backend and remote runner for the tofu
CLI. Both were absent.

Two claims were also stale in a way that sent readers elsewhere:

- "Local CLI; remote execution requires a third-party runner" and the
  Commercial option row, which listed only Spacelift, env0, and Scalr as
  sources of managed state and collaboration for OpenTofu users.
- "Choose OpenTofu when you have a large existing investment in HCL
  configurations, modules, and team expertise that you don't want to migrate."

Changes:

- Intro and "What is Pulumi?": establish interoperability, with the OpenTofu
  registry provider-resolution detail.
- Table: Language support, State management, Execution model, Modularity, and
  Commercial option rows now account for Pulumi HCL, the state backend, and
  module hosting.
- "Language support" and "Execution and rollbacks": add the HCL-is-a-choice
  paragraph and correct the remote-execution claim.
- "Choose OpenTofu when": replace the stale HCL-investment bullet with MPL 2.0
  engine licensing and community governance, which is the real reason a team
  picks OpenTofu, and state plainly that an HCL investment no longer decides it.
- Adoption: add the state backend (with tofu init -migrate-state) and HCL.
- FAQ: add HCL and OpenTofu-state-in-Pulumi-Cloud questions. All ten still
  emit in the FAQPage schema.

Depends on #19453 for /docs/idp/concepts/terraform-modules/.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

domain:blog PR touches blog posts or customer stories domain:docs PR touches technical docs domain:mixed PR touches more than one domain review:stale New commits since last Claude review; refresh on next ready-transition or @claude mention

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Document TF module to Pulumi package conversion in the registry docs and launch post

3 participants