Skip to content

feat(lib): validate Fn usage against Terraform/OpenTofu versions#268

Merged
so0k merged 6 commits into
mainfrom
fix-generate-function-bindings
Jun 26, 2026
Merged

feat(lib): validate Fn usage against Terraform/OpenTofu versions#268
so0k merged 6 commits into
mainfrom
fix-generate-function-bindings

Conversation

@so0k

@so0k so0k commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Stacked on #269 (declared targetVersions foundation).

Terraform and OpenTofu have diverged in their built-in function sets, and the checked-in function metadata predated Terraform 1.8 — so Fn was missing every function added since, and using a function your targeted runtimes don't support only failed at terraform plan time. 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)

  1. chore(lib): function availability dataset + tooling — swept metadata functions -json across every stable Terraform (1.5.7→1.15.6, 66 releases) and OpenTofu (1.6.0→1.12.1, 54 releases) release into functions-matrix.json. Includes a pnpm update-function-matrix delta 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.
  2. feat(lib): regenerate function bindings from Terraform 1.15.6 — filters core:: aliases (terraform ≥1.8 reports every builtin twice; not valid identifiers), merges OpenTofu-only functions from the matrix. New on Fn and the cdktn convert map: convert, ephemeralasnull, issensitive, templatestring, base64gunzip, cidrcontains, urldecode. None are variadic, so no new INTERNAL_METHODS. Upstream metadata also refined format/formatlist return types (anystring/string[]; rendered expressions unchanged).
  3. feat(lib): validate Fn usage against declared targets (validateFunctionVersions flag) — a usage registry records Fn calls at the terraformFunction() chokepoint (~5ns/call, 0.04% of an Fn call); ValidateFunctionVersionSupport checks used functions from the generated availability map against the project's declared targetVersions via checkFeatureSupportedByTargets (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 to FUTURE_FLAGS so cdktn init enables it for new projects.
  4. chore(tests): integration test + cross-version docs — every synth runs with a nonexistent TERRAFORM_BINARY_NAME, proving no binary involvement: the default baseline targets deterministically reject issensitive (introduced TF 1.8 / Tofu 1.7), and raising the declared floor in cdktf.json makes it pass.

Example validation error:

Validation failed with the following errors:
  [MyStack/resource] Terraform function "templatestring" requires terraform >=1.9.0, but the project targets terraform >=1.5.7. It is available in terraform >=1.9.0 and opentofu >=1.7.0.

Companion docs PR: open-constructs/cdk-terrain-docs

Test plan

  • pnpm nx test cdktn — 433/434 pass (the one failure is matchers.test.ts toPlanSuccessfully, which requires a running Docker daemon; environmental, unrelated)
  • 8 new unit tests in 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 wiring
  • jsii build clean (nx run cdktn:build)
  • Delta updater verified: rewinding the matrix one release and re-running reconstructs it byte-identically
  • Integration test requires pnpm package dist (runs in CI); deterministic across the version matrix — no binary is executed

🤖 Generated with Claude Code

Comment thread tools/generate-function-bindings/scripts/generate.ts
Comment thread tools/generate-function-bindings/scripts/functions.json
Comment thread packages/cdktn/src/features.ts
@so0k so0k force-pushed the fix-generate-function-bindings branch from d18ecd9 to 7b9a82b Compare June 11, 2026 18:39
@sakul-learning

This comment has been minimized.

@so0k

This comment was marked as resolved.

@so0k so0k force-pushed the fix-generate-function-bindings branch from 7b9a82b to 30a4cb8 Compare June 12, 2026 00:11
@so0k so0k changed the base branch from main to target-versions-foundation June 12, 2026 00:11
@so0k so0k force-pushed the target-versions-foundation branch from 03dc31f to 12f9975 Compare June 12, 2026 00:30
@so0k so0k force-pushed the fix-generate-function-bindings branch from 30a4cb8 to 72db7b9 Compare June 12, 2026 00:35
@so0k

This comment was marked as duplicate.

@so0k so0k force-pushed the fix-generate-function-bindings branch from 72db7b9 to e2a5af1 Compare June 12, 2026 01:41
@so0k

This comment was marked as resolved.

@X-Guardian

Copy link
Copy Markdown
Contributor

this PR absorbs some of the work in #262 and I could make absorb all of it cc @X-Guardian

Up to you Vincent.

My only comment on this Pr would be why are we using Python scripts rather than Node?

@so0k

This comment was marked as resolved.

@X-Guardian

Copy link
Copy Markdown
Contributor

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.

@so0k so0k force-pushed the target-versions-foundation branch from 1af9ee5 to 4c23aa5 Compare June 13, 2026 04:13
@so0k so0k force-pushed the fix-generate-function-bindings branch from 831c0e1 to d99e731 Compare June 13, 2026 04:28
@so0k

so0k commented Jun 13, 2026

Copy link
Copy Markdown
Contributor Author

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.

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

Comment thread packages/cdktn/src/functions/function-availability.generated.ts
Comment thread packages/cdktn/src/features.ts
Comment thread tools/generate-function-bindings/scripts/generate.ts
@so0k so0k force-pushed the target-versions-foundation branch from 4c23aa5 to 9171dcd Compare June 24, 2026 16:52
jsteinich pushed a commit that referenced this pull request Jun 25, 2026
…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>
Base automatically changed from target-versions-foundation to main June 25, 2026 02:15
…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>
so0k and others added 4 commits June 25, 2026 17:48
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>
@so0k so0k force-pushed the fix-generate-function-bindings branch from d99e731 to ea4e8df Compare June 25, 2026 12:12
@so0k so0k marked this pull request as ready for review June 25, 2026 12:12
Comment thread packages/cdktn/src/functions/usage-registry.ts
Comment thread test/Readme.md Outdated
…etVersions

Function version validation checks Fn usage against the declared
targetVersions using the vendored availability matrix; it never executes a
Terraform/OpenTofu CLI. Only the optional binary verification validation runs
<binary> version. Correct the stale wording in features.ts, the generator
docstring, and test/Readme.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@so0k so0k enabled auto-merge (squash) June 25, 2026 15:38
@so0k so0k merged commit cdf8115 into main Jun 26, 2026
514 of 516 checks passed
@so0k so0k deleted the fix-generate-function-bindings branch June 26, 2026 02:24
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.

4 participants