Merge pull request #32308 from hashicorp/jbardin/output-eval-fix

always evaluate outputs from state during apply
This commit is contained in:
James Bardin 2022-12-01 09:33:31 -05:00 committed by GitHub
commit 23aaa39747
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 66 additions and 25 deletions

View File

@ -375,6 +375,11 @@ func (d *evaluationStateData) GetModule(addr addrs.ModuleCall, rng tfdiags.Sourc
// so we only need to store these by instance key. // so we only need to store these by instance key.
stateMap := map[addrs.InstanceKey]map[string]cty.Value{} stateMap := map[addrs.InstanceKey]map[string]cty.Value{}
for _, output := range d.Evaluator.State.ModuleOutputs(d.ModulePath, addr) { for _, output := range d.Evaluator.State.ModuleOutputs(d.ModulePath, addr) {
val := output.Value
if output.Sensitive {
val = val.Mark(marks.Sensitive)
}
_, callInstance := output.Addr.Module.CallInstance() _, callInstance := output.Addr.Module.CallInstance()
instance, ok := stateMap[callInstance.Key] instance, ok := stateMap[callInstance.Key]
if !ok { if !ok {
@ -382,7 +387,7 @@ func (d *evaluationStateData) GetModule(addr addrs.ModuleCall, rng tfdiags.Sourc
stateMap[callInstance.Key] = instance stateMap[callInstance.Key] = instance
} }
instance[output.Addr.OutputValue.Name] = output.Value instance[output.Addr.OutputValue.Name] = val
} }
// Get all changes that reside for this module call within our path. // Get all changes that reside for this module call within our path.
@ -426,10 +431,6 @@ func (d *evaluationStateData) GetModule(addr addrs.ModuleCall, rng tfdiags.Sourc
} }
instance[cfg.Name] = outputState instance[cfg.Name] = outputState
if cfg.Sensitive {
instance[cfg.Name] = outputState.Mark(marks.Sensitive)
}
} }
// any pending changes override the state state values // any pending changes override the state state values

View File

@ -150,7 +150,11 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
&AttachStateTransformer{State: b.State}, &AttachStateTransformer{State: b.State},
// Create orphan output nodes // Create orphan output nodes
&OrphanOutputTransformer{Config: b.Config, State: b.State}, &OrphanOutputTransformer{
Config: b.Config,
State: b.State,
Planning: true,
},
// Attach the configuration to any resources // Attach the configuration to any resources
&AttachResourceConfigTransformer{Config: b.Config}, &AttachResourceConfigTransformer{Config: b.Config},

View File

@ -176,11 +176,13 @@ func (n *nodeCloseModule) Name() string {
} }
func (n *nodeCloseModule) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) { func (n *nodeCloseModule) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) {
if n.Addr.IsRoot() { if !n.Addr.IsRoot() {
return
}
// If this is the root module, we are cleaning up the walk, so close // If this is the root module, we are cleaning up the walk, so close
// any running provisioners // any running provisioners
diags = diags.Append(ctx.CloseProvisioners()) diags = diags.Append(ctx.CloseProvisioners())
}
switch op { switch op {
case walkApply, walkDestroy: case walkApply, walkDestroy:
@ -188,10 +190,6 @@ func (n *nodeCloseModule) Execute(ctx EvalContext, op walkOperation) (diags tfdi
defer ctx.State().Unlock() defer ctx.State().Unlock()
for modKey, mod := range state.Modules { for modKey, mod := range state.Modules {
if !n.Addr.Equal(mod.Addr.Module()) {
continue
}
// clean out any empty resources // clean out any empty resources
for resKey, res := range mod.Resources { for resKey, res := range mod.Resources {
if len(res.Instances) == 0 { if len(res.Instances) == 0 {

View File

@ -47,6 +47,18 @@ func TestNodeCloseModuleExecute(t *testing.T) {
t.Fatalf("unexpected error: %s", diags.Err()) t.Fatalf("unexpected error: %s", diags.Err())
} }
// Since module.child has no resources, it should be removed
if _, ok := state.Modules["module.child"]; !ok {
t.Fatal("module.child should not be removed from state yet")
}
// the root module should do all the module cleanup
node = nodeCloseModule{addrs.RootModule}
diags = node.Execute(ctx, walkApply)
if diags.HasErrors() {
t.Fatalf("unexpected error: %s", diags.Err())
}
// Since module.child has no resources, it should be removed // Since module.child has no resources, it should be removed
if _, ok := state.Modules["module.child"]; ok { if _, ok := state.Modules["module.child"]; ok {
t.Fatal("module.child was not removed from state") t.Fatal("module.child was not removed from state")

View File

@ -103,6 +103,7 @@ func (n *nodeExpandOutput) DynamicExpand(ctx EvalContext) (*Graph, error) {
case module.IsRoot() && (n.PlanDestroy || n.ApplyDestroy): case module.IsRoot() && (n.PlanDestroy || n.ApplyDestroy):
node = &NodeDestroyableOutput{ node = &NodeDestroyableOutput{
Addr: absAddr, Addr: absAddr,
Planning: n.Planning,
} }
case n.PlanDestroy: case n.PlanDestroy:
@ -116,6 +117,7 @@ func (n *nodeExpandOutput) DynamicExpand(ctx EvalContext) (*Graph, error) {
Change: change, Change: change,
RefreshOnly: n.RefreshOnly, RefreshOnly: n.RefreshOnly,
DestroyApply: n.ApplyDestroy, DestroyApply: n.ApplyDestroy,
Planning: n.Planning,
} }
} }
@ -203,6 +205,8 @@ type NodeApplyableOutput struct {
// DestroyApply indicates that we are applying a destroy plan, and do not // DestroyApply indicates that we are applying a destroy plan, and do not
// need to account for conditional blocks. // need to account for conditional blocks.
DestroyApply bool DestroyApply bool
Planning bool
} }
var ( var (
@ -422,6 +426,7 @@ func (n *NodeApplyableOutput) DotNode(name string, opts *dag.DotOpts) *dag.DotNo
// its application will remove the output from the state. // its application will remove the output from the state.
type NodeDestroyableOutput struct { type NodeDestroyableOutput struct {
Addr addrs.AbsOutputValue Addr addrs.AbsOutputValue
Planning bool
} }
var ( var (
@ -468,7 +473,7 @@ func (n *NodeDestroyableOutput) Execute(ctx EvalContext, op walkOperation) tfdia
} }
changes := ctx.Changes() changes := ctx.Changes()
if changes != nil { if changes != nil && n.Planning {
change := &plans.OutputChange{ change := &plans.OutputChange{
Addr: n.Addr, Addr: n.Addr,
Sensitive: sensitiveBefore, Sensitive: sensitiveBefore,
@ -485,6 +490,7 @@ func (n *NodeDestroyableOutput) Execute(ctx EvalContext, op walkOperation) tfdia
panic(fmt.Sprintf("planned change for %s could not be encoded: %s", n.Addr, err)) panic(fmt.Sprintf("planned change for %s could not be encoded: %s", n.Addr, err))
} }
log.Printf("[TRACE] NodeDestroyableOutput: Saving %s change for %s in changeset", change.Action, n.Addr) log.Printf("[TRACE] NodeDestroyableOutput: Saving %s change for %s in changeset", change.Action, n.Addr)
changes.RemoveOutputChange(n.Addr) // remove any existing planned change, if present changes.RemoveOutputChange(n.Addr) // remove any existing planned change, if present
changes.AppendOutputChange(cs) // add the new planned change changes.AppendOutputChange(cs) // add the new planned change
} }
@ -509,7 +515,7 @@ func (n *NodeApplyableOutput) setValue(state *states.SyncState, changes *plans.C
// there and lookup the prior value in the state. This is used 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 // preference to the state where present, since it *is* able to represent
// unknowns, while the state cannot. // unknowns, while the state cannot.
if changes != nil { 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
sensitiveBefore := false sensitiveBefore := false
@ -575,18 +581,36 @@ func (n *NodeApplyableOutput) setValue(state *states.SyncState, changes *plans.C
panic(fmt.Sprintf("planned change for %s could not be encoded: %s", n.Addr, err)) panic(fmt.Sprintf("planned change for %s could not be encoded: %s", n.Addr, err))
} }
log.Printf("[TRACE] setValue: Saving %s change for %s in changeset", change.Action, n.Addr) log.Printf("[TRACE] setValue: Saving %s change for %s in changeset", change.Action, n.Addr)
changes.RemoveOutputChange(n.Addr) // remove any existing planned change, if present
changes.AppendOutputChange(cs) // add the new planned change changes.AppendOutputChange(cs) // add the new planned change
} }
if changes != nil && !n.Planning {
// During apply there is no longer any change to track, so we must
// ensure the state is updated and not overridden by a change.
changes.RemoveOutputChange(n.Addr)
}
if val.IsKnown() && !val.IsNull() { if val.IsKnown() && !val.IsNull() {
// The state itself doesn't represent unknown values, so we null them // 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 // out here and then we'll save the real unknown value in the planned
// changeset below, if we have one on this graph walk. // changeset below, 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)
unmarkedVal, _ := val.UnmarkDeep()
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) stateVal := cty.UnknownAsNull(unmarkedVal)
state.SetOutputValue(n.Addr, stateVal, n.Config.Sensitive) state.SetOutputValue(n.Addr, stateVal, sensitive)
} else { } else {
log.Printf("[TRACE] setValue: Removing %s from state (it is now null)", n.Addr) log.Printf("[TRACE] setValue: Removing %s from state (it is now null)", n.Addr)
state.RemoveOutputValue(n.Addr) state.RemoveOutputValue(n.Addr)

View File

@ -14,6 +14,7 @@ import (
type OrphanOutputTransformer struct { type OrphanOutputTransformer struct {
Config *configs.Config // Root of config tree Config *configs.Config // Root of config tree
State *states.State // State is the root state State *states.State // State is the root state
Planning bool
} }
func (t *OrphanOutputTransformer) Transform(g *Graph) error { func (t *OrphanOutputTransformer) Transform(g *Graph) error {
@ -53,6 +54,7 @@ func (t *OrphanOutputTransformer) transform(g *Graph, ms *states.Module) error {
g.Add(&NodeDestroyableOutput{ g.Add(&NodeDestroyableOutput{
Addr: addrs.OutputValue{Name: name}.Absolute(moduleAddr), Addr: addrs.OutputValue{Name: name}.Absolute(moduleAddr),
Planning: t.Planning,
}) })
} }