]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package terraform |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | "log" | |
6 | ||
107c1cdb ND |
7 | "github.com/hashicorp/hcl2/hcl" |
8 | "github.com/zclconf/go-cty/cty" | |
9 | ||
10 | "github.com/hashicorp/terraform/addrs" | |
11 | "github.com/hashicorp/terraform/plans" | |
12 | "github.com/hashicorp/terraform/states" | |
bae9f6d2 JC |
13 | ) |
14 | ||
15 | // EvalDeleteOutput is an EvalNode implementation that deletes an output | |
16 | // from the state. | |
17 | type EvalDeleteOutput struct { | |
107c1cdb | 18 | Addr addrs.OutputValue |
bae9f6d2 JC |
19 | } |
20 | ||
21 | // TODO: test | |
22 | func (n *EvalDeleteOutput) Eval(ctx EvalContext) (interface{}, error) { | |
107c1cdb | 23 | state := ctx.State() |
bae9f6d2 JC |
24 | if state == nil { |
25 | return nil, nil | |
26 | } | |
27 | ||
107c1cdb | 28 | state.RemoveOutputValue(n.Addr.Absolute(ctx.Path())) |
bae9f6d2 JC |
29 | return nil, nil |
30 | } | |
31 | ||
32 | // EvalWriteOutput is an EvalNode implementation that writes the output | |
33 | // for the given name to the current state. | |
34 | type EvalWriteOutput struct { | |
107c1cdb | 35 | Addr addrs.OutputValue |
bae9f6d2 | 36 | Sensitive bool |
107c1cdb | 37 | Expr hcl.Expression |
15c0b25d AP |
38 | // ContinueOnErr allows interpolation to fail during Input |
39 | ContinueOnErr bool | |
bae9f6d2 JC |
40 | } |
41 | ||
42 | // TODO: test | |
43 | func (n *EvalWriteOutput) Eval(ctx EvalContext) (interface{}, error) { | |
107c1cdb ND |
44 | addr := n.Addr.Absolute(ctx.Path()) |
45 | ||
46 | // This has to run before we have a state lock, since evaluation also | |
15c0b25d | 47 | // reads the state |
107c1cdb ND |
48 | val, diags := ctx.EvaluateExpr(n.Expr, cty.DynamicPseudoType, nil) |
49 | // We'll handle errors below, after we have loaded the module. | |
bae9f6d2 | 50 | |
107c1cdb | 51 | state := ctx.State() |
bae9f6d2 | 52 | if state == nil { |
107c1cdb | 53 | return nil, nil |
bae9f6d2 JC |
54 | } |
55 | ||
107c1cdb | 56 | changes := ctx.Changes() // may be nil, if we're not working on a changeset |
bae9f6d2 | 57 | |
15c0b25d | 58 | // handling the interpolation error |
107c1cdb | 59 | if diags.HasErrors() { |
15c0b25d | 60 | if n.ContinueOnErr || flagWarnOutputErrors { |
107c1cdb | 61 | log.Printf("[ERROR] Output interpolation %q failed: %s", n.Addr.Name, diags.Err()) |
15c0b25d | 62 | // if we're continuing, make sure the output is included, and |
107c1cdb ND |
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())) | |
15c0b25d AP |
66 | return nil, EvalEarlyExitError{} |
67 | } | |
107c1cdb | 68 | return nil, diags.Err() |
15c0b25d AP |
69 | } |
70 | ||
107c1cdb ND |
71 | n.setValue(addr, state, changes, val) |
72 | ||
73 | return nil, nil | |
74 | } | |
75 | ||
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) | |
84 | } else { | |
85 | log.Printf("[TRACE] EvalWriteOutput: Removing %s from state (it is now null)", addr) | |
86 | state.RemoveOutputValue(addr) | |
bae9f6d2 JC |
87 | } |
88 | ||
107c1cdb ND |
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. | |
92 | if changes != nil { | |
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. | |
98 | ||
99 | var change *plans.OutputChange | |
100 | if !val.IsNull() { | |
101 | change = &plans.OutputChange{ | |
102 | Addr: addr, | |
bae9f6d2 | 103 | Sensitive: n.Sensitive, |
107c1cdb ND |
104 | Change: plans.Change{ |
105 | Action: plans.Create, | |
106 | Before: cty.NullVal(cty.DynamicPseudoType), | |
107 | After: val, | |
108 | }, | |
109 | } | |
110 | } else { | |
111 | change = &plans.OutputChange{ | |
112 | Addr: addr, | |
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), | |
122 | }, | |
bae9f6d2 | 123 | } |
bae9f6d2 | 124 | } |
bae9f6d2 | 125 | |
107c1cdb ND |
126 | cs, err := change.Encode() |
127 | if err != nil { | |
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)) | |
130 | } | |
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 | |
134 | } | |
bae9f6d2 | 135 | } |