feat(lib): validate Fn usage against Terraform/OpenTofu versions#268
feat(lib): validate Fn usage against Terraform/OpenTofu versions#268so0k wants to merge 10 commits into
Conversation
| returnType: "dynamic", | ||
| returnType: "string", |
There was a problem hiding this comment.
this is a fix that comes from newer terraform binary output in the terraform metadata funcions -json call
| }, | ||
| ], | ||
| }, | ||
| issensitive: { |
| const matrix: FunctionsMatrix = JSON.parse( | ||
| (await fs.readFile(FUNCTIONS_MATRIX_FILE)).toString(), | ||
| ); |
There was a problem hiding this comment.
functions-matrix.json should be the single source of truth, this PR aims for a minimal diff but if you want I can re-write generate.ts to not depend on functions.json at all or split that off for a follow-up PR.
There was a problem hiding this comment.
Let's split that out separately
There was a problem hiding this comment.
the duplication here between abs and core::abs is due to a change in TF 1.8, generate.ts merges them and it has no impact on function binding generation aside from slight bloat in functions.json
| * used through `Fn` are supported by the version of the Terraform-compatible | ||
| * CLI (Terraform or OpenTofu) found on the system. | ||
| */ | ||
| export const VALIDATE_FUNCTION_VERSIONS = "validateFunctionVersions"; |
There was a problem hiding this comment.
There was a problem hiding this comment.
As we are only applying this to new functions, is there actually a need for a flag to disable them? Users will be able to adjust their target versions appropriately to signify which functions are valid.
d18ecd9 to
7b9a82b
Compare
This comment has been minimized.
This comment has been minimized.
|
Converting to draft: this PR depends on #269 - declared This ensures Installed-binary verification stays opt-in per #269. |
7b9a82b to
30a4cb8
Compare
03dc31f to
12f9975
Compare
30a4cb8 to
72db7b9
Compare
This comment was marked as duplicate.
This comment was marked as duplicate.
72db7b9 to
e2a5af1
Compare
This comment was marked as resolved.
This comment was marked as resolved.
Up to you Vincent. My only comment on this Pr would be why are we using Python scripts rather than Node? |
This comment was marked as resolved.
This comment was marked as resolved.
|
Personally, I would merge #262 first, and then refactor your changes on top, otherwise these PRs gain more and more bloat and are then difficult to review. |
Projects can declare the Terraform-compatible runtimes they intend to
support as npm semver ranges per product:
"targetVersions": { "terraform": ">=1.5.7", "opentofu": ">=1.6.0" }
parseConfig validates the shape (unknown products, malformed ranges, and
Terraform's "~>" constraint syntax — which npm semver would silently parse
as "~" with different semantics — are rejected with actionable messages)
and threads the declaration to synthesized apps through the existing
CDKTF_CONTEXT_JSON channel.
Also adds parseTerraformCliVersion to commons (product+version detection
from plain `version` output) and a validateInstalledBinary config flag,
both consumed by the follow-up cli-core commit.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the reusable foundation for validating features against the project's declared Terraform/OpenTofu targets instead of probing whichever binary happens to be installed during synth: - resolveTargetVersions(scope): reads the targetVersions context (injected by the CLI from cdktf.json), defaulting to the dual product baseline (terraform >=1.5.7, opentofu >=1.6.0) when undeclared - checkFeatureSupportedByTargets: semver range *subset* check — a feature passes only if it is supported across the entire declared range of every targeted product - ValidateFeatureTargetSupport: IValidation over the two; never executes a binary, so it behaves identically in CI/synth-only environments Synth-time validations no longer probe the installed binary: - ValidateTerraformFeatureVersion (merged unreleased in #237, zero call sites) is removed together with its execSync machinery; the pure parseTerraformCliVersion parser and the TerraformFeatureVersionConstraints type remain. Installed-binary verification is an explicit opt-in handled by the CLI (validateInstalledBinary). - The long-released ValidateBinaryVersion and the resource-move probe built on it stay unchanged as the legacy exception, both marked with TODO(target-versions) for a follow-up migration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Versions With "validateInstalledBinary": true in cdktf.json, commands that are about to execute the Terraform-compatible CLI (diff/deploy/destroy via initalizeTerraform) first verify that the installed binary's product and version fall inside the project's declared targetVersions, failing with a usage error otherwise. Off by default: declared targets state intent and are deliberately not treated as proof about the local binary. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New projects created with cdktn init declare the dual product baseline (terraform >=1.5.7, opentofu >=1.6.0) explicitly in cdktf.json, making the project's intended runtime range discoverable and editable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1af9ee5 to
4c23aa5
Compare
…tooling Vendor functions-matrix.json — a per-function availability map across every stable Terraform (>=1.5.7) and OpenTofu (>=1.6.0) release — and the TypeScript tooling that consumes and maintains it: - update-function-matrix: delta updater for new releases (downloads only unseen versions into a temp dir, warns loudly if a function disappears) - generate-function-availability: emits the version-constraint map consumed by the cdktn validation (only non-universal functions) - matrix.ts: shared matrix location + types The baseline data sweep that produced the matrix — the per-release `metadata functions -json` dumps plus the build-matrix.py / build-report.py / sweep.sh scripts and the interactive HTML report — lives in the open-constructs/cdktn-planning repo (RFCS/function-availability); only the resulting matrix is vendored here. A README in function-availability/ points there. Mark the matrix linguist-generated and exclude the directory from copywrite headers, prettier and eslint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The checked-in functions.json predated Terraform 1.8, so Fn was missing every function added since. generate.ts now: - drops `core::` namespaced aliases (terraform >=1.8 reports every builtin twice; the aliases are not valid identifiers) - merges OpenTofu-only functions from the availability matrix so Fn covers both products, linking their docstrings to opentofu.org New on Fn (and the hcl2cdk convert map): convert, ephemeralasnull, issensitive, templatestring, base64gunzip, cidrcontains, urldecode. None have variadic parameters, so no new INTERNAL_METHODS overrides. Upstream metadata also refined return types: format now returns string and formatlist string[] (previously any); rendered expressions are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e flag Terraform and OpenTofu support different function sets (and introduce shared functions at different versions). With the validateFunctionVersions feature flag enabled, every TerraformStack validates the functions used through Fn against the project's declared targetVersions instead of letting `terraform plan` fail later: - functions/usage-registry: records each Fn call at the terraformFunction() chokepoint (process-global by design; ~5ns per call) - ValidateFunctionVersionSupport: checks only functions present in the generated availability map against the declared targets via checkFeatureSupportedByTargets (range subset), with an availability hint pointing at the products/versions that do support the function - purely declarative: no binary is executed, so synth behaves identically in CI and environments without Terraform/OpenTofu installed (verifying the installed binary is the separate opt-in validateInstalledBinary CLI behavior) - add flag to FUTURE_FLAGS so cdktn init enables it for new projects Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The test runs every synth with a nonexistent TERRAFORM_BINARY_NAME, proving the validation never executes a binary: outcomes are driven purely by the declared targetVersions in cdktf.json (default baseline fails for a function introduced later; raising the declared floor makes it pass). Document in test/Readme.md how the .terraform.versions.json matrix and the CI Docker image binaries relate to TERRAFORM_BINARY_NAME, which tests execute a binary, and that synth-time validations are deliberately not version-sensitive. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The integration test sets a nonexistent TERRAFORM_BINARY_NAME to prove the Fn version validation is purely declarative (no binary executed). But the test's cdktf.json declared hashicorp/random, so setupTypescriptProject's `cdktn get` tried to fetch that provider's schema via `terraform init` and failed before any assertion ran. main.ts never uses the random provider, so removing it makes `cdktn get` a no-op and lets the nonexistent-binary premise hold across the entire flow (init -> get -> synth), strengthening the "no binary executed" guarantee. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
831c0e1 to
d99e731
Compare
Done! #262 merged, #269 rebased to main (Ready to merge) and this PR stacked on that to land the missing function support from TF 1.5.x -> TF(latest)/OTF(latest) with synth time validation |
|
|
||
| /** | ||
| * Terraform functions that are not universally available across all | ||
| * supported Terraform (>=1.5.7) and OpenTofu |
There was a problem hiding this comment.
We do technically support older versions still, but I don't think we need to backfill this as everything else has already existed.
| * used through `Fn` are supported by the version of the Terraform-compatible | ||
| * CLI (Terraform or OpenTofu) found on the system. | ||
| */ | ||
| export const VALIDATE_FUNCTION_VERSIONS = "validateFunctionVersions"; |
There was a problem hiding this comment.
As we are only applying this to new functions, is there actually a need for a flag to disable them? Users will be able to adjust their target versions appropriately to signify which functions are valid.
| const matrix: FunctionsMatrix = JSON.parse( | ||
| (await fs.readFile(FUNCTIONS_MATRIX_FILE)).toString(), | ||
| ); |
There was a problem hiding this comment.
Let's split that out separately
4c23aa5 to
9171dcd
Compare
…ion) (#269) ## Summary > Reworks unreleased #237 to not run terraform binary during synth Prerequisite for #268. Instead of probing whichever Terraform/OpenTofu binary happens to be installed during synth, projects declare the runtimes they intend to support, and validations answer: *"is this feature supported by the project's declared target range?"* — deterministically, with no binary required. Installed-binary verification becomes an explicit opt-in for commands that execute the binary anyway. ```jsonc // cdktf.json { "targetVersions": { "terraform": ">=1.5.7", "opentofu": ">=1.6.0" }, "validateInstalledBinary": true // opt-in, off by default } ``` ### Commits 1. **feat(cli): add targetVersions to cdktf.json config** — typed field in `@cdktn/commons`; `parseConfig` validates product names and npm-semver ranges (Terraform's `~>` syntax is rejected with a hint — npm semver would silently parse it as `~` with different semantics) and threads the declaration to apps through the existing `CDKTF_CONTEXT_JSON` channel. Also adds `parseTerraformCliVersion` to commons and the `validateInstalledBinary` flag. 2. **feat(lib): validate features against declared runtime targets** — `resolveTargetVersions(scope)` (context → dual-baseline default `terraform >=1.5.7` + `opentofu >=1.6.0`), `checkFeatureSupportedByTargets` (semver range **subset**: a feature passes only if supported across the *entire* declared range of every targeted product), and `ValidateFeatureTargetSupport` (an `IValidation` that never executes a binary). Synth-time binary probing is removed: `ValidateTerraformFeatureVersion` (merged unreleased in #237, zero call sites) is deleted together with its execSync machinery — the pure `parseTerraformCliVersion` parser and the `TerraformFeatureVersionConstraints` type remain. The long-released `ValidateBinaryVersion` and the resource-move probe built on it stay unchanged as the legacy exception, both marked `TODO(target-versions)`. 3. **feat(cli): opt-in installed-binary verification** — when `validateInstalledBinary: true`, `initalizeTerraform` (the diff/deploy/destroy chokepoint, where the binary is about to run anyway) verifies the installed product/version against the declared targets. Declared targets are deliberately *not* treated as proof about the local binary. 4. **feat(cli): write default targetVersions in init templates** — new projects get the dual baseline explicitly in `cdktf.json`. ### Semantics - **Subset check**: declaring `terraform: ">=1.5.7"` while using a feature that needs `>=1.8.0` fails — the project claims to support 1.5.7..1.8.0 where the feature is missing. The fix is raising the declared floor (or narrowing the range), which keeps the declaration honest. - **Missing product key in a feature's constraints** = unsupported by that product; **missing product key in declared targets** = the project doesn't target that product. - **Undeclared** = dual product baseline (strictest, most portable interpretation). - **Synth never executes a binary.** The only places a binary is run are commands that execute it anyway (opt-in `validateInstalledBinary`) and the legacy `ValidateBinaryVersion`/resource-move path kept for compatibility. ### Stacked work #268 (function availability validation) is stacked on this branch: its `ValidateFunctionVersionSupport` checks used `Fn` functions against the declared targets via `checkFeatureSupportedByTargets`, gated by the `validateFunctionVersions` feature flag. ## Test plan - [x] `@cdktn/commons`: 83/83 — parser accepts constraint-shaped targets, rejects unknown products / malformed ranges / `~>` / empty declarations / non-strings; context payload merge - [x] `cdktn`: 425/426 — resolver default + context override + malformed-context errors; subset pass/fail; independent product evaluation; product-not-targeted skip; complex ranges; `ValidateFeatureTargetSupport` end-to-end with an execSync spy proving no binary call (the 1 failure is the pre-existing Docker-daemon-dependent `matchers.test.ts`, unrelated) - [x] `@cdktn/cli-core`: 146 passed / 30 skipped (dist-dependent) — installed-binary check helper: satisfies/outside-range/undeclared-product/unparseable/default-baseline - [ ] Integration: existing init tests exercise the new template field in CI 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Terraform and OpenTofu have diverged in their built-in function sets, and the checked-in function metadata predated Terraform 1.8 — so
Fnwas missing every function added since, and using a function your targeted runtimes don't support only failed atterraform plantime. This PR fixes the bindings generator, regenerates the bindings to cover both products, and adds an opt-in synth-time validation of function usage against the project's declared targets.Full Source data: open-constructs/cdktn-planning - RFCS/03-function-availability/PROPOSAL.md
Full function matrix: https://cdktn.io/function-matrix.html
Commits (reviewable independently)
lib/→src/output paths,babel-tsparser, pinned babel deps.metadata functions -jsonacross every stable Terraform (1.5.7→1.15.6, 66 releases) and OpenTofu (1.6.0→1.12.1, 54 releases) release intofunctions-matrix.json. Includes apnpm update-function-matrixdelta updater for new releases (downloads only unseen versions) and the generator for the availability map. Baseline sweep scripts are marked linguist-generated and excluded from copywrite/prettier/eslint.core::aliases (terraform ≥1.8 reports every builtin twice; not valid identifiers), merges OpenTofu-only functions from the matrix. New onFnand thecdktn convertmap:convert,ephemeralasnull,issensitive,templatestring,base64gunzip,cidrcontains,urldecode. None are variadic, so no newINTERNAL_METHODS. Upstream metadata also refinedformat/formatlistreturn types (any→string/string[]; rendered expressions unchanged).validateFunctionVersionsflag) — a usage registry recordsFncalls at theterraformFunction()chokepoint (~5ns/call, 0.04% of anFncall);ValidateFunctionVersionSupportchecks used functions from the generated availability map against the project's declaredtargetVersionsviacheckFeatureSupportedByTargets(range subset), with an availability hint pointing at the products/versions that do support the function. Purely declarative — no binary is ever executed; synth behaves identically in CI and environments without Terraform/OpenTofu installed. Added toFUTURE_FLAGSsocdktn initenables it for new projects.TERRAFORM_BINARY_NAME, proving no binary involvement: the default baseline targets deterministically rejectissensitive(introduced TF 1.8 / Tofu 1.7), and raising the declared floor incdktf.jsonmakes it pass.Example validation error:
Companion docs PR: open-constructs/cdk-terrain-docs
Test plan
pnpm nx test cdktn— 433/434 pass (the one failure ismatchers.test.tstoPlanSuccessfully, which requires a running Docker daemon; environmental, unrelated)validations.test.ts: baseline functions never resolve targets (execSync spy), default-baseline rejection, declared-targets pass (no exec), product-exclusive with availability hint, OpenTofu-only targeting, malformed declared targets, flag on/off wiringnx run cdktn:build)pnpm packagedist (runs in CI); deterministic across the version matrix — no binary is executed🤖 Generated with Claude Code