Skip to content

resource/pingone_davinci_variable: Export secret variables with variable-backed empty values - #161

Merged
samir-gandhi merged 3 commits into
mainfrom
release-v052
Oct 16, 2026
Merged

samir-gandhi merged 3 commits into
mainfrom
release-v052

Conversation

@samir-gandhi

@samir-gandhi samir-gandhi commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Secret DaVinci variables (data_type = "secret") now export with a value block backed by a generated, sensitive, empty Terraform variable — matching the connector clientSecret contract.

Before: secret variables exported as resources and import blocks but with no value block and no Terraform input variable — users had to hand-write both to adopt an environment.

After: each secret variable exports value = { secret_string = var.davinci_variable_<name>_value }, the generated variable is declared sensitive = true in module + root variables.tf, and its tfvars entry is empty (= "" # Secret value - provide manually under --include-values). terraform plan fails on an empty secret by design — the provider's secret_string carries LengthAtLeast(1) (the connector's clientSecret does not) — so users cannot silently apply an empty secret. Non-secret variables are byte-identical to before, and mutable now passes through from the API instead of being forced true.

Mechanism is declarative — no platform Go code, no new handlers:

  • definitions/pingone/davinci/variable.yaml — a conditional_defaults rule re-emits the block suppressed by the existing skip_conditions with the provider-required secret_string key (declared before the mutable rule so secrets keep their API mutable); the eligible_attributes rule gains conditional is_secret_when_all / sensitive_when_all flags on data_type = secret instead of a blanket is_secret.
  • internal/schema — VariableRule grows the two conditional flag lists; the DefaultCondition evaluator moves from the processor to schema.EvaluateConditions so the processor and extractor share one implementation.
  • internal/variables/extractor.go — evaluates the conditional flags; absent condition lists never fire (an empty list must not be vacuously true — that bug would have marked every variable secret).

Cross-resource aliasing fix

Live verification against a two-secret environment caught a real bug the unit tests missed: conditional_defaults assigned the single parsed SetValue map by reference into every resource's Attributes, so the extractor's later per-resource var.X injection into that shared single-key block leaked across resources — secret-a rendered var.davinci_variable_secret_b_value (both resources applied the value of whatever variable was processed last). evaluateConditionalDefaults now deep-copies map/slice SetValue per resource. Audited: it is the only map-valued set_value in the codebase (the other rules set scalars, which alias-proof by value semantics), so no other resource was affected on any branch.

Branch note: this PR previously carried v0.5.2 release-notes preparation and a pr-155 changelog archive. It has been restructured to ship content only — the release will be v0.6.0 (#159 + #161), prepared in a separate release PR after both content PRs merge, per the repo's release convention.

Tests

All new behaviour is covered; full suite green on this branch (go test ./internal/... -count=1 — 13/13 test packages; the only failure across repeated runs is the known pre-existing flake below):

  • internal/core: TestProcessorConditionalDefaults_SecretValueBlockEmitted (block re-emitted with secret_string), TestProcessorConditionalDefaults_SecretMutablePassesThrough (API mutable survives — pins rule ordering), TestProcessorConditionalDefaults_SecretRuleNotFiredForStringType (no secret_string leak into plain variables)
  • internal/variables: TestSecretVariablePipeline / TestSecretVariablePipeline_PlainStringUnchanged (end-to-end, real embedded YAML: secret → secret+sensitive variable, plain → unchanged), TestExtract_ConditionalSecretRule / _NotSecretForStringType, TestExtract_ConditionalRule_NoConditionsNeverFires (vacuous-true guard), TestSecretVariablePipeline_TwoSecretsNoCrossWire (aliasing regression — fails without the deep-copy, reproduces the live-observed bug)
  • internal/formatters/hcl: TestFormatter_Format_TypeDiscriminatedBlock_SecretStringKey (secret_string = var.X unquoted)
  • internal/formatters/tfjson: TestFormat_TypeDiscriminatedBlock_SecretStringKey ("secret_string": "${var.X}")
  • internal/schema: TestLoadVariableDefinition extended to pin the YAML rule shape (conditional flags present, is_secret false, secret rule precedes mutable rule)

make vet, gofmt -l, and go run ./tools/validate-definitions definitions/ all clean.

Known test flake (pre-existing, not from this branch)

TestResolveEmbeddedReferences_DisambiguatedName_StableAcrossEnvironments (internal/core/embedded_references_test.go:1357) fails intermittently (~1-in-3 full-suite runs) — reproduced with this branch's changes stashed, so it exists on main too. It is map-iteration-order dependent: the fixture walks node1/node2 map keys in nondeterministic order and expects the #130 disambiguation suffix to always land on node1. Worth a dedicated fix; not a regression from this PR.

Live verification

Validated against the org-admin DaVinci environment (8 variables, 2 secret): secret resources gained correct per-resource value blocks, both variables declared sensitive = true, tfvars entries empty with the manual-fill comment, non-secret output unchanged, mutable passthrough confirmed. The first live run exposed the aliasing bug above; the fix is covered by the unit regression test.

Breaking changes

None. New value blocks and variables are additive; the accepted plan-fails-until-filled UX for empty secrets matches the connector clientSecret contract and is called out in the changelog entry (.changelog/pr-161.txt).

🤖 Generated with Claude Code

samir-gandhi added a commit that referenced this pull request Oct 16, 2026
The PR opened as #161, not the predicted #160. Renames the changelog
entry file and updates the PR references in the generated v0.5.2 release
notes to match.

Co-Authored-By: Claude Code <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Regression Test Results

Matrix Entry Status Breaking Acceptable

✅ No regressions detected

All export configurations produced compatible output.


Generated by regression workflow • View run

samir-gandhi and others added 2 commits October 16, 2026 15:10
…ble-backed empty values

Secret DaVinci variables (data_type = "secret") were exported as resources
and import blocks but without their value block and without a generated
Terraform input variable, because skip_conditions suppressed the whole
type_discriminated_block for secret data types.

They now export like other variable types: conditional_defaults re-emits the
suppressed block with the provider-required secret_string key, and the
variable extractor backs it with a generated Terraform variable marked
sensitive = true whose tfvars entry stays empty (the API returns no readable
value). The mutable conditional default no longer fires for secrets, so the
API-provided mutable value passes through.

Mechanism is declarative — no platform Go code:

- variable.yaml: conditional_defaults emits value = {secret_string: ...} for
  data_type = secret (declared before the mutable rule so secrets keep their
  API mutable); the eligible_attributes rule gains conditional
  is_secret_when_all / sensitive_when_all flags on data_type.
- schema: VariableRule grows conditional is_secret_when_all /
  sensitive_when_all lists; the DefaultCondition evaluator moves to
  schema.EvaluateConditions so the processor and extractor share semantics.
- variables/extractor: evaluates the conditional rule flags; absent
  condition lists never fire (an empty list must not be vacuously true).

Secret values are never emitted: the provider requires a non-empty
secret_string (LengthAtLeast(1)), so Terraform plan fails until the user
fills the value in — the same manual-fill contract as connector clientSecret,
which has no length validator.

Co-Authored-By: Claude Code <noreply@anthropic.com>
…aliasing

The parsed YAML ConditionalDefault.SetValue is a single shared value across
every resource of a type. Assigning it directly into each resource's
Attributes map aliased one underlying map — for the DaVinci secret-variable
rule (value = {secret_string: ...}), the variable extractor's subsequent
ResolvedReference injection into that single-key block was visible from
every resource, so two secret variables both rendered the LAST resource's
var.X reference in the exported .tf (secret-a pointed at
davinci_variable_secret_b_value).

evaluateConditionalDefaults now deep-copies map and slice SetValue values
per resource; scalars are unaffected. Regression test
TestSecretVariablePipeline_TwoSecretsNoCrossWire processes two secret
variables through the real YAML definition and asserts each resource
references its own variable.

Found in live export verification against the org-admin environment; unit
tests using single resources could not observe it.

Co-Authored-By: Claude Code <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Regression Test Results

Matrix Entry Status Breaking Acceptable
default-hcl ✅ PASS 0 0
default-tfjson ✅ PASS 0 0
hcl-include-all ✅ PASS 0 0
hcl-skip-deps ✅ PASS 0 0
tfjson-skip-deps ✅ PASS 0 0

✅ No regressions detected

All export configurations produced compatible output.


Generated by regression workflow • View run

The PR opened as #161, not the predicted #160. Renames the changelog
entry file and updates the PR references in the generated v0.5.2 release
notes to match.

Co-Authored-By: Claude Code <noreply@anthropic.com>

# Conflicts:
#	release-notes/v0.5.2/GITHUB_RELEASE.md
#	release-notes/v0.5.2/RELEASE_NOTES.adoc
@samir-gandhi samir-gandhi changed the title release: Prepare v0.5.2 — export secret DaVinci variables with variable-backed empty values resource/pingone_davinci_variable: Export secret variables with variable-backed empty values Oct 16, 2026
@github-actions

Copy link
Copy Markdown

Regression Test Results

Matrix Entry Status Breaking Acceptable
default-hcl ✅ PASS 0 0
default-tfjson ✅ PASS 0 0
hcl-include-all ✅ PASS 0 0
hcl-skip-deps ✅ PASS 0 0
tfjson-skip-deps ✅ PASS 0 0

✅ No regressions detected

All export configurations produced compatible output.


Generated by regression workflow • View run

@samir-gandhi
samir-gandhi merged commit aa4e567 into main Oct 16, 2026
8 checks passed
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.

1 participant