Repository navigation
resource/pingone_davinci_variable: Export secret variables with variable-backed empty values - #161
Merged
Merged
Conversation
Regression Test Results
✅ No regressions detectedAll export configurations produced compatible output. Generated by regression workflow • View run |
…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>
Regression Test Results
✅ No regressions detectedAll 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
force-pushed
the
release-v052
branch
from
October 16, 2026 21:11
762e075 to
76d13f1
Compare
Regression Test Results
✅ No regressions detectedAll export configurations produced compatible output. Generated by regression workflow • View run |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Secret DaVinci variables (
data_type = "secret") now export with avalueblock backed by a generated, sensitive, empty Terraform variable — matching the connectorclientSecretcontract.Before: secret variables exported as resources and import blocks but with no
valueblock 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 declaredsensitive = truein module + rootvariables.tf, and its tfvars entry is empty (= "" # Secret value - provide manuallyunder--include-values).terraform planfails on an empty secret by design — the provider'ssecret_stringcarriesLengthAtLeast(1)(the connector'sclientSecretdoes not) — so users cannot silently apply an empty secret. Non-secret variables are byte-identical to before, andmutablenow passes through from the API instead of being forcedtrue.Mechanism is declarative — no platform Go code, no new handlers:
definitions/pingone/davinci/variable.yaml— aconditional_defaultsrule re-emits the block suppressed by the existingskip_conditionswith the provider-requiredsecret_stringkey (declared before the mutable rule so secrets keep their APImutable); theeligible_attributesrule gains conditionalis_secret_when_all/sensitive_when_allflags ondata_type = secretinstead of a blanketis_secret.internal/schema—VariableRulegrows the two conditional flag lists; theDefaultConditionevaluator moves from the processor toschema.EvaluateConditionsso 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_defaultsassigned the single parsedSetValuemap by reference into every resource'sAttributes, so the extractor's later per-resourcevar.Xinjection into that shared single-key block leaked across resources —secret-arenderedvar.davinci_variable_secret_b_value(both resources applied the value of whatever variable was processed last).evaluateConditionalDefaultsnow deep-copies map/sliceSetValueper resource. Audited: it is the only map-valuedset_valuein the codebase (the other rules set scalars, which alias-proof by value semantics), so no other resource was affected on any branch.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 withsecret_string),TestProcessorConditionalDefaults_SecretMutablePassesThrough(APImutablesurvives — pins rule ordering),TestProcessorConditionalDefaults_SecretRuleNotFiredForStringType(nosecret_stringleak 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.Xunquoted)internal/formatters/tfjson:TestFormat_TypeDiscriminatedBlock_SecretStringKey("secret_string": "${var.X}")internal/schema:TestLoadVariableDefinitionextended to pin the YAML rule shape (conditional flags present,is_secretfalse, secret rule precedes mutable rule)make vet,gofmt -l, andgo 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 onmaintoo. It is map-iteration-order dependent: the fixture walksnode1/node2map keys in nondeterministic order and expects the #130 disambiguation suffix to always land onnode1. 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
valueblocks, both variables declaredsensitive = true, tfvars entries empty with the manual-fill comment, non-secret output unchanged,mutablepassthrough confirmed. The first live run exposed the aliasing bug above; the fix is covered by the unit regression test.Breaking changes
None. New
valueblocks and variables are additive; the accepted plan-fails-until-filled UX for empty secrets matches the connectorclientSecretcontract and is called out in the changelog entry (.changelog/pr-161.txt).🤖 Generated with Claude Code