Skip to content

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

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

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
jsteinich force-pushed the feat/opentofu-registry-resolution branch from 5912d8f to a3c68a6 Compare September 19, 2026 14:49
@jsteinich
jsteinich marked this pull request as ready for review September 23, 2026 01:45
@jsteinich
jsteinich requested a review from a team as a code owner September 23, 2026 01:45
@sakul-learning

Copy link
Copy Markdown
Contributor

providerAdd() selects the provider registry from the process working directory rather than from the project identified by its projectDirectory argument.

In packages/@cdktn/cli-core/src/lib/provider-add.ts, the registry comes from readConfigSync().targetVersions; without a path, readConfigSync() reads process.cwd()/cdktf.json. The same function then constructs DependencyManager with projectDirectory. These can differ: packages/@cdktn/cli-core/src/lib/init.ts reads the newly scaffolded config from destination and calls providerAdd({ projectDirectory: destination }) without changing the working directory.

Consequently, adding a bare provider to an OpenTofu-only destination can normalize and resolve it against registry.terraform.io based on the caller's CWD instead of against registry.opentofu.org based on the destination project.

Could the registry selection read targetVersions from projectDirectory (or receive the already-read target versions from the caller)? A focused regression test with different CWD and project-directory configs would cover this boundary.

jsteinich and others added 3 commits September 23, 2026 08:07
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>
Review finding on open-constructs#445. `providerAdd` picked the registry from
`readConfigSync()`, which with no argument reads `process.cwd()/cdktf.json`,
while passing `projectDirectory` to DependencyManager. Those are not the same
directory: init() scaffolds into `destination` and calls providerAdd with
`projectDirectory: destination` without changing cwd
(cli-core/src/lib/init.ts:93).

So adding a bare provider to a freshly scaffolded OpenTofu-only project would
normalize and resolve it against registry.terraform.io, chosen from whatever
project the CLI happened to be run from.

Regression test covers the boundary directly: cwd is a Terraform-targeting
project, the destination targets OpenTofu only, and the selection must follow
the destination.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jsteinich
jsteinich force-pushed the feat/opentofu-registry-resolution branch from 7304164 to b17afc6 Compare September 23, 2026 13:15
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.

2 participants