Skip to content

Commit

Permalink
Merge pull request #32583 from hashicorp/jbardin/store-null-module-ou…
Browse files Browse the repository at this point in the history
…tputs

save null module outputs in state
  • Loading branch information
jbardin committed Jan 26, 2023
2 parents 6236fb5 + 47fed6d commit fc8fed0
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 22 deletions.
55 changes: 55 additions & 0 deletions internal/terraform/context_apply2_test.go
Expand Up @@ -1832,3 +1832,58 @@ output "a" {
_, diags = ctx.Apply(plan, m)
assertNoErrors(t, diags)
}

func TestContext2Apply_destroyNullModuleOutput(t *testing.T) {
p := testProvider("test")
ctx := testContext2(t, &ContextOpts{
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
},
})

m := testModuleInline(t, map[string]string{
"main.tf": `
module "null_module" {
source = "./mod"
}
locals {
module_output = module.null_module.null_module_test
}
output "test_root" {
value = module.null_module.test_output
}
output "root_module" {
value = local.module_output #fails
}
`,

"mod/main.tf": `
output "test_output" {
value = "test"
}
output "null_module_test" {
value = null
}
`,
})

// verify plan and apply
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
Mode: plans.NormalMode,
})
assertNoErrors(t, diags)
state, diags := ctx.Apply(plan, m)
assertNoErrors(t, diags)

// now destroy
plan, diags = ctx.Plan(m, state, &PlanOpts{
Mode: plans.DestroyMode,
})
assertNoErrors(t, diags)
_, diags = ctx.Apply(plan, m)
assertNoErrors(t, diags)
}
45 changes: 23 additions & 22 deletions internal/terraform/node_output.go
Expand Up @@ -592,30 +592,31 @@ func (n *NodeApplyableOutput) setValue(state *states.SyncState, changes *plans.C
changes.RemoveOutputChange(n.Addr)
}

if val.IsKnown() && !val.IsNull() {
// 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 below, 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 evaluate 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
}
}

stateVal := cty.UnknownAsNull(unmarkedVal)
state.SetOutputValue(n.Addr, stateVal, sensitive)

} else {
// Null outputs must be saved for modules so that they can still be
// evaluated. Null root outputs are removed entirely, which is always fine
// because they can't be referenced by anything else in the configuration.
if n.Addr.Module.IsRoot() && val.IsNull() {
log.Printf("[TRACE] setValue: Removing %s from state (it is now null)", n.Addr)
state.RemoveOutputValue(n.Addr)
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
}
}

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

0 comments on commit fc8fed0

Please sign in to comment.