7 "github.com/hashicorp/hcl2/hcl"
8 "github.com/zclconf/go-cty/cty"
10 "github.com/hashicorp/terraform/addrs"
11 "github.com/hashicorp/terraform/plans"
12 "github.com/hashicorp/terraform/states"
15 // EvalDeleteOutput is an EvalNode implementation that deletes an output
17 type EvalDeleteOutput struct {
18 Addr addrs.OutputValue
22 func (n *EvalDeleteOutput) Eval(ctx EvalContext) (interface{}, error) {
28 state.RemoveOutputValue(n.Addr.Absolute(ctx.Path()))
32 // EvalWriteOutput is an EvalNode implementation that writes the output
33 // for the given name to the current state.
34 type EvalWriteOutput struct {
35 Addr addrs.OutputValue
38 // ContinueOnErr allows interpolation to fail during Input
43 func (n *EvalWriteOutput) Eval(ctx EvalContext) (interface{}, error) {
44 addr := n.Addr.Absolute(ctx.Path())
46 // This has to run before we have a state lock, since evaluation also
48 val, diags := ctx.EvaluateExpr(n.Expr, cty.DynamicPseudoType, nil)
49 // We'll handle errors below, after we have loaded the module.
56 changes := ctx.Changes() // may be nil, if we're not working on a changeset
58 // handling the interpolation error
59 if diags.HasErrors() {
60 if n.ContinueOnErr || flagWarnOutputErrors {
61 log.Printf("[ERROR] Output interpolation %q failed: %s", n.Addr.Name, diags.Err())
62 // if we're continuing, make sure the output is included, and
63 // marked as unknown. If the evaluator was able to find a type
64 // for the value in spite of the error then we'll use it.
65 n.setValue(addr, state, changes, cty.UnknownVal(val.Type()))
66 return nil, EvalEarlyExitError{}
68 return nil, diags.Err()
71 n.setValue(addr, state, changes, val)
76 func (n *EvalWriteOutput) setValue(addr addrs.AbsOutputValue, state *states.SyncState, changes *plans.ChangesSync, val cty.Value) {
77 if val.IsKnown() && !val.IsNull() {
78 // The state itself doesn't represent unknown values, so we null them
79 // out here and then we'll save the real unknown value in the planned
80 // changeset below, if we have one on this graph walk.
81 log.Printf("[TRACE] EvalWriteOutput: Saving value for %s in state", addr)
82 stateVal := cty.UnknownAsNull(val)
83 state.SetOutputValue(addr, stateVal, n.Sensitive)
85 log.Printf("[TRACE] EvalWriteOutput: Removing %s from state (it is now null)", addr)
86 state.RemoveOutputValue(addr)
89 // If we also have an active changeset then we'll replicate the value in
90 // there. This is used in preference to the state where present, since it
91 // *is* able to represent unknowns, while the state cannot.
93 // For the moment we are not properly tracking changes to output
94 // values, and just marking them always as "Create" or "Destroy"
95 // actions. A future release will rework the output lifecycle so we
96 // can track their changes properly, in a similar way to how we work
97 // with resource instances.
99 var change *plans.OutputChange
101 change = &plans.OutputChange{
103 Sensitive: n.Sensitive,
104 Change: plans.Change{
105 Action: plans.Create,
106 Before: cty.NullVal(cty.DynamicPseudoType),
111 change = &plans.OutputChange{
113 Sensitive: n.Sensitive,
114 Change: plans.Change{
115 // This is just a weird placeholder delete action since
116 // we don't have an actual prior value to indicate.
117 // FIXME: Generate real planned changes for output values
118 // that include the old values.
119 Action: plans.Delete,
120 Before: cty.NullVal(cty.DynamicPseudoType),
121 After: cty.NullVal(cty.DynamicPseudoType),
126 cs, err := change.Encode()
128 // Should never happen, since we just constructed this right above
129 panic(fmt.Sprintf("planned change for %s could not be encoded: %s", addr, err))
131 log.Printf("[TRACE] EvalWriteOutput: Saving %s change for %s in changeset", change.Action, addr)
132 changes.RemoveOutputChange(addr) // remove any existing planned change, if present
133 changes.AppendOutputChange(cs) // add the new planned change