Skip to content

feat(cli): resolve providers from the registry the project targets - #445

Draft
jsteinich wants to merge 3 commits into
open-constructs:mainfrom
jsteinich:feat/opentofu-registry-resolution
Draft

jsteinich wants to merge 3 commits into
open-constructs:mainfrom
jsteinich:feat/opentofu-registry-resolution

Conversation

@jsteinich

@jsteinich jsteinich commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Related issue

Addresses the code-level readings of #208. Stacked on #443 — merge that first; this branch contains its commit and reuses the registry mechanism it adds.

Description

#208's title reads like a dependency-manager change while its body is about sample code on registries, so it admits three readings. This takes the two that are code-level, using the same targetVersions-derived registry as #443 — OpenTofu only when it is the sole declared product, Terraform otherwise.

The through-line: OpenTofu users have been told to fully qualify their providers, because everything here hardcoded registry.terraform.io. These changes make that advice optional rather than required, without breaking anyone already following it.

1. Bare sources expand against the project's registry

normalizeProviderSource hardcoded the Terraform host, so cdktn provider add aws always produced registry.terraform.io/hashicorp/aws. It now expands against the registry the project targets, so an OpenTofu project gets registry.opentofu.org/hashicorp/aws.

A source that already names a host is still left untouched, so an explicit registry.opentofu.org/... always wins — over the project's target, and over the default for a project that declares no targetVersions.

2. Explicitly qualified OpenTofu providers silently skipped version resolution

This is a live bug on main, and it hits exactly the users who followed the fully-qualify advice. Version resolution was gated on:

if (!constraint.version && constraint.isFromTerraformRegistry()) {

where isFromTerraformRegistry() is hostname === "registry.terraform.io". A provider written as registry.opentofu.org/hashicorp/aws with no version failed that check, so no version was resolved and none was written.

It becomes isFromPublicRegistry() — whether the host is a registry cdktn can query at all. Private and self-hosted registries are still correctly excluded, since they expose no versions API.

3. Resolution and the browse URL follow the constraint

Both now derive from the constraint's own hostname rather than a registry threaded down the call chain. That is what makes explicit qualification win, and it let the DependencyManager parameter from the first commit go away again.

Both registries expose the same /v1/providers/{ns}/{name}/versions shape, and their version lists differ — 42 versus 41 for hashicorp/random at time of writing — so an OpenTofu project could previously be told to pin a version its own registry does not carry.

The error text's browse URL follows too: search.opentofu.org/providers for OpenTofu, since registry.opentofu.org/browse/providers is a 404.

simplifiedName drops whichever public host it carries, not just Terraform's.

What is still out of scope

The third reading of #208 — publishing CDK Terrain samples to the OpenTofu registry — is untouched, so the issue stays open for it.

cdktf-config-manager still strips only the Terraform prefix when writing cdktf.json, which means an OpenTofu project's providers are written fully qualified. That round-trips correctly and matches what those users already write by hand, so it is left alone deliberately rather than risking a strip that normalization would not re-add.

Testing

registry-api.test.ts covers all four paths with the MockAgent pattern the neighbouring tests use: default resolves against Terraform; a bare source under an OpenTofu target expands and resolves against OpenTofu; an explicitly qualified OpenTofu source resolves even with no declared target; a private registry is not queryable.

nx test @cdktn/cli-core (so the dependency chain is built): all suites pass.

Running jest directly in the package without building siblings reports failures on Cannot find module '@cdktn/hcl2cdk' / '@cdktn/hcl-tools' — pre-existing and unrelated.

🤖 Generated with Claude Code

jsteinich and others added 3 commits September 19, 2026 09:48
Generated bindings built their documentation links from the FQPN hostname,
which comes from whichever CLI fetched the schema. Under OpenTofu that
produced registry.opentofu.org/providers/... links, which 404 - that host
does not serve the Terraform registry's path shape.

Deriving the host from the *fetching CLI* was the real defect: the same
provider at the same version generated different output depending on whether
`cdktn get` ran under terraform or tofu. The project's declared targets are
the stable signal, so linkToDocs now keys off those.

OpenTofu is used only when it is the sole declared product. Declaring both,
or declaring nothing (DEFAULT_TARGET_VERSIONS declares both), gives the
Terraform registry - so the default case is deterministic and output no
longer depends on the CLI. That is why no snapshots move.

The two registries are not a hostname swap. Confirmed against
getDocumentationUrl.ts in opentofu/registry-ui and checked against live
renders, OpenTofu uses /provider/ singular, a v-prefixed version, and
"datasources" unhyphenated. It has no ephemeral-resource route yet
(opentofu/registry-ui#348), so those fall back to the provider page.

The registry identity lives in @cdktn/commons because @cdktn/cli-core needs
the same mapping for open-constructs#208 - the provider-browse URL in its error text and the
hostname it resolves providers from.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Addresses the two code-level readings of open-constructs#208 using the mechanism added for
open-constructs#393: the registry comes from the project's declared targetVersions, so
OpenTofu only when it is the sole declared product.

1. The "could not find a version" error pointed everyone at
   registry.terraform.io/browse/providers. An OpenTofu project now gets
   search.opentofu.org/providers - registry.opentofu.org/browse/providers is
   a 404, the listing lives on the search host.

2. `cdktn provider add` resolved available versions from
   registry.terraform.io regardless of target. Both registries expose the
   same /v1/providers/<ns>/<name>/versions shape and their version lists
   differ - 42 versions vs 41 for hashicorp/random at time of writing - so an
   OpenTofu project could be told to pin a version its own registry does not
   carry.

Deliberately out of scope: DEFAULT_HOSTNAME, which normalizes a bare
"hashicorp/aws" into a fully qualified source. That is provider *identity*,
not resolution, and it is consumed by more than this path -
cdktf-config-manager and prebuilt-providers both hardcode stripping the
"registry.terraform.io/" prefix, so making normalization registry-aware would
break prebuilt matching and cdktf.json round-tripping for OpenTofu projects.
It needs its own change with those consumers updated together.

The registry parameter defaults to Terraform, so every caller that does not
yet thread targetVersions keeps its current behaviour.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Folds DEFAULT_HOSTNAME into the registry work, per review.

OpenTofu users have been told to fully qualify their providers because
normalization hardcoded registry.terraform.io. Two changes so that advice
becomes optional rather than required, without breaking anyone following it:

- A bare or namespace-only source now expands against the registry the
  project targets, so `cdktn provider add aws` in an OpenTofu project yields
  registry.opentofu.org/hashicorp/aws.
- A source that already names a host is still left alone, so an explicit
  registry.opentofu.org/... always wins over the project's target - and over
  the default, for a project that declares no targetVersions.

`isFromTerraformRegistry` becomes `isFromPublicRegistry`. That predicate
gated automatic version resolution on the host being Terraform's, so an
explicitly qualified OpenTofu provider - exactly what tofu users were told to
write - silently skipped resolution and got no version. It now asks whether
the host is a registry we can query at all, so private and self-hosted
registries are still correctly excluded.

Resolution and the browse URL in the error text both follow the constraint's
own hostname rather than a registry passed down the call chain, which is what
makes explicit qualification win and let the DependencyManager parameter go
away again.

simplifiedName drops whichever public host it carries, not just Terraform's.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jsteinich
jsteinich force-pushed the feat/opentofu-registry-resolution branch from 5912d8f to a3c68a6 Compare September 19, 2026 14:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant