feat: support newer provider plugin-protocol features via targetVersions (RFC-04)#296
feat: support newer provider plugin-protocol features via targetVersions (RFC-04)#296so0k wants to merge 17 commits into
Conversation
Draft PR #296 review notes / verification resultsI built and published a small reusable demo harness against this PR head so the new provider-protocol features can be exercised with real generated AWS bindings: https://github.com/sakul-learning/cdktn-provider-features-demo PR head used for the run: Verification performedUsing the PR-head packages directly, not npmjs:
The AWS provider schema from Terraform
Generated bindings confirmed:
OpenTofu UX / integration notes from the demoThese are non-blocking, but important feedback from trying to use the generated bindings in a real app:
Non-blocking findings / suggested follow-ups
Overall: the core provider-function and ephemeral-resource generated bindings worked in real synth/plan flows for Terraform and OpenTofu once the UX issues above were accounted for. The largest current generated-coverage gap is that Terraform exposes AWS list resources, actions, and resource identities, but I did not see first-class generated bindings for those schema sections yet. |
… path CdktfConfig.targetVersions (used by runGetInDir) returned the raw cdktf.json value, bypassing the parseConfig validation the main get handler goes through — and a malformed range then made semver.intersects throw inside the fetch-time emission check, crashing cdktn get with a raw stack trace. Two layers: the getter now validates via commons validateTargetVersions (warn + ignore invalid targets; generation proceeds, only the warning/ stamp lose them), and checkSchemaEmissionGapFamilies treats an invalid target range as not-wanted instead of throwing — a best-effort diagnostics path must never break get. Found in draft-PR verification (#296 review notes). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Terraform ephemeral blocks support only precondition/postcondition in lifecycle — createBeforeDestroy, preventDestroy, ignoreChanges and replaceTriggeredBy are state-oriented concepts that do not apply to stateless ephemeral resources. New TerraformEphemeralResourceLifecycle replaces the full TerraformResourceLifecycle on the ephemeral API; narrowing later would be jsii-breaking, so it lands before first release. Found in draft-PR verification (#296 review notes). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…generated JSDoc Calling a provider-defined function inside the configuration block of the same provider asks Terraform/OpenTofu to evaluate the function while that provider is still being configured — a self-referential cycle. Generated method JSDoc and TerraformProviderFunction.invoke now carry the caveat. Also documents why write-only usage registration deliberately skips ephemeral resources: write-only is a state concept, ephemeral resources have no state, and no provider schema in the RFC-04 sweep combines the two. From draft-PR verification (#296 review notes). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| const SELF_REFERENCE_CYCLE_JSDOC = | ||
| "Note: provider-defined functions are evaluated by the provider itself — do not call this inside the configuration of the same provider (Terraform reports a self-referential cycle)."; | ||
|
|
There was a problem hiding this comment.
not sure if useful to have this in every JSDocs - it's a very limited edge case
… path CdktfConfig.targetVersions (used by runGetInDir) returned the raw cdktf.json value, bypassing the parseConfig validation the main get handler goes through — and a malformed range then made semver.intersects throw inside the fetch-time emission check, crashing cdktn get with a raw stack trace. Two layers: the getter now validates via commons validateTargetVersions (warn + ignore invalid targets; generation proceeds, only the warning/ stamp lose them), and checkSchemaEmissionGapFamilies treats an invalid target range as not-wanted instead of throwing — a best-effort diagnostics path must never break get. Found in draft-PR verification (#296 review notes). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Terraform ephemeral blocks support only precondition/postcondition in lifecycle — createBeforeDestroy, preventDestroy, ignoreChanges and replaceTriggeredBy are state-oriented concepts that do not apply to stateless ephemeral resources. New TerraformEphemeralResourceLifecycle replaces the full TerraformResourceLifecycle on the ephemeral API; narrowing later would be jsii-breaking, so it lands before first release. Found in draft-PR verification (#296 review notes). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…generated JSDoc Calling a provider-defined function inside the configuration block of the same provider asks Terraform/OpenTofu to evaluate the function while that provider is still being configured — a self-referential cycle. Generated method JSDoc and TerraformProviderFunction.invoke now carry the caveat. Also documents why write-only usage registration deliberately skips ephemeral resources: write-only is a state concept, ephemeral resources have no state, and no provider schema in the RFC-04 sweep combines the two. From draft-PR verification (#296 review notes). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ac13f35 to
71148c5
Compare
… path CdktfConfig.targetVersions (used by runGetInDir) returned the raw cdktf.json value, bypassing the parseConfig validation the main get handler goes through — and a malformed range then made semver.intersects throw inside the fetch-time emission check, crashing cdktn get with a raw stack trace. Two layers: the getter now validates via commons validateTargetVersions (warn + ignore invalid targets; generation proceeds, only the warning/ stamp lose them), and checkSchemaEmissionGapFamilies treats an invalid target range as not-wanted instead of throwing — a best-effort diagnostics path must never break get. Found in draft-PR verification (#296 review notes). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Terraform ephemeral blocks support only precondition/postcondition in lifecycle — createBeforeDestroy, preventDestroy, ignoreChanges and replaceTriggeredBy are state-oriented concepts that do not apply to stateless ephemeral resources. New TerraformEphemeralResourceLifecycle replaces the full TerraformResourceLifecycle on the ephemeral API; narrowing later would be jsii-breaking, so it lands before first release. Found in draft-PR verification (#296 review notes). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…generated JSDoc Calling a provider-defined function inside the configuration block of the same provider asks Terraform/OpenTofu to evaluate the function while that provider is still being configured — a self-referential cycle. Generated method JSDoc and TerraformProviderFunction.invoke now carry the caveat. Also documents why write-only usage registration deliberately skips ephemeral resources: write-only is a state concept, ephemeral resources have no state, and no provider schema in the RFC-04 sweep combines the two. From draft-PR verification (#296 review notes). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
71148c5 to
244dd68
Compare
Once any provider function was invoked in a long-lived process, later unrelated apps in the same process (sequential apps in one jest file being the common case) saw the process-global usedProviderFunctions set and could fail validation despite never using provider functions — the new validator registers unconditionally on every stack. The Fn registry shares the design and the same cross-App leak, but its validator is feature-flag-gated. The registries stay process-global (tokens are scope-free; per-stack attribution is impossible by design) — but App construction now resets both, scoping usage to the current synthesis session. Within one App the global registry remains by design: every stack resolves the same targetVersions from App context, so a sibling-stack validation reports the same error the using stack would. Regression tests per review: app1 uses a provider function under satisfying targets, then app2 with default targets and no usage synths clean in the same process; analogous flag-gated Fn-registry case. From PR #296 review. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s too The fetch-time emission-gap warning lived inside readProviderSchema, so cachedAccess bypassed it entirely on a hit: a schema cached by an old CLI was reused silently even when the project's targetVersions admit the newer sections that fetch could never have contained. The warning moves to readSchema and now runs after every provider schema resolves — cached or fresh — using the cli_name/cli_version stamps the schema itself carries (falling back to probing the current binary when stamps are absent). Regression test drives readSchema twice against a temp cache dir: one producer call, two warnings. From PR #296 review. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
providerFeatureConstraints (usage boundaries, packages/cdktn) and SCHEMA_EMISSION_BOUNDARIES (fetching-CLI emission boundaries, @cdktn/provider-schema) now name features-matrix.json as their source, cross-reference each other, and spell out the deliberate OpenTofu provider-functions exception (language support 1.7.0 vs schema emission 1.8.0). The matrix README gains an in-repo consumers section. An automated drift check between the maps and the matrix is tracked in #309. From PR #296 review. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| // Only managed resources extend TerraformResource (and therefore have | ||
| // registerProviderFeatureUsage available); data sources, providers, and | ||
| // ephemeral resources do not, so their write-only attributes (if any) | ||
| // only get the deprecated getter. |
There was a problem hiding this comment.
deliberately not exposed there because the combination doesn't occur - verified empirically across every provider in the 1.15.6 sweep dump — zero ephemeral attributes carry write_only.
|
While extending 1. Provider-function bindings that return a documented "dynamic" type are force-coerced to string, silently dropping struct-typed resultsSome public static conditionIf(condition: any, valueIfTrue: any, valueIfFalse: any, providerLocalName?: string): any {
return cdktn.Token.asString(cdktn.TerraformProviderFunction.invoke(providerLocalName ?? "cfncompat", "condition_if", [condition, valueIfTrue, valueIfFalse])) as any;
}That's fine for the common string-return case, but breaks when the result is assigned to a struct-typed resource attribute: Repro: new KinesisStream(this, "stream", {
streamEncryption: CfncompatProviderFunctions.conditionIf(
someCondition, {}, { encryptionType: "KMS", keyId: "alias/aws/kinesis" },
),
});
Workaround: call Suggested fix: only wrap a provider function's result in 2.
|
… path CdktfConfig.targetVersions (used by runGetInDir) returned the raw cdktf.json value, bypassing the parseConfig validation the main get handler goes through — and a malformed range then made semver.intersects throw inside the fetch-time emission check, crashing cdktn get with a raw stack trace. Two layers: the getter now validates via commons validateTargetVersions (warn + ignore invalid targets; generation proceeds, only the warning/ stamp lose them), and checkSchemaEmissionGapFamilies treats an invalid target range as not-wanted instead of throwing — a best-effort diagnostics path must never break get. Found in draft-PR verification (#296 review notes). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2365bea to
1818644
Compare
… them
TerraformProviderFunction.invoke validated its whole args array with a
single variadic(anyValue) — and variadic()/listOf() filters out
null/undefined entries, which is right for a genuinely variadic list
but wrong for a fixed-arity positional call: invoke("cfncompat",
"condition_if", [cond, null, value]) lost the null and Terraform
failed with 'Not enough function arguments' (the shift makes it blame
the wrong slot). Arguments are now validated per position, so every
slot — including null/undefined — is preserved.
Preserving the slot exposed a second latent defect: FunctionCall
renders its arguments via Array.prototype.join, which collapses
null/undefined entries to empty strings, so the slot rendered as
'(cond, , value)' instead of the Terraform null keyword.
resolveExpressionPart now renders resolved null/undefined as 'null'
(a legal expression in every Terraform context this layer serves).
Generated wrappers for genuinely variadic functions already flatten
the trailing array before calling invoke, so each element stays its
own positional slot — behavior unchanged.
Reported in PR #296 verification against the cfncompat provider's
condition_if (CloudFormation Fn::If polyfill).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…unction results Wrappers for functions whose schema return type is dynamic/object/map coerced the result through Token.asString(...) as any (mirroring the Fn asAny convention). That encoded-string token is not recognized by Tokenization.isResolvable(), so when the result was assigned to a struct-typed resource attribute the generated OutputReference setter treated it as a plain object with no known keys and the attribute silently vanished from synth output — no warning, no error. The generated body now returns the raw TerraformProviderFunction.invoke result (an IResolvable, which struct setters recognize and resolve correctly); the declared return type stays any. String, number, bool and list mappings are unchanged. Synthetic fixture gains a condition_if-style dynamic-return function so the dynamic branch has its own snapshot coverage. Reported in PR #296 verification: cfncompat condition_if results assigned to awscc struct attributes disappeared from terraform plan. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
I also did a local provider-schema cache/binding-generation simulation across Terraform/OpenTofu CLI versions to sanity-check the cache-busting behavior around the new schema sections. What I verified:
One documentation point I think is worth making very explicit because it is easy to misread the matrix:
In other words, targeting OpenTofu 1.7 can be valid for using provider functions, but generating those bindings requires an OpenTofu 1.8+ generator binary. The current split between usage constraints and |
eduardomourar
left a comment
There was a problem hiding this comment.
Overall looks good to me. Just a few non-blocking comments.
Merged sweep observations + source-verified CLI serializer history for the provider plugin-protocol capability families (provider functions, ephemeral resources, write-only attributes, resource identity, list resources, actions, state stores) across Terraform 1.5.7-1.15.x and OpenTofu 1.6.0-1.12.x. Source dataset for the providerFeatureConstraints map in packages/cdktn. Sweep tooling, fixtures, report and proposal live in open-constructs/cdktn-planning RFCS/04-provider-feature-availability (same split as tools/generate-function-bindings/function-availability). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New public base class for ephemeral resources (Terraform >=1.10 / OpenTofu >=1.11), synthesizing into the top-level `ephemeral` key of cdk.tf.json and an `ephemeral "type" "name"` block in HCL output. References resolve as ephemeral.<type>.<id>.<attr>. Ephemeral blocks support no provisioners/connection and are not importable/movable, so those APIs are omitted. The constructor registers ValidateFeatureTargetSupport (from #269) unconditionally: synth fails when the project's declared targetVersions admit releases without ephemeral resources, naming the offending range. providerFeatureConstraints is the hand-maintained per-product constraint map for the provider-protocol feature families, sourced from tools/provider-feature-availability/features-matrix.json; internal by design (generated bindings reach it by extending base classes). Part of RFC-04 (provider feature availability), rollout item 2. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ions
Phase 0 of RFC-04 — schema acquisition correctness:
- Type the newer providers-schema sections in @cdktn/commons:
ephemeral_resource_schemas, functions (FunctionSignature),
resource_identity_schemas, and the write_only attribute flag.
List/action/state-store sections stay untyped (nothing consumes them;
they pass through the in-place sanitizer untouched).
- Extend sanitizeProviderSchema's attribute-doubling walk to
ephemeral_resource_schemas.
- Stamp fetched schemas with the fetching CLI ({cli_name, cli_version},
best-effort) — emission of the new sections is a property of the
fetching CLI, not just the provider.
- Make the experimental schema cache honest: the cache key now includes
the fetching CLI product+minor, so upgrading the CLI re-fetches richer
schemas instead of serving section-less ones forever.
- Warn at fetch time when the CLI is the bottleneck: if targetVersions
admit versions whose sections the fetching binary structurally cannot
emit, log which features will be missing and the minimum CLI versions
that would emit them. (Nothing passes targetVersions yet; the CLI
thread-through is a follow-up rollout item.)
Part of RFC-04 (provider feature availability), rollout item 3.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Third schema family in buildResourceModels, mirroring the data-source pipeline with an ephemeral_ type prefix: random_password generates EphemeralRandomPassword in ephemeral-random-password/, extending cdktn.TerraformEphemeralResource (which enforces targetVersions at synth). Generated Config interfaces extend TerraformEphemeralMetaArguments (no provisioners/connection) and no generateConfigForImport is emitted — ephemeral resources are not importable. Ephemeral models are appended after resources and data sources so class-name dedup stays order-stable (zero churn in existing snapshots). Snapshot-tested against a real terraform 1.15.6 providers-schema fragment of hashicorp/random (ephemeral + managed coexistence). Part of RFC-04 (provider feature availability), rollout item 4. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Providers shipping schema `functions` now get a per-provider
provider-functions namespace: TimeProviderFunctions.rfc3339Parse(ts)
renders ${provider::time::rfc3339_parse(...)}. Methods default the
provider:: namespace to the registry short name and take an optional
providerLocalName override (local names change the namespace, aliases
do not). Type mapping follows the Fn bindings rules, extended with an
object/map/dynamic -> any branch (the real time provider functions all
return objects).
Runtime flows through one public jsii chokepoint,
TerraformProviderFunction.invoke, which records usage in a dedicated
registry; TerraformStack now registers
ValidateProviderFunctionTargetSupport unconditionally, so synth fails
when provider functions are used and the declared targetVersions admit
releases without them (terraform >=1.8.0 / opentofu >=1.7.0 — OpenTofu
language support predates its schema emission, hence the asymmetry with
the fetch-time boundary).
Snapshot-tested against the real terraform 1.15.6 hashicorp/time
schema fragment plus a synthetic fixture covering variadic parameters,
primitive/list returns and reserved parameter names.
Part of RFC-04 (provider feature availability), rollout item 5.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…idate usage write_only attributes (e.g. the vault *_wo pairs) now flow into AttributeModel. The setter stays; the getter is emitted @deprecated with the protocol contract spelled out — providers never persist write-only values, so every read of that token is null and the state-backed getter is a trap. Removing it outright is JSII-breaking for regenerated/prebuilt providers, so: deprecate now, remove at the next prebuilt major. Setting a write-only attribute (via setter or constructor config, which bypasses the setter) calls the new protected TerraformResource.registerProviderFeatureUsage hook, which registers ValidateFeatureTargetSupport once per construct: synth fails when targetVersions admit terraform <1.11 / opentofu <1.11 with the standard upgrade-or-narrow-targets message. Generated bindings reach the validation machinery only by extending base classes, per the RFC guiding decision. Struct-level (nested) write-only attributes get the deprecated getter but no usage registration (no construct node) — accepted v1 limitation. Snapshot-tested against the real terraform 1.15.6 hashicorp/vault vault_alicloud_secret_backend fragment (secret_key_wo + secret_key_wo_version). Part of RFC-04 (provider feature availability), rollout item 6. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
GetOptions gains targetVersions, threaded from cdktf.json at all three creation sites (get, provider upgrade, runGetInDir) into ConstructsMaker and down to readSchema — driving the fetch-time warning when the fetching CLI structurally cannot emit schema sections the declared targets admit. constraints.json is additionally stamped with the targets and the fetching CLI identity for diagnostics and cache debugging. Both fields are diagnostics-only: targetVersions does not affect the generated surface (full surface always generated, narrowing happens at synth), so filterAlreadyGenerated's staleness check deliberately ignores them and legacy constraints.json files stay valid. Part of RFC-04 (provider feature availability), rollout item 7 — the last item of the RFC's PR split. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… path CdktfConfig.targetVersions (used by runGetInDir) returned the raw cdktf.json value, bypassing the parseConfig validation the main get handler goes through — and a malformed range then made semver.intersects throw inside the fetch-time emission check, crashing cdktn get with a raw stack trace. Two layers: the getter now validates via commons validateTargetVersions (warn + ignore invalid targets; generation proceeds, only the warning/ stamp lose them), and checkSchemaEmissionGapFamilies treats an invalid target range as not-wanted instead of throwing — a best-effort diagnostics path must never break get. Found in draft-PR verification (#296 review notes). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Terraform ephemeral blocks support only precondition/postcondition in lifecycle — createBeforeDestroy, preventDestroy, ignoreChanges and replaceTriggeredBy are state-oriented concepts that do not apply to stateless ephemeral resources. New TerraformEphemeralResourceLifecycle replaces the full TerraformResourceLifecycle on the ephemeral API; narrowing later would be jsii-breaking, so it lands before first release. Found in draft-PR verification (#296 review notes). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…generated JSDoc Calling a provider-defined function inside the configuration block of the same provider asks Terraform/OpenTofu to evaluate the function while that provider is still being configured — a self-referential cycle. Generated method JSDoc and TerraformProviderFunction.invoke now carry the caveat. Also documents why write-only usage registration deliberately skips ephemeral resources: write-only is a state concept, ephemeral resources have no state, and no provider schema in the RFC-04 sweep combines the two. From draft-PR verification (#296 review notes). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Once any provider function was invoked in a long-lived process, later unrelated apps in the same process (sequential apps in one jest file being the common case) saw the process-global usedProviderFunctions set and could fail validation despite never using provider functions — the new validator registers unconditionally on every stack. The Fn registry shares the design and the same cross-App leak, but its validator is feature-flag-gated. The registries stay process-global (tokens are scope-free; per-stack attribution is impossible by design) — but App construction now resets both, scoping usage to the current synthesis session. Within one App the global registry remains by design: every stack resolves the same targetVersions from App context, so a sibling-stack validation reports the same error the using stack would. Regression tests per review: app1 uses a provider function under satisfying targets, then app2 with default targets and no usage synths clean in the same process; analogous flag-gated Fn-registry case. From PR #296 review. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s too The fetch-time emission-gap warning lived inside readProviderSchema, so cachedAccess bypassed it entirely on a hit: a schema cached by an old CLI was reused silently even when the project's targetVersions admit the newer sections that fetch could never have contained. The warning moves to readSchema and now runs after every provider schema resolves — cached or fresh — using the cli_name/cli_version stamps the schema itself carries (falling back to probing the current binary when stamps are absent). Regression test drives readSchema twice against a temp cache dir: one producer call, two warnings. From PR #296 review. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
providerFeatureConstraints (usage boundaries, packages/cdktn) and SCHEMA_EMISSION_BOUNDARIES (fetching-CLI emission boundaries, @cdktn/provider-schema) now name features-matrix.json as their source, cross-reference each other, and spell out the deliberate OpenTofu provider-functions exception (language support 1.7.0 vs schema emission 1.8.0). The matrix README gains an in-repo consumers section. An automated drift check between the maps and the matrix is tracked in #309. From PR #296 review. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… them
TerraformProviderFunction.invoke validated its whole args array with a
single variadic(anyValue) — and variadic()/listOf() filters out
null/undefined entries, which is right for a genuinely variadic list
but wrong for a fixed-arity positional call: invoke("cfncompat",
"condition_if", [cond, null, value]) lost the null and Terraform
failed with 'Not enough function arguments' (the shift makes it blame
the wrong slot). Arguments are now validated per position, so every
slot — including null/undefined — is preserved.
Preserving the slot exposed a second latent defect: FunctionCall
renders its arguments via Array.prototype.join, which collapses
null/undefined entries to empty strings, so the slot rendered as
'(cond, , value)' instead of the Terraform null keyword.
resolveExpressionPart now renders resolved null/undefined as 'null'
(a legal expression in every Terraform context this layer serves).
Generated wrappers for genuinely variadic functions already flatten
the trailing array before calling invoke, so each element stays its
own positional slot — behavior unchanged.
Reported in PR #296 verification against the cfncompat provider's
condition_if (CloudFormation Fn::If polyfill).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…unction results Wrappers for functions whose schema return type is dynamic/object/map coerced the result through Token.asString(...) as any (mirroring the Fn asAny convention). That encoded-string token is not recognized by Tokenization.isResolvable(), so when the result was assigned to a struct-typed resource attribute the generated OutputReference setter treated it as a plain object with no known keys and the attribute silently vanished from synth output — no warning, no error. The generated body now returns the raw TerraformProviderFunction.invoke result (an IResolvable, which struct setters recognize and resolve correctly); the declared return type stays any. String, number, bool and list mappings are unchanged. Synthetic fixture gains a condition_if-style dynamic-return function so the dynamic branch has its own snapshot coverage. Reported in PR #296 verification: cfncompat condition_if results assigned to awscc struct attributes disappeared from terraform plan. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…re enum The feature argument was an open-ended string validated only at runtime. Export a string-valued ProviderFeature enum (values unchanged, so wire format, registry keys, and error messages stay identical) and use it in the registerProviderFeatureUsage signature so misuse is caught at compile time in TypeScript and surfaced as a proper enum in all jsii languages. The runtime unknownProviderFeature guard stays as the backstop for plain-JS and non-TypeScript jsii callers. Generated bindings now emit cdktn.ProviderFeature.WRITE_ONLY_ATTRIBUTES instead of the bare literal (compile-coupled to the signature change). TerraformEphemeralResource reuses providerFeatureLabels via the enum instead of duplicating the label literal; the constraint/label maps themselves stay internal. Addresses review feedback on #296. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cover the boundary conditions raised in review of #296: floors and pins exactly at a feature's minimum, pins and floors just below it, an empty targets object fed directly to the comparator, and prerelease-floor subset semantics (documented as inherent npm-semver behavior). On the emission-gap side, cover a fetching CLI exactly at a family boundary and one with no version metadata. In the get-time plumbing tests, cover a partial (single-product) targetVersions stamped verbatim without default injection, a schema without CLI identity omitting the cli stamp, and changed targetVersions between runs not forcing regeneration. Expected values verified against the repo's own semver (subset/lt/intersects) rather than assumed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1818644 to
30b1724
Compare
Implements RFC-04 — provider feature availability: support the newer provider plugin-protocol capability families with
targetVersions-aware codegen and synth-time validation. Proposal, dataset, sweep tooling and interactive report live in open-constructs/cdktn-planning →RFCS/04-provider-feature-availability/(only the merged matrix is vendored here, matching the function-availability split).Builds directly on the #269 foundation (
targetVersions+ValidateFeatureTargetSupport) and the #268 usage-registry pattern.Guiding decision
Generate the full surface the schema offers; narrow per project at synth time via
targetVersions. Generation-time filtering would fork the generated API by project configuration — impossible for prebuilt providers and hostile to caching. Constraints baked into the validations are the per-product>=ranges from the sweep dataset.Review guide (one commit per RFC rollout item)
chore: vendor provider-feature availability matrix— dataset digest + README only, no behavior change.feat(lib): TerraformEphemeralResource— new public base class synthesizing to the top-levelephemeralkey (JSON + HCL renderer), refs asephemeral.<type>.<id>.<attr>, no provisioners/connection/import/move. Constructor registers the target-version validation unconditionally (new API surface — only fires on use, no feature flag per RFC). InternalproviderFeatureConstraintsmap sourced from the vendored matrix.feat(provider-generator): acquire newer provider-protocol schema sections(Phase 0, a bugfix on its own) — commons types forfunctions/ephemeral_resource_schemas/resource_identity_schemas/write_only; sanitizer walks ephemeral schemas; fetched schemas stamped with the fetching CLI; cache key now includes CLI product+minor (a schema fetched once with an old CLI no longer poisons every later generation); fetch-time warning when the fetching binary structurally cannot emit sections the targets admit.feat(provider-generator): generate ephemeral resource bindings— third schema family mirroring thedata_pipeline (EphemeralRandomPasswordinephemeral-random-password/), config extendsTerraformEphemeralMetaArguments, nogenerateConfigForImport. Zero churn in existing snapshots (ephemeral models append after resources/data sources; class-name dedup is order-dependent).feat(provider-generator): generate provider-defined function bindings—TimeProviderFunctions.rfc3339Parse(ts)→${provider::time::rfc3339_parse(...)}; namespace defaults to the registry short name with aproviderLocalNameoverride (local names change the namespace, aliases don't). Runtime flows through one public jsii chokepoint (TerraformProviderFunction.invoke) feeding a usage registry;TerraformStackvalidates usage againstterraform >=1.8.0/opentofu >=1.7.0— note the deliberate asymmetry: OpenTofu language support (1.7.0) predates its schema emission (1.8.0), so generation and validation use different boundaries.feat(provider-generator): deprecate write-only attribute getters, validate usage— providers never persist write-only values (every read isnullby protocol contract), so the state-backed getter is a trap: emitted@deprecatednow, removal rides the next prebuilt major (JSII-breaking otherwise). Setting one (setter or constructor config) registers usage via a new protectedTerraformResource.registerProviderFeatureUsagehook — generated code reaches the validation machinery only by extending base classes.feat(cli): thread targetVersions into cdktn get—GetOptions.targetVersions→ConstructsMaker→readSchema, driving the Phase 0 fetch-time warning;constraints.jsongains diagnostic stamps (targetVersions, fetchingcli) without affecting thefilterAlreadyGeneratedstaleness logic (targets don't change codegen output, so they must not force regeneration).Test coverage
terraform providers schema -jsonfragments from the sweep (random ephemeral, time functions incl. object returns, vault*_wo), plus a synthetic fixture for variadic/reserved-name mapping branches. Zero churn in pre-existing snapshots.validations.test.ts(admit/exclude per product, hint text, the OpenTofu 1.7.0 asymmetry, registry resets).Known/deferred
matchers.test.ts › toPlanSuccessfullyfails in this environment before and after these changes (downloads the real docker provider; verified pre-existing via stash on the unmodified tree).hcl2cdkephemeral conversion), Edge-provider schema: cross-language compile coverage for ephemeral resources, provider functions, write-only attributes #311 (edge-provider schema cross-language coverage), Integration test: ephemeral resources end-to-end, gated on Terraform >= 1.10 #312 (ephemeral integration test gated on TF >= 1.10), fix(lib): HCL synth corrupts or drops core blocks (terraform{} extras, top-level overrides, lifecycle conditions) #313 (pre-existing HCL renderer defects), lib: add addProviderMeta() helper for terraform { provider_meta } blocks (value stays any) #304 (first-classprovider_meta; feat(lib): first-class provider_meta support (aws user_agent module attribution) #314 closed as its duplicate), Remove write-only attribute getters at the next prebuilt-provider major (deprecated in #296) #316 (remove the deprecated write-only getters at the next prebuilt-provider major — the Phase 3 end state).Review findings addressed (from the demo-harness verification comment)
targetVersionsvalidation bypass:CdktfConfig.targetVersions(therunGetInDirpath) now validates via commonsvalidateTargetVersions(warn + ignore), and the emission-gap check treats invalid ranges as not-wanted instead of throwing — a malformed range previously crashedcdktn getviasemver.intersects.TerraformEphemeralResourceLifecycle(precondition/postconditiononly) replaces the full managed-resource lifecycle on the ephemeral API, before it ships as jsii surface.TerraformProviderFunction.invokenow warn against calling a provider's functions inside that same provider's configuration block.write_onlyinside nested blocks gets the deprecated getter but skips usage registration; deep config scanning deserves its own PR (a miss degrades to the plan-time error, not silent breakage).Fn.ephemeralasnull+sensitiveon outputs is Terraform semantics (docs candidate); list resources / actions / resource identity codegen is the RFC Phase 4 deferral noted above.Second review round addressed
Appconstruction now resets both usage registries (a new App = a new synthesis session). Within an App the process-global registry is by design — every stack resolves the sametargetVersionsfrom App context — but usage no longer leaks into later, unrelated Apps in the same process (the reviewer's jest scenario). Regression tests cover the exact sequence; the flag-gatedFnregistry had the same latent leak and is reset too.readProviderSchemaintoreadSchema, running for cached and fresh schemas using thecli_name/cli_versionstamps the schema carries. Regression test: tworeadSchemacalls against a cache dir → one producer call, two warnings.features-matrix.jsonas their source and cross-reference each other; automated drift check tracked in In-repo consistency check for the provider-feature availability matrix and its hand-maintained maps #309.Third review round addressed (cfncompat/awscc demo findings)
dynamic/object/mapreturn types no longer wrap inToken.asString(...); they return the rawinvoke()IResolvable, whichTokenization.isResolvable()recognizes — struct-typed attribute assignments (OutputReference.internalValue) no longer vanish silently from synth output.invoke()dropped literalnullpositional arguments: arguments are now validated per position (variadic flattening stays in the generated wrappers where the signature is known), and a latent third bug this exposed was fixed too:FunctionCallrendered args viaArray.prototype.join, which collapsesnullentries to empty strings — resolvednull/undefinednow render as the Terraformnullkeyword. Regression tests pin the exactprovider::cfncompat::condition_if(true, null, {"a" = 1})rendering.Resolves Terraform CDK issues
🤖 Generated with Claude Code