Repository navigation
Conversation
Save resolved Terraform module inputs in a reserved sensitive output during deployment. Restore them before destroy so that redacted sensitive properties do not cause config validation failures. Fixes #13330 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: nicolejms <nicolej@microsoft.com>
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
There was a problem hiding this comment.
🟡 Changes recommended
Saved inputs can be lost after failed destruction, and numeric restoration can change input values.
2 open issues
What changed in this PR
Restores Terraform recipe inputs before deletion to address failures caused by redacted sensitive properties.
Changes:
- Saves module inputs in a reserved sensitive state output and restores them only for destroy.
- Adds validation, regression tests, and an optional Terraform lifecycle test.
- Documents state security and compatibility with older deployments.
| File | Description |
|---|---|
pkg/recipes/terraform/execute.go |
Integrates input persistence and restoration. |
pkg/recipes/terraform/execute_test.go |
Tests restoration, failures, logging, and lifecycle behavior. |
pkg/recipes/terraform/config/inputs.go |
Encodes, validates, and restores saved inputs. |
pkg/recipes/terraform/config/inputs_test.go |
Tests round trips and invalid saved data. |
docs/architecture/extensibility.md |
Explains retention, security, and legacy-state behavior. |
Review effort: Balanced
…stroy failure Fill only missing or null current inputs from the saved values, so module version changes do not break deletion. After a failed destroy, run a refresh-only apply to write the saved inputs output back to state for a retry. Install Terraform in the unit-test workflow so the lifecycle test runs in CI. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: nicolejms <nicolej@microsoft.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #13343 +/- ##
==========================================
+ Coverage 60.27% 60.37% +0.09%
==========================================
Files 748 749 +1
Lines 44255 44366 +111
==========================================
+ Hits 26676 26784 +108
- Misses 17579 17582 +3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Decode saved and current inputs with UseNumber so integers above 2^53 are not rounded through float64. Keep rejecting trailing data after the saved JSON value. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: nicolejms <nicolej@microsoft.com>
Radius functional test overviewClick here to see the test run details
Test Status⌛ Building Radius and pushing container images for functional tests... |
|
Let us re-consider this approach.. We should not persist the original sensitive inputs in Terraform state to support deletion. Redacting them on the Radius resource while retaining a recoverable copy in _radius_recipe_inputs is ideally not the behavior we want. We need a deletion approach that does not retain those values. The guarded recipe-side placeholder appears appropriate for this secret recipe because destroy does not need the original secret value; create/update must continue rejecting missing values. Please reconsider the runtime design on that basis. |
|
making the fix in the recipe is the correct way to do this. closing this pr |


Summary
This change saves the resolved Terraform module inputs during deployment. Radius writes the inputs to a reserved, sensitive Terraform output named
_radius_recipe_inputs. Beforeterraform destroy, Radius reads this output and restores the module inputs. Thus, deletion does not use the redactednullvalues of sensitive properties.Restore rules:
null. Current non-null values stay unchanged.If
terraform destroyfails after a restore, Radius runsterraform apply -refresh-only. Terraform removes root outputs during a destroy apply, also when the destroy fails. The refresh-only apply writes the saved output back to state, so a retry can restore the inputs again. If this step fails, Radius logs the error and returns the original destroy error.Create and update operations do not restore inputs. A missing required value still causes those operations to fail.
The unit-test workflow now installs Terraform and sets
RADIUS_REQUIRE_TERRAFORM_TESTS=true. Thus, the real Terraform lifecycle test runs in CI and fails if Terraform is missing.Reason for change
Radius stores sensitive resource properties redacted as
null. During deletion, Terraform validates the recipe config with thesenullvalues. When a required module argument comes from a sensitive property,terraform destroyfails and the resource cannot be deleted.Fixes #13330
Notes:
${}and%{}text in the saved data. It is not encryption. The Terraformsensitiveflag does not encrypt state. Access control on the state backend protects the saved values. This also removes the benefit of Terraform write-only and ephemeral attributes for these inputs. An alternative design keeps the ciphertext on the Radius resource and decrypts it only for delete. Please confirm which design to use.How to test
Prerequisites: Go toolchain and Terraform CLI (
make install-terraform). IfRADIUS_REQUIRE_TERRAFORM_TESTSis nottrueand Terraform is not installed, the lifecycle test is skipped.Run the unit and regression tests:
Expected result: all tests pass.
TestRecipeInputsTerraformLifecycledeploys module v1 with real Terraform, changes to module v2 (one variable removed, one required variable added), and redacts the sensitive input. The first destroy fails on purpose and the test confirms that the saved output stays in state. The retry succeeds.End-to-end check on a cluster:
Validation that I ran (Windows, Terraform 1.16.6):
RADIUS_REQUIRE_TERRAFORM_TESTS=true go test ./pkg/recipes/terraform -run 'TestRecipeInputs|TestInitAndDestroyRestoresRecipeInputs|TestGenerateConfig|TestValidateOutputMappings|TestUsesOutputMappings|TestRetryTerraformInit|TestIsTransientTerraformInitError' -count=1go test ./pkg/recipes/terraform/config/... ./pkg/recipes/driver/terraform -skip 'Test_Save_ConfigFileReadOnly|Test_Save_InvalidWorkingDir' -count=1TestRecipeInputsTerraformLifecycleUseNumber, then runTestRecipeInputsPreserveNumericPrecisiongofmt -l pkg/recipes/terraformandgo vet ./pkg/recipes/terraform/...markdownlint-cli2 docs/architecture/extensibility.md --config ./.github/linters/.markdownlint-cli2.yamlmarkdown-table-formatter docs/architecture/extensibility.md --checkmake spellcheckgit diff --checkshellcheck -x build/scripts/install-terraform.sh(script used by the new workflow step)actionlint .github/workflows/unit-tests.yamlNot run on Windows: tests that create symlinks (they need extra OS privileges),
Test_Save_ConfigFileReadOnlyandTest_Save_InvalidWorkingDir(they fail onmainon Windows because of OS error text), andgo test -race(not supported on windows/arm64). CI runs these tests on Linux.File change summary
pkg/recipes/terraform/config/inputs.goAddRecipeInputsandRestoreRecipeInputs. They save the module inputs to a reserved sensitive output and restore them. Restore fills only missing ornullvalues and only for inputs in the current config. It keeps numbers exact (UseNumber) and rejects trailing data. It sets the output to the saved value. It does not put secret data in error messages.pkg/recipes/terraform/config/inputs_test.gopkg/recipes/terraform/execute.gopkg/recipes/terraform/execute_test.go.github/workflows/unit-tests.yamlRADIUS_REQUIRE_TERRAFORM_TESTS=true, so the lifecycle test runs in CI.docs/architecture/extensibility.md