From 33263aa7b799129c4c40e85ecd5978d85f4a32ec Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 1 Jul 2026 08:09:30 +0000 Subject: [PATCH] fix(provider-generator): match shorthand provider sources against OpenTofu schema keys Shorthand provider sources like "hashicorp/awscc" omit a registry hostname. isMatching assumed the missing hostname was always registry.terraform.io, but `tofu providers schema -json` returns schema keys under registry.opentofu.org. As a result `cdktn get` failed with "Could not find provider with constraint ..." under OpenTofu even though the provider had been installed correctly (#216). Only compare the hostname when the source explicitly pins one (a 3-part source). Shorthand 1- and 2-part sources now match on namespace/name regardless of the schema key's registry hostname, so the same cdktf.json works under both Terraform and OpenTofu. Explicit 3-part hostnames stay strict and do not cross registries. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01BG9ZUpdDa3GDNRkRMC2b5U --- .../provider-source-matching.test.ts | 84 +++++++++++++++++++ .../src/get/generator/provider-generator.ts | 9 +- 2 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 packages/@cdktn/provider-generator/src/get/__tests__/generator/provider-source-matching.test.ts diff --git a/packages/@cdktn/provider-generator/src/get/__tests__/generator/provider-source-matching.test.ts b/packages/@cdktn/provider-generator/src/get/__tests__/generator/provider-source-matching.test.ts new file mode 100644 index 000000000..a8c2efbcf --- /dev/null +++ b/packages/@cdktn/provider-generator/src/get/__tests__/generator/provider-source-matching.test.ts @@ -0,0 +1,84 @@ +// Copyright (c) HashiCorp, Inc +// SPDX-License-Identifier: MPL-2.0 +import { + ConstructsMakerProviderTarget, + Language, + ProviderSchema, + TerraformProviderConstraint, +} from "@cdktn/commons"; +import { CodeMaker } from "codemaker"; +import { TerraformProviderGenerator } from "../../generator/provider-generator"; + +// Builds a minimal provider schema keyed by a single FQPN. The provider body is +// intentionally empty (like the empty-provider-resources fixture) since these +// tests only exercise the schema-key <-> target-source matching logic. +const schemaWithKey = (fqpn: string): ProviderSchema => ({ + provider_schemas: { + [fqpn]: { resource_schemas: null } as any, + }, + provider_versions: { + [fqpn]: "1.84.0", + }, +}); + +const targetForSource = (source: string) => + new ConstructsMakerProviderTarget( + new TerraformProviderConstraint(`${source}@1.84.0`), + Language.TYPESCRIPT, + ); + +const generateFor = (schemaKey: string, source: string) => { + const generator = new TerraformProviderGenerator( + new CodeMaker(), + schemaWithKey(schemaKey), + ); + generator.generate(targetForSource(source)); + return generator; +}; + +describe("provider source matching against schema keys", () => { + it("matches a shorthand source against an OpenTofu registry schema key", () => { + // Regression test for #216: `tofu providers schema` returns keys under + // registry.opentofu.org, so a shorthand source must not assume + // registry.terraform.io. + const generator = generateFor( + "registry.opentofu.org/hashicorp/awscc", + "hashicorp/awscc", + ); + + expect(Object.keys(generator.versions)).toContain( + "registry.opentofu.org/hashicorp/awscc", + ); + }); + + it("matches a shorthand source against a Terraform registry schema key", () => { + const generator = generateFor( + "registry.terraform.io/hashicorp/awscc", + "hashicorp/awscc", + ); + + expect(Object.keys(generator.versions)).toContain( + "registry.terraform.io/hashicorp/awscc", + ); + }); + + it("keeps an explicit hostname strict and does not cross registries", () => { + expect(() => + generateFor( + "registry.opentofu.org/hashicorp/awscc", + "registry.terraform.io/hashicorp/awscc", + ), + ).toThrow(/Could not find provider with constraint/); + }); + + it("matches an explicit hostname when it agrees with the schema key", () => { + const generator = generateFor( + "registry.opentofu.org/hashicorp/awscc", + "registry.opentofu.org/hashicorp/awscc", + ); + + expect(Object.keys(generator.versions)).toContain( + "registry.opentofu.org/hashicorp/awscc", + ); + }); +}); diff --git a/packages/@cdktn/provider-generator/src/get/generator/provider-generator.ts b/packages/@cdktn/provider-generator/src/get/generator/provider-generator.ts index 48bf860b4..92446bb89 100644 --- a/packages/@cdktn/provider-generator/src/get/generator/provider-generator.ts +++ b/packages/@cdktn/provider-generator/src/get/generator/provider-generator.ts @@ -54,6 +54,13 @@ const isMatching = ( name: "", }; const targetElements = target.source.split("/"); + // Only a 3-part source explicitly pins a registry hostname. Shorthand + // sources (e.g. "hashicorp/awscc") omit it, so we must not assume a + // default: Terraform resolves them under registry.terraform.io while + // OpenTofu resolves them under registry.opentofu.org. Comparing against a + // hard-coded default would make schema lookup fail under OpenTofu even + // though the provider was installed correctly. + const explicitHostname = targetElements.length === 3; switch (targetElements.length) { case 1: // only name set targetSource.name = targetElements[0].toLowerCase(); @@ -74,7 +81,7 @@ const isMatching = ( } return ( - targetSource.hostname === hostname && + (!explicitHostname || targetSource.hostname === hostname) && targetSource.namespace === namespace && targetSource.name === provider );