mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Merge pull request #32891 from hashicorp/jbardin/sensitive-mod-outputs
Store all sensitive marks for non-root module outputs in state
This commit is contained in:
commit
9504b2640f
@ -1887,3 +1887,70 @@ output "null_module_test" {
|
|||||||
_, diags = ctx.Apply(plan, m)
|
_, diags = ctx.Apply(plan, m)
|
||||||
assertNoErrors(t, diags)
|
assertNoErrors(t, diags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContext2Apply_moduleOutputWithSensitiveAttrs(t *testing.T) {
|
||||||
|
// Ensure that nested sensitive marks are stored when accessing non-root
|
||||||
|
// module outputs, and that they do not cause the entire output value to
|
||||||
|
// become sensitive.
|
||||||
|
m := testModuleInline(t, map[string]string{
|
||||||
|
"main.tf": `
|
||||||
|
module "mod" {
|
||||||
|
source = "./mod"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "test_resource" "b" {
|
||||||
|
// if the module output were wholly sensitive it would not be valid to use in
|
||||||
|
// for_each
|
||||||
|
for_each = module.mod.resources
|
||||||
|
value = each.value.output
|
||||||
|
}
|
||||||
|
|
||||||
|
output "root_output" {
|
||||||
|
// The root output cannot contain any sensitive marks at all.
|
||||||
|
// Applying nonsensitive would fail here if the nested sensitive mark were
|
||||||
|
// not maintained through the output.
|
||||||
|
value = [ for k, v in module.mod.resources : nonsensitive(v.output) ]
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
"./mod/main.tf": `
|
||||||
|
resource "test_resource" "a" {
|
||||||
|
for_each = {"key": "value"}
|
||||||
|
value = each.key
|
||||||
|
}
|
||||||
|
|
||||||
|
output "resources" {
|
||||||
|
value = test_resource.a
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
|
||||||
|
p := testProvider("test")
|
||||||
|
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
|
||||||
|
ResourceTypes: map[string]*configschema.Block{
|
||||||
|
"test_resource": {
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"value": {
|
||||||
|
Type: cty.String,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
Type: cty.String,
|
||||||
|
Sensitive: true,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
ctx := testContext2(t, &ContextOpts{
|
||||||
|
Providers: map[addrs.Provider]providers.Factory{
|
||||||
|
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
||||||
|
Mode: plans.NormalMode,
|
||||||
|
})
|
||||||
|
assertNoErrors(t, diags)
|
||||||
|
_, diags = ctx.Apply(plan, m)
|
||||||
|
assertNoErrors(t, diags)
|
||||||
|
}
|
||||||
|
@ -513,10 +513,6 @@ func (n *NodeDestroyableOutput) DotNode(name string, opts *dag.DotOpts) *dag.Dot
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (n *NodeApplyableOutput) setValue(state *states.SyncState, changes *plans.ChangesSync, val cty.Value) {
|
func (n *NodeApplyableOutput) setValue(state *states.SyncState, changes *plans.ChangesSync, val cty.Value) {
|
||||||
// If we have an active changeset then we'll first replicate the value in
|
|
||||||
// there and lookup the prior value in the state. This is used in
|
|
||||||
// preference to the state where present, since it *is* able to represent
|
|
||||||
// unknowns, while the state cannot.
|
|
||||||
if changes != nil && n.Planning {
|
if changes != nil && n.Planning {
|
||||||
// if this is a root module, try to get a before value from the state for
|
// if this is a root module, try to get a before value from the state for
|
||||||
// the diff
|
// the diff
|
||||||
@ -538,8 +534,8 @@ func (n *NodeApplyableOutput) setValue(state *states.SyncState, changes *plans.C
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We will not show the value is either the before or after are marked
|
// We will not show the value if either the before or after are marked
|
||||||
// as sensitivity. We can show the value again once sensitivity is
|
// as sensitive. We can show the value again once sensitivity is
|
||||||
// removed from both the config and the state.
|
// removed from both the config and the state.
|
||||||
sensitiveChange := sensitiveBefore || n.Config.Sensitive
|
sensitiveChange := sensitiveBefore || n.Config.Sensitive
|
||||||
|
|
||||||
@ -601,22 +597,14 @@ func (n *NodeApplyableOutput) setValue(state *states.SyncState, changes *plans.C
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// The state itself doesn't represent unknown values, so we null them
|
|
||||||
// out here and then we'll save the real unknown value in the planned
|
|
||||||
// changeset, if we have one on this graph walk.
|
|
||||||
log.Printf("[TRACE] setValue: Saving value for %s in state", n.Addr)
|
log.Printf("[TRACE] setValue: Saving value for %s in state", n.Addr)
|
||||||
sensitive := n.Config.Sensitive
|
|
||||||
unmarkedVal, valueMarks := val.UnmarkDeep()
|
|
||||||
|
|
||||||
// If the evaluated value contains sensitive marks, the output has no
|
// non-root outputs need to keep sensitive marks for evaluation, but are
|
||||||
// choice but to declare itself as "sensitive".
|
// not serialized.
|
||||||
for mark := range valueMarks {
|
if n.Addr.Module.IsRoot() {
|
||||||
if mark == marks.Sensitive {
|
val, _ = val.UnmarkDeep()
|
||||||
sensitive = true
|
val = cty.UnknownAsNull(val)
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stateVal := cty.UnknownAsNull(unmarkedVal)
|
state.SetOutputValue(n.Addr, val, n.Config.Sensitive)
|
||||||
state.SetOutputValue(n.Addr, stateVal, sensitive)
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user