Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Store all sensitive marks for non-root module outputs in state #32891

Merged
merged 3 commits into from
Mar 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions internal/terraform/context_apply2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1887,3 +1887,70 @@ output "null_module_test" {
_, diags = ctx.Apply(plan, m)
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)
}
30 changes: 9 additions & 21 deletions internal/terraform/node_output.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
// 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 this is a root module, try to get a before value from the state for
// the diff
Expand All @@ -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
// as sensitivity. We can show the value again once sensitivity is
// We will not show the value if either the before or after are marked
// as sensitive. We can show the value again once sensitivity is
// removed from both the config and the state.
sensitiveChange := sensitiveBefore || n.Config.Sensitive

Expand Down Expand Up @@ -601,22 +597,14 @@ func (n *NodeApplyableOutput) setValue(state *states.SyncState, changes *plans.C
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)
sensitive := n.Config.Sensitive
unmarkedVal, valueMarks := val.UnmarkDeep()

// If the evaluated value contains sensitive marks, the output has no
// choice but to declare itself as "sensitive".
for mark := range valueMarks {
if mark == marks.Sensitive {
sensitive = true
break
}

// non-root outputs need to keep sensitive marks for evaluation, but are
// not serialized.
if n.Addr.Module.IsRoot() {
val, _ = val.UnmarkDeep()
val = cty.UnknownAsNull(val)
}

stateVal := cty.UnknownAsNull(unmarkedVal)
state.SetOutputValue(n.Addr, stateVal, sensitive)
state.SetOutputValue(n.Addr, val, n.Config.Sensitive)
}