diff options
author | Nathan Dench <ndenc2@gmail.com> | 2019-05-24 15:16:44 +1000 |
---|---|---|
committer | Nathan Dench <ndenc2@gmail.com> | 2019-05-24 15:16:44 +1000 |
commit | 107c1cdb09c575aa2f61d97f48d8587eb6bada4c (patch) | |
tree | ca7d008643efc555c388baeaf1d986e0b6b3e28c /vendor/github.com/hashicorp/terraform/terraform/eval_diff.go | |
parent | 844b5a68d8af4791755b8f0ad293cc99f5959183 (diff) | |
download | terraform-provider-statuscake-107c1cdb09c575aa2f61d97f48d8587eb6bada4c.tar.gz terraform-provider-statuscake-107c1cdb09c575aa2f61d97f48d8587eb6bada4c.tar.zst terraform-provider-statuscake-107c1cdb09c575aa2f61d97f48d8587eb6bada4c.zip |
Upgrade to 0.12
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/terraform/eval_diff.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/terraform/eval_diff.go | 906 |
1 files changed, 678 insertions, 228 deletions
diff --git a/vendor/github.com/hashicorp/terraform/terraform/eval_diff.go b/vendor/github.com/hashicorp/terraform/terraform/eval_diff.go index 26205ce..b7acfb0 100644 --- a/vendor/github.com/hashicorp/terraform/terraform/eval_diff.go +++ b/vendor/github.com/hashicorp/terraform/terraform/eval_diff.go | |||
@@ -1,92 +1,114 @@ | |||
1 | package terraform | 1 | package terraform |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "bytes" | ||
4 | "fmt" | 5 | "fmt" |
5 | "log" | 6 | "log" |
7 | "reflect" | ||
6 | "strings" | 8 | "strings" |
7 | 9 | ||
8 | "github.com/hashicorp/terraform/config" | 10 | "github.com/hashicorp/hcl2/hcl" |
9 | "github.com/hashicorp/terraform/version" | 11 | "github.com/zclconf/go-cty/cty" |
12 | |||
13 | "github.com/hashicorp/terraform/addrs" | ||
14 | "github.com/hashicorp/terraform/configs" | ||
15 | "github.com/hashicorp/terraform/plans" | ||
16 | "github.com/hashicorp/terraform/plans/objchange" | ||
17 | "github.com/hashicorp/terraform/providers" | ||
18 | "github.com/hashicorp/terraform/states" | ||
19 | "github.com/hashicorp/terraform/tfdiags" | ||
10 | ) | 20 | ) |
11 | 21 | ||
12 | // EvalCompareDiff is an EvalNode implementation that compares two diffs | 22 | // EvalCheckPlannedChange is an EvalNode implementation that produces errors |
13 | // and errors if the diffs are not equal. | 23 | // if the _actual_ expected value is not compatible with what was recorded |
14 | type EvalCompareDiff struct { | 24 | // in the plan. |
15 | Info *InstanceInfo | 25 | // |
16 | One, Two **InstanceDiff | 26 | // Errors here are most often indicative of a bug in the provider, so our |
27 | // error messages will report with that in mind. It's also possible that | ||
28 | // there's a bug in Terraform's Core's own "proposed new value" code in | ||
29 | // EvalDiff. | ||
30 | type EvalCheckPlannedChange struct { | ||
31 | Addr addrs.ResourceInstance | ||
32 | ProviderAddr addrs.AbsProviderConfig | ||
33 | ProviderSchema **ProviderSchema | ||
34 | |||
35 | // We take ResourceInstanceChange objects here just because that's what's | ||
36 | // convenient to pass in from the evaltree implementation, but we really | ||
37 | // only look at the "After" value of each change. | ||
38 | Planned, Actual **plans.ResourceInstanceChange | ||
17 | } | 39 | } |
18 | 40 | ||
19 | // TODO: test | 41 | func (n *EvalCheckPlannedChange) Eval(ctx EvalContext) (interface{}, error) { |
20 | func (n *EvalCompareDiff) Eval(ctx EvalContext) (interface{}, error) { | 42 | providerSchema := *n.ProviderSchema |
21 | one, two := *n.One, *n.Two | 43 | plannedChange := *n.Planned |
22 | 44 | actualChange := *n.Actual | |
23 | // If either are nil, let them be empty | 45 | |
24 | if one == nil { | 46 | schema, _ := providerSchema.SchemaForResourceAddr(n.Addr.ContainingResource()) |
25 | one = new(InstanceDiff) | 47 | if schema == nil { |
26 | one.init() | 48 | // Should be caught during validation, so we don't bother with a pretty error here |
27 | } | 49 | return nil, fmt.Errorf("provider does not support %q", n.Addr.Resource.Type) |
28 | if two == nil { | 50 | } |
29 | two = new(InstanceDiff) | 51 | |
30 | two.init() | 52 | var diags tfdiags.Diagnostics |
31 | } | 53 | absAddr := n.Addr.Absolute(ctx.Path()) |
32 | oneId, _ := one.GetAttribute("id") | 54 | |
33 | twoId, _ := two.GetAttribute("id") | 55 | log.Printf("[TRACE] EvalCheckPlannedChange: Verifying that actual change (action %s) matches planned change (action %s)", actualChange.Action, plannedChange.Action) |
34 | one.DelAttribute("id") | 56 | |
35 | two.DelAttribute("id") | 57 | if plannedChange.Action != actualChange.Action { |
36 | defer func() { | 58 | switch { |
37 | if oneId != nil { | 59 | case plannedChange.Action == plans.Update && actualChange.Action == plans.NoOp: |
38 | one.SetAttribute("id", oneId) | 60 | // It's okay for an update to become a NoOp once we've filled in |
39 | } | 61 | // all of the unknown values, since the final values might actually |
40 | if twoId != nil { | 62 | // match what was there before after all. |
41 | two.SetAttribute("id", twoId) | 63 | log.Printf("[DEBUG] After incorporating new values learned so far during apply, %s change has become NoOp", absAddr) |
42 | } | 64 | default: |
43 | }() | 65 | diags = diags.Append(tfdiags.Sourceless( |
44 | 66 | tfdiags.Error, | |
45 | if same, reason := one.Same(two); !same { | 67 | "Provider produced inconsistent final plan", |
46 | log.Printf("[ERROR] %s: diffs didn't match", n.Info.Id) | 68 | fmt.Sprintf( |
47 | log.Printf("[ERROR] %s: reason: %s", n.Info.Id, reason) | 69 | "When expanding the plan for %s to include new values learned so far during apply, provider %q changed the planned action from %s to %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", |
48 | log.Printf("[ERROR] %s: diff one: %#v", n.Info.Id, one) | 70 | absAddr, n.ProviderAddr.ProviderConfig.Type, |
49 | log.Printf("[ERROR] %s: diff two: %#v", n.Info.Id, two) | 71 | plannedChange.Action, actualChange.Action, |
50 | return nil, fmt.Errorf( | 72 | ), |
51 | "%s: diffs didn't match during apply. This is a bug with "+ | 73 | )) |
52 | "Terraform and should be reported as a GitHub Issue.\n"+ | 74 | } |
53 | "\n"+ | ||
54 | "Please include the following information in your report:\n"+ | ||
55 | "\n"+ | ||
56 | " Terraform Version: %s\n"+ | ||
57 | " Resource ID: %s\n"+ | ||
58 | " Mismatch reason: %s\n"+ | ||
59 | " Diff One (usually from plan): %#v\n"+ | ||
60 | " Diff Two (usually from apply): %#v\n"+ | ||
61 | "\n"+ | ||
62 | "Also include as much context as you can about your config, state, "+ | ||
63 | "and the steps you performed to trigger this error.\n", | ||
64 | n.Info.Id, version.Version, n.Info.Id, reason, one, two) | ||
65 | } | 75 | } |
66 | 76 | ||
67 | return nil, nil | 77 | errs := objchange.AssertObjectCompatible(schema, plannedChange.After, actualChange.After) |
78 | for _, err := range errs { | ||
79 | diags = diags.Append(tfdiags.Sourceless( | ||
80 | tfdiags.Error, | ||
81 | "Provider produced inconsistent final plan", | ||
82 | fmt.Sprintf( | ||
83 | "When expanding the plan for %s to include new values learned so far during apply, provider %q produced an invalid new value for %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", | ||
84 | absAddr, n.ProviderAddr.ProviderConfig.Type, tfdiags.FormatError(err), | ||
85 | ), | ||
86 | )) | ||
87 | } | ||
88 | return nil, diags.Err() | ||
68 | } | 89 | } |
69 | 90 | ||
70 | // EvalDiff is an EvalNode implementation that does a refresh for | 91 | // EvalDiff is an EvalNode implementation that detects changes for a given |
71 | // a resource. | 92 | // resource instance. |
72 | type EvalDiff struct { | 93 | type EvalDiff struct { |
73 | Name string | 94 | Addr addrs.ResourceInstance |
74 | Info *InstanceInfo | 95 | Config *configs.Resource |
75 | Config **ResourceConfig | 96 | Provider *providers.Interface |
76 | Provider *ResourceProvider | 97 | ProviderAddr addrs.AbsProviderConfig |
77 | Diff **InstanceDiff | 98 | ProviderSchema **ProviderSchema |
78 | State **InstanceState | 99 | State **states.ResourceInstanceObject |
79 | OutputDiff **InstanceDiff | 100 | PreviousDiff **plans.ResourceInstanceChange |
80 | OutputState **InstanceState | 101 | |
81 | 102 | // CreateBeforeDestroy is set if either the resource's own config sets | |
82 | // Resource is needed to fetch the ignore_changes list so we can | 103 | // create_before_destroy explicitly or if dependencies have forced the |
83 | // filter user-requested ignored attributes from the diff. | 104 | // resource to be handled as create_before_destroy in order to avoid |
84 | Resource *config.Resource | 105 | // a dependency cycle. |
85 | 106 | CreateBeforeDestroy bool | |
86 | // Stub is used to flag the generated InstanceDiff as a stub. This is used to | 107 | |
87 | // ensure that the node exists to perform interpolations and generate | 108 | OutputChange **plans.ResourceInstanceChange |
88 | // computed paths off of, but not as an actual diff where resouces should be | 109 | OutputValue *cty.Value |
89 | // counted, and not as a diff that should be acted on. | 110 | OutputState **states.ResourceInstanceObject |
111 | |||
90 | Stub bool | 112 | Stub bool |
91 | } | 113 | } |
92 | 114 | ||
@@ -95,81 +117,303 @@ func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) { | |||
95 | state := *n.State | 117 | state := *n.State |
96 | config := *n.Config | 118 | config := *n.Config |
97 | provider := *n.Provider | 119 | provider := *n.Provider |
120 | providerSchema := *n.ProviderSchema | ||
121 | |||
122 | if providerSchema == nil { | ||
123 | return nil, fmt.Errorf("provider schema is unavailable for %s", n.Addr) | ||
124 | } | ||
125 | if n.ProviderAddr.ProviderConfig.Type == "" { | ||
126 | panic(fmt.Sprintf("EvalDiff for %s does not have ProviderAddr set", n.Addr.Absolute(ctx.Path()))) | ||
127 | } | ||
128 | |||
129 | var diags tfdiags.Diagnostics | ||
130 | |||
131 | // Evaluate the configuration | ||
132 | schema, _ := providerSchema.SchemaForResourceAddr(n.Addr.ContainingResource()) | ||
133 | if schema == nil { | ||
134 | // Should be caught during validation, so we don't bother with a pretty error here | ||
135 | return nil, fmt.Errorf("provider does not support resource type %q", n.Addr.Resource.Type) | ||
136 | } | ||
137 | keyData := EvalDataForInstanceKey(n.Addr.Key) | ||
138 | configVal, _, configDiags := ctx.EvaluateBlock(config.Config, schema, nil, keyData) | ||
139 | diags = diags.Append(configDiags) | ||
140 | if configDiags.HasErrors() { | ||
141 | return nil, diags.Err() | ||
142 | } | ||
143 | |||
144 | absAddr := n.Addr.Absolute(ctx.Path()) | ||
145 | var priorVal cty.Value | ||
146 | var priorValTainted cty.Value | ||
147 | var priorPrivate []byte | ||
148 | if state != nil { | ||
149 | if state.Status != states.ObjectTainted { | ||
150 | priorVal = state.Value | ||
151 | priorPrivate = state.Private | ||
152 | } else { | ||
153 | // If the prior state is tainted then we'll proceed below like | ||
154 | // we're creating an entirely new object, but then turn it into | ||
155 | // a synthetic "Replace" change at the end, creating the same | ||
156 | // result as if the provider had marked at least one argument | ||
157 | // change as "requires replacement". | ||
158 | priorValTainted = state.Value | ||
159 | priorVal = cty.NullVal(schema.ImpliedType()) | ||
160 | } | ||
161 | } else { | ||
162 | priorVal = cty.NullVal(schema.ImpliedType()) | ||
163 | } | ||
164 | |||
165 | proposedNewVal := objchange.ProposedNewObject(schema, priorVal, configVal) | ||
98 | 166 | ||
99 | // Call pre-diff hook | 167 | // Call pre-diff hook |
100 | if !n.Stub { | 168 | if !n.Stub { |
101 | err := ctx.Hook(func(h Hook) (HookAction, error) { | 169 | err := ctx.Hook(func(h Hook) (HookAction, error) { |
102 | return h.PreDiff(n.Info, state) | 170 | return h.PreDiff(absAddr, states.CurrentGen, priorVal, proposedNewVal) |
103 | }) | 171 | }) |
104 | if err != nil { | 172 | if err != nil { |
105 | return nil, err | 173 | return nil, err |
106 | } | 174 | } |
107 | } | 175 | } |
108 | 176 | ||
109 | // The state for the diff must never be nil | 177 | // The provider gets an opportunity to customize the proposed new value, |
110 | diffState := state | 178 | // which in turn produces the _planned_ new value. |
111 | if diffState == nil { | 179 | resp := provider.PlanResourceChange(providers.PlanResourceChangeRequest{ |
112 | diffState = new(InstanceState) | 180 | TypeName: n.Addr.Resource.Type, |
181 | Config: configVal, | ||
182 | PriorState: priorVal, | ||
183 | ProposedNewState: proposedNewVal, | ||
184 | PriorPrivate: priorPrivate, | ||
185 | }) | ||
186 | diags = diags.Append(resp.Diagnostics.InConfigBody(config.Config)) | ||
187 | if diags.HasErrors() { | ||
188 | return nil, diags.Err() | ||
189 | } | ||
190 | |||
191 | plannedNewVal := resp.PlannedState | ||
192 | plannedPrivate := resp.PlannedPrivate | ||
193 | |||
194 | if plannedNewVal == cty.NilVal { | ||
195 | // Should never happen. Since real-world providers return via RPC a nil | ||
196 | // is always a bug in the client-side stub. This is more likely caused | ||
197 | // by an incompletely-configured mock provider in tests, though. | ||
198 | panic(fmt.Sprintf("PlanResourceChange of %s produced nil value", absAddr.String())) | ||
199 | } | ||
200 | |||
201 | // We allow the planned new value to disagree with configuration _values_ | ||
202 | // here, since that allows the provider to do special logic like a | ||
203 | // DiffSuppressFunc, but we still require that the provider produces | ||
204 | // a value whose type conforms to the schema. | ||
205 | for _, err := range plannedNewVal.Type().TestConformance(schema.ImpliedType()) { | ||
206 | diags = diags.Append(tfdiags.Sourceless( | ||
207 | tfdiags.Error, | ||
208 | "Provider produced invalid plan", | ||
209 | fmt.Sprintf( | ||
210 | "Provider %q planned an invalid value for %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", | ||
211 | n.ProviderAddr.ProviderConfig.Type, tfdiags.FormatErrorPrefixed(err, absAddr.String()), | ||
212 | ), | ||
213 | )) | ||
214 | } | ||
215 | if diags.HasErrors() { | ||
216 | return nil, diags.Err() | ||
217 | } | ||
218 | |||
219 | if errs := objchange.AssertPlanValid(schema, priorVal, configVal, plannedNewVal); len(errs) > 0 { | ||
220 | if resp.LegacyTypeSystem { | ||
221 | // The shimming of the old type system in the legacy SDK is not precise | ||
222 | // enough to pass this consistency check, so we'll give it a pass here, | ||
223 | // but we will generate a warning about it so that we are more likely | ||
224 | // to notice in the logs if an inconsistency beyond the type system | ||
225 | // leads to a downstream provider failure. | ||
226 | var buf strings.Builder | ||
227 | fmt.Fprintf(&buf, "[WARN] Provider %q produced an invalid plan for %s, but we are tolerating it because it is using the legacy plugin SDK.\n The following problems may be the cause of any confusing errors from downstream operations:", n.ProviderAddr.ProviderConfig.Type, absAddr) | ||
228 | for _, err := range errs { | ||
229 | fmt.Fprintf(&buf, "\n - %s", tfdiags.FormatError(err)) | ||
230 | } | ||
231 | log.Print(buf.String()) | ||
232 | } else { | ||
233 | for _, err := range errs { | ||
234 | diags = diags.Append(tfdiags.Sourceless( | ||
235 | tfdiags.Error, | ||
236 | "Provider produced invalid plan", | ||
237 | fmt.Sprintf( | ||
238 | "Provider %q planned an invalid value for %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", | ||
239 | n.ProviderAddr.ProviderConfig.Type, tfdiags.FormatErrorPrefixed(err, absAddr.String()), | ||
240 | ), | ||
241 | )) | ||
242 | } | ||
243 | return nil, diags.Err() | ||
244 | } | ||
113 | } | 245 | } |
114 | diffState.init() | ||
115 | 246 | ||
116 | // Diff! | 247 | { |
117 | diff, err := provider.Diff(n.Info, diffState, config) | 248 | var moreDiags tfdiags.Diagnostics |
118 | if err != nil { | 249 | plannedNewVal, moreDiags = n.processIgnoreChanges(priorVal, plannedNewVal) |
119 | return nil, err | 250 | diags = diags.Append(moreDiags) |
120 | } | 251 | if moreDiags.HasErrors() { |
121 | if diff == nil { | 252 | return nil, diags.Err() |
122 | diff = new(InstanceDiff) | 253 | } |
123 | } | 254 | } |
124 | 255 | ||
125 | // Set DestroyDeposed if we have deposed instances | 256 | // The provider produces a list of paths to attributes whose changes mean |
126 | _, err = readInstanceFromState(ctx, n.Name, nil, func(rs *ResourceState) (*InstanceState, error) { | 257 | // that we must replace rather than update an existing remote object. |
127 | if len(rs.Deposed) > 0 { | 258 | // However, we only need to do that if the identified attributes _have_ |
128 | diff.DestroyDeposed = true | 259 | // actually changed -- particularly after we may have undone some of the |
129 | } | 260 | // changes in processIgnoreChanges -- so now we'll filter that list to |
261 | // include only where changes are detected. | ||
262 | reqRep := cty.NewPathSet() | ||
263 | if len(resp.RequiresReplace) > 0 { | ||
264 | for _, path := range resp.RequiresReplace { | ||
265 | if priorVal.IsNull() { | ||
266 | // If prior is null then we don't expect any RequiresReplace at all, | ||
267 | // because this is a Create action. | ||
268 | continue | ||
269 | } | ||
130 | 270 | ||
131 | return nil, nil | 271 | priorChangedVal, priorPathDiags := hcl.ApplyPath(priorVal, path, nil) |
132 | }) | 272 | plannedChangedVal, plannedPathDiags := hcl.ApplyPath(plannedNewVal, path, nil) |
133 | if err != nil { | 273 | if plannedPathDiags.HasErrors() && priorPathDiags.HasErrors() { |
134 | return nil, err | 274 | // This means the path was invalid in both the prior and new |
135 | } | 275 | // values, which is an error with the provider itself. |
276 | diags = diags.Append(tfdiags.Sourceless( | ||
277 | tfdiags.Error, | ||
278 | "Provider produced invalid plan", | ||
279 | fmt.Sprintf( | ||
280 | "Provider %q has indicated \"requires replacement\" on %s for a non-existent attribute path %#v.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", | ||
281 | n.ProviderAddr.ProviderConfig.Type, absAddr, path, | ||
282 | ), | ||
283 | )) | ||
284 | continue | ||
285 | } | ||
136 | 286 | ||
137 | // Preserve the DestroyTainted flag | 287 | // Make sure we have valid Values for both values. |
138 | if n.Diff != nil { | 288 | // Note: if the opposing value was of the type |
139 | diff.SetTainted((*n.Diff).GetDestroyTainted()) | 289 | // cty.DynamicPseudoType, the type assigned here may not exactly |
140 | } | 290 | // match the schema. This is fine here, since we're only going to |
291 | // check for equality, but if the NullVal is to be used, we need to | ||
292 | // check the schema for th true type. | ||
293 | switch { | ||
294 | case priorChangedVal == cty.NilVal && plannedChangedVal == cty.NilVal: | ||
295 | // this should never happen without ApplyPath errors above | ||
296 | panic("requires replace path returned 2 nil values") | ||
297 | case priorChangedVal == cty.NilVal: | ||
298 | priorChangedVal = cty.NullVal(plannedChangedVal.Type()) | ||
299 | case plannedChangedVal == cty.NilVal: | ||
300 | plannedChangedVal = cty.NullVal(priorChangedVal.Type()) | ||
301 | } | ||
141 | 302 | ||
142 | // Require a destroy if there is an ID and it requires new. | 303 | eqV := plannedChangedVal.Equals(priorChangedVal) |
143 | if diff.RequiresNew() && state != nil && state.ID != "" { | 304 | if !eqV.IsKnown() || eqV.False() { |
144 | diff.SetDestroy(true) | 305 | reqRep.Add(path) |
306 | } | ||
307 | } | ||
308 | if diags.HasErrors() { | ||
309 | return nil, diags.Err() | ||
310 | } | ||
145 | } | 311 | } |
146 | 312 | ||
147 | // If we're creating a new resource, compute its ID | 313 | eqV := plannedNewVal.Equals(priorVal) |
148 | if diff.RequiresNew() || state == nil || state.ID == "" { | 314 | eq := eqV.IsKnown() && eqV.True() |
149 | var oldID string | 315 | |
150 | if state != nil { | 316 | var action plans.Action |
151 | oldID = state.Attributes["id"] | 317 | switch { |
318 | case priorVal.IsNull(): | ||
319 | action = plans.Create | ||
320 | case eq: | ||
321 | action = plans.NoOp | ||
322 | case !reqRep.Empty(): | ||
323 | // If there are any "requires replace" paths left _after our filtering | ||
324 | // above_ then this is a replace action. | ||
325 | if n.CreateBeforeDestroy { | ||
326 | action = plans.CreateThenDelete | ||
327 | } else { | ||
328 | action = plans.DeleteThenCreate | ||
152 | } | 329 | } |
153 | 330 | default: | |
154 | // Add diff to compute new ID | 331 | action = plans.Update |
155 | diff.init() | 332 | // "Delete" is never chosen here, because deletion plans are always |
156 | diff.SetAttribute("id", &ResourceAttrDiff{ | 333 | // created more directly elsewhere, such as in "orphan" handling. |
157 | Old: oldID, | 334 | } |
158 | NewComputed: true, | 335 | |
159 | RequiresNew: true, | 336 | if action.IsReplace() { |
160 | Type: DiffAttrOutput, | 337 | // In this strange situation we want to produce a change object that |
338 | // shows our real prior object but has a _new_ object that is built | ||
339 | // from a null prior object, since we're going to delete the one | ||
340 | // that has all the computed values on it. | ||
341 | // | ||
342 | // Therefore we'll ask the provider to plan again here, giving it | ||
343 | // a null object for the prior, and then we'll meld that with the | ||
344 | // _actual_ prior state to produce a correctly-shaped replace change. | ||
345 | // The resulting change should show any computed attributes changing | ||
346 | // from known prior values to unknown values, unless the provider is | ||
347 | // able to predict new values for any of these computed attributes. | ||
348 | nullPriorVal := cty.NullVal(schema.ImpliedType()) | ||
349 | |||
350 | // create a new proposed value from the null state and the config | ||
351 | proposedNewVal = objchange.ProposedNewObject(schema, nullPriorVal, configVal) | ||
352 | |||
353 | resp = provider.PlanResourceChange(providers.PlanResourceChangeRequest{ | ||
354 | TypeName: n.Addr.Resource.Type, | ||
355 | Config: configVal, | ||
356 | PriorState: nullPriorVal, | ||
357 | ProposedNewState: proposedNewVal, | ||
358 | PriorPrivate: plannedPrivate, | ||
161 | }) | 359 | }) |
360 | // We need to tread carefully here, since if there are any warnings | ||
361 | // in here they probably also came out of our previous call to | ||
362 | // PlanResourceChange above, and so we don't want to repeat them. | ||
363 | // Consequently, we break from the usual pattern here and only | ||
364 | // append these new diagnostics if there's at least one error inside. | ||
365 | if resp.Diagnostics.HasErrors() { | ||
366 | diags = diags.Append(resp.Diagnostics.InConfigBody(config.Config)) | ||
367 | return nil, diags.Err() | ||
368 | } | ||
369 | plannedNewVal = resp.PlannedState | ||
370 | plannedPrivate = resp.PlannedPrivate | ||
371 | for _, err := range plannedNewVal.Type().TestConformance(schema.ImpliedType()) { | ||
372 | diags = diags.Append(tfdiags.Sourceless( | ||
373 | tfdiags.Error, | ||
374 | "Provider produced invalid plan", | ||
375 | fmt.Sprintf( | ||
376 | "Provider %q planned an invalid value for %s%s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", | ||
377 | n.ProviderAddr.ProviderConfig.Type, absAddr, tfdiags.FormatError(err), | ||
378 | ), | ||
379 | )) | ||
380 | } | ||
381 | if diags.HasErrors() { | ||
382 | return nil, diags.Err() | ||
383 | } | ||
162 | } | 384 | } |
163 | 385 | ||
164 | // filter out ignored resources | 386 | // If our prior value was tainted then we actually want this to appear |
165 | if err := n.processIgnoreChanges(diff); err != nil { | 387 | // as a replace change, even though so far we've been treating it as a |
166 | return nil, err | 388 | // create. |
389 | if action == plans.Create && priorValTainted != cty.NilVal { | ||
390 | if n.CreateBeforeDestroy { | ||
391 | action = plans.CreateThenDelete | ||
392 | } else { | ||
393 | action = plans.DeleteThenCreate | ||
394 | } | ||
395 | priorVal = priorValTainted | ||
396 | } | ||
397 | |||
398 | // As a special case, if we have a previous diff (presumably from the plan | ||
399 | // phases, whereas we're now in the apply phase) and it was for a replace, | ||
400 | // we've already deleted the original object from state by the time we | ||
401 | // get here and so we would've ended up with a _create_ action this time, | ||
402 | // which we now need to paper over to get a result consistent with what | ||
403 | // we originally intended. | ||
404 | if n.PreviousDiff != nil { | ||
405 | prevChange := *n.PreviousDiff | ||
406 | if prevChange.Action.IsReplace() && action == plans.Create { | ||
407 | log.Printf("[TRACE] EvalDiff: %s treating Create change as %s change to match with earlier plan", absAddr, prevChange.Action) | ||
408 | action = prevChange.Action | ||
409 | priorVal = prevChange.Before | ||
410 | } | ||
167 | } | 411 | } |
168 | 412 | ||
169 | // Call post-refresh hook | 413 | // Call post-refresh hook |
170 | if !n.Stub { | 414 | if !n.Stub { |
171 | err = ctx.Hook(func(h Hook) (HookAction, error) { | 415 | err := ctx.Hook(func(h Hook) (HookAction, error) { |
172 | return h.PostDiff(n.Info, diff) | 416 | return h.PostDiff(absAddr, states.CurrentGen, action, priorVal, plannedNewVal) |
173 | }) | 417 | }) |
174 | if err != nil { | 418 | if err != nil { |
175 | return nil, err | 419 | return nil, err |
@@ -177,30 +421,135 @@ func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) { | |||
177 | } | 421 | } |
178 | 422 | ||
179 | // Update our output if we care | 423 | // Update our output if we care |
180 | if n.OutputDiff != nil { | 424 | if n.OutputChange != nil { |
181 | *n.OutputDiff = diff | 425 | *n.OutputChange = &plans.ResourceInstanceChange{ |
426 | Addr: absAddr, | ||
427 | Private: plannedPrivate, | ||
428 | ProviderAddr: n.ProviderAddr, | ||
429 | Change: plans.Change{ | ||
430 | Action: action, | ||
431 | Before: priorVal, | ||
432 | After: plannedNewVal, | ||
433 | }, | ||
434 | RequiredReplace: reqRep, | ||
435 | } | ||
436 | } | ||
437 | |||
438 | if n.OutputValue != nil { | ||
439 | *n.OutputValue = configVal | ||
182 | } | 440 | } |
183 | 441 | ||
184 | // Update the state if we care | 442 | // Update the state if we care |
185 | if n.OutputState != nil { | 443 | if n.OutputState != nil { |
186 | *n.OutputState = state | 444 | *n.OutputState = &states.ResourceInstanceObject{ |
187 | 445 | // We use the special "planned" status here to note that this | |
188 | // Merge our state so that the state is updated with our plan | 446 | // object's value is not yet complete. Objects with this status |
189 | if !diff.Empty() && n.OutputState != nil { | 447 | // cannot be used during expression evaluation, so the caller |
190 | *n.OutputState = state.MergeDiff(diff) | 448 | // must _also_ record the returned change in the active plan, |
449 | // which the expression evaluator will use in preference to this | ||
450 | // incomplete value recorded in the state. | ||
451 | Status: states.ObjectPlanned, | ||
452 | Value: plannedNewVal, | ||
191 | } | 453 | } |
192 | } | 454 | } |
193 | 455 | ||
194 | return nil, nil | 456 | return nil, nil |
195 | } | 457 | } |
196 | 458 | ||
197 | func (n *EvalDiff) processIgnoreChanges(diff *InstanceDiff) error { | 459 | func (n *EvalDiff) processIgnoreChanges(prior, proposed cty.Value) (cty.Value, tfdiags.Diagnostics) { |
198 | if diff == nil || n.Resource == nil || n.Resource.Id() == "" { | 460 | // ignore_changes only applies when an object already exists, since we |
461 | // can't ignore changes to a thing we've not created yet. | ||
462 | if prior.IsNull() { | ||
463 | return proposed, nil | ||
464 | } | ||
465 | |||
466 | ignoreChanges := n.Config.Managed.IgnoreChanges | ||
467 | ignoreAll := n.Config.Managed.IgnoreAllChanges | ||
468 | |||
469 | if len(ignoreChanges) == 0 && !ignoreAll { | ||
470 | return proposed, nil | ||
471 | } | ||
472 | if ignoreAll { | ||
473 | return prior, nil | ||
474 | } | ||
475 | if prior.IsNull() || proposed.IsNull() { | ||
476 | // Ignore changes doesn't apply when we're creating for the first time. | ||
477 | // Proposed should never be null here, but if it is then we'll just let it be. | ||
478 | return proposed, nil | ||
479 | } | ||
480 | |||
481 | return processIgnoreChangesIndividual(prior, proposed, ignoreChanges) | ||
482 | } | ||
483 | |||
484 | func processIgnoreChangesIndividual(prior, proposed cty.Value, ignoreChanges []hcl.Traversal) (cty.Value, tfdiags.Diagnostics) { | ||
485 | // When we walk below we will be using cty.Path values for comparison, so | ||
486 | // we'll convert our traversals here so we can compare more easily. | ||
487 | ignoreChangesPath := make([]cty.Path, len(ignoreChanges)) | ||
488 | for i, traversal := range ignoreChanges { | ||
489 | path := make(cty.Path, len(traversal)) | ||
490 | for si, step := range traversal { | ||
491 | switch ts := step.(type) { | ||
492 | case hcl.TraverseRoot: | ||
493 | path[si] = cty.GetAttrStep{ | ||
494 | Name: ts.Name, | ||
495 | } | ||
496 | case hcl.TraverseAttr: | ||
497 | path[si] = cty.GetAttrStep{ | ||
498 | Name: ts.Name, | ||
499 | } | ||
500 | case hcl.TraverseIndex: | ||
501 | path[si] = cty.IndexStep{ | ||
502 | Key: ts.Key, | ||
503 | } | ||
504 | default: | ||
505 | panic(fmt.Sprintf("unsupported traversal step %#v", step)) | ||
506 | } | ||
507 | } | ||
508 | ignoreChangesPath[i] = path | ||
509 | } | ||
510 | |||
511 | var diags tfdiags.Diagnostics | ||
512 | ret, _ := cty.Transform(proposed, func(path cty.Path, v cty.Value) (cty.Value, error) { | ||
513 | // First we must see if this is a path that's being ignored at all. | ||
514 | // We're looking for an exact match here because this walk will visit | ||
515 | // leaf values first and then their containers, and we want to do | ||
516 | // the "ignore" transform once we reach the point indicated, throwing | ||
517 | // away any deeper values we already produced at that point. | ||
518 | var ignoreTraversal hcl.Traversal | ||
519 | for i, candidate := range ignoreChangesPath { | ||
520 | if reflect.DeepEqual(path, candidate) { | ||
521 | ignoreTraversal = ignoreChanges[i] | ||
522 | } | ||
523 | } | ||
524 | if ignoreTraversal == nil { | ||
525 | return v, nil | ||
526 | } | ||
527 | |||
528 | // If we're able to follow the same path through the prior value, | ||
529 | // we'll take the value there instead, effectively undoing the | ||
530 | // change that was planned. | ||
531 | priorV, diags := hcl.ApplyPath(prior, path, nil) | ||
532 | if diags.HasErrors() { | ||
533 | // We just ignore the errors and move on here, since we assume it's | ||
534 | // just because the prior value was a slightly-different shape. | ||
535 | // It could potentially also be that the traversal doesn't match | ||
536 | // the schema, but we should've caught that during the validate | ||
537 | // walk if so. | ||
538 | return v, nil | ||
539 | } | ||
540 | return priorV, nil | ||
541 | }) | ||
542 | return ret, diags | ||
543 | } | ||
544 | |||
545 | func (n *EvalDiff) processIgnoreChangesOld(diff *InstanceDiff) error { | ||
546 | if diff == nil || n.Config == nil || n.Config.Managed == nil { | ||
199 | return nil | 547 | return nil |
200 | } | 548 | } |
201 | ignoreChanges := n.Resource.Lifecycle.IgnoreChanges | 549 | ignoreChanges := n.Config.Managed.IgnoreChanges |
550 | ignoreAll := n.Config.Managed.IgnoreAllChanges | ||
202 | 551 | ||
203 | if len(ignoreChanges) == 0 { | 552 | if len(ignoreChanges) == 0 && !ignoreAll { |
204 | return nil | 553 | return nil |
205 | } | 554 | } |
206 | 555 | ||
@@ -220,9 +569,14 @@ func (n *EvalDiff) processIgnoreChanges(diff *InstanceDiff) error { | |||
220 | 569 | ||
221 | // get the complete set of keys we want to ignore | 570 | // get the complete set of keys we want to ignore |
222 | ignorableAttrKeys := make(map[string]bool) | 571 | ignorableAttrKeys := make(map[string]bool) |
223 | for _, ignoredKey := range ignoreChanges { | 572 | for k := range attrs { |
224 | for k := range attrs { | 573 | if ignoreAll { |
225 | if ignoredKey == "*" || strings.HasPrefix(k, ignoredKey) { | 574 | ignorableAttrKeys[k] = true |
575 | continue | ||
576 | } | ||
577 | for _, ignoredTraversal := range ignoreChanges { | ||
578 | ignoredKey := legacyFlatmapKeyForTraversal(ignoredTraversal) | ||
579 | if k == ignoredKey || strings.HasPrefix(k, ignoredKey+".") { | ||
226 | ignorableAttrKeys[k] = true | 580 | ignorableAttrKeys[k] = true |
227 | } | 581 | } |
228 | } | 582 | } |
@@ -285,14 +639,56 @@ func (n *EvalDiff) processIgnoreChanges(diff *InstanceDiff) error { | |||
285 | 639 | ||
286 | // If we didn't hit any of our early exit conditions, we can filter the diff. | 640 | // If we didn't hit any of our early exit conditions, we can filter the diff. |
287 | for k := range ignorableAttrKeys { | 641 | for k := range ignorableAttrKeys { |
288 | log.Printf("[DEBUG] [EvalIgnoreChanges] %s - Ignoring diff attribute: %s", | 642 | log.Printf("[DEBUG] [EvalIgnoreChanges] %s: Ignoring diff attribute: %s", n.Addr.String(), k) |
289 | n.Resource.Id(), k) | ||
290 | diff.DelAttribute(k) | 643 | diff.DelAttribute(k) |
291 | } | 644 | } |
292 | 645 | ||
293 | return nil | 646 | return nil |
294 | } | 647 | } |
295 | 648 | ||
649 | // legacyFlagmapKeyForTraversal constructs a key string compatible with what | ||
650 | // the flatmap package would generate for an attribute addressable by the given | ||
651 | // traversal. | ||
652 | // | ||
653 | // This is used only to shim references to attributes within the diff and | ||
654 | // state structures, which have not (at the time of writing) yet been updated | ||
655 | // to use the newer HCL-based representations. | ||
656 | func legacyFlatmapKeyForTraversal(traversal hcl.Traversal) string { | ||
657 | var buf bytes.Buffer | ||
658 | first := true | ||
659 | for _, step := range traversal { | ||
660 | if !first { | ||
661 | buf.WriteByte('.') | ||
662 | } | ||
663 | switch ts := step.(type) { | ||
664 | case hcl.TraverseRoot: | ||
665 | buf.WriteString(ts.Name) | ||
666 | case hcl.TraverseAttr: | ||
667 | buf.WriteString(ts.Name) | ||
668 | case hcl.TraverseIndex: | ||
669 | val := ts.Key | ||
670 | switch val.Type() { | ||
671 | case cty.Number: | ||
672 | bf := val.AsBigFloat() | ||
673 | buf.WriteString(bf.String()) | ||
674 | case cty.String: | ||
675 | s := val.AsString() | ||
676 | buf.WriteString(s) | ||
677 | default: | ||
678 | // should never happen, since no other types appear in | ||
679 | // traversals in practice. | ||
680 | buf.WriteByte('?') | ||
681 | } | ||
682 | default: | ||
683 | // should never happen, since we've covered all of the types | ||
684 | // that show up in parsed traversals in practice. | ||
685 | buf.WriteByte('?') | ||
686 | } | ||
687 | first = false | ||
688 | } | ||
689 | return buf.String() | ||
690 | } | ||
691 | |||
296 | // a group of key-*ResourceAttrDiff pairs from the same flatmapped container | 692 | // a group of key-*ResourceAttrDiff pairs from the same flatmapped container |
297 | type flatAttrDiff map[string]*ResourceAttrDiff | 693 | type flatAttrDiff map[string]*ResourceAttrDiff |
298 | 694 | ||
@@ -343,159 +739,213 @@ func groupContainers(d *InstanceDiff) map[string]flatAttrDiff { | |||
343 | // EvalDiffDestroy is an EvalNode implementation that returns a plain | 739 | // EvalDiffDestroy is an EvalNode implementation that returns a plain |
344 | // destroy diff. | 740 | // destroy diff. |
345 | type EvalDiffDestroy struct { | 741 | type EvalDiffDestroy struct { |
346 | Info *InstanceInfo | 742 | Addr addrs.ResourceInstance |
347 | State **InstanceState | 743 | DeposedKey states.DeposedKey |
348 | Output **InstanceDiff | 744 | State **states.ResourceInstanceObject |
745 | ProviderAddr addrs.AbsProviderConfig | ||
746 | |||
747 | Output **plans.ResourceInstanceChange | ||
748 | OutputState **states.ResourceInstanceObject | ||
349 | } | 749 | } |
350 | 750 | ||
351 | // TODO: test | 751 | // TODO: test |
352 | func (n *EvalDiffDestroy) Eval(ctx EvalContext) (interface{}, error) { | 752 | func (n *EvalDiffDestroy) Eval(ctx EvalContext) (interface{}, error) { |
753 | absAddr := n.Addr.Absolute(ctx.Path()) | ||
353 | state := *n.State | 754 | state := *n.State |
354 | 755 | ||
355 | // If there is no state or we don't have an ID, we're already destroyed | 756 | if n.ProviderAddr.ProviderConfig.Type == "" { |
356 | if state == nil || state.ID == "" { | 757 | if n.DeposedKey == "" { |
758 | panic(fmt.Sprintf("EvalDiffDestroy for %s does not have ProviderAddr set", absAddr)) | ||
759 | } else { | ||
760 | panic(fmt.Sprintf("EvalDiffDestroy for %s (deposed %s) does not have ProviderAddr set", absAddr, n.DeposedKey)) | ||
761 | } | ||
762 | } | ||
763 | |||
764 | // If there is no state or our attributes object is null then we're already | ||
765 | // destroyed. | ||
766 | if state == nil || state.Value.IsNull() { | ||
357 | return nil, nil | 767 | return nil, nil |
358 | } | 768 | } |
359 | 769 | ||
360 | // Call pre-diff hook | 770 | // Call pre-diff hook |
361 | err := ctx.Hook(func(h Hook) (HookAction, error) { | 771 | err := ctx.Hook(func(h Hook) (HookAction, error) { |
362 | return h.PreDiff(n.Info, state) | 772 | return h.PreDiff( |
773 | absAddr, n.DeposedKey.Generation(), | ||
774 | state.Value, | ||
775 | cty.NullVal(cty.DynamicPseudoType), | ||
776 | ) | ||
363 | }) | 777 | }) |
364 | if err != nil { | 778 | if err != nil { |
365 | return nil, err | 779 | return nil, err |
366 | } | 780 | } |
367 | 781 | ||
368 | // The diff | 782 | // Change is always the same for a destroy. We don't need the provider's |
369 | diff := &InstanceDiff{Destroy: true} | 783 | // help for this one. |
784 | // TODO: Should we give the provider an opportunity to veto this? | ||
785 | change := &plans.ResourceInstanceChange{ | ||
786 | Addr: absAddr, | ||
787 | DeposedKey: n.DeposedKey, | ||
788 | Change: plans.Change{ | ||
789 | Action: plans.Delete, | ||
790 | Before: state.Value, | ||
791 | After: cty.NullVal(cty.DynamicPseudoType), | ||
792 | }, | ||
793 | ProviderAddr: n.ProviderAddr, | ||
794 | } | ||
370 | 795 | ||
371 | // Call post-diff hook | 796 | // Call post-diff hook |
372 | err = ctx.Hook(func(h Hook) (HookAction, error) { | 797 | err = ctx.Hook(func(h Hook) (HookAction, error) { |
373 | return h.PostDiff(n.Info, diff) | 798 | return h.PostDiff( |
799 | absAddr, | ||
800 | n.DeposedKey.Generation(), | ||
801 | change.Action, | ||
802 | change.Before, | ||
803 | change.After, | ||
804 | ) | ||
374 | }) | 805 | }) |
375 | if err != nil { | 806 | if err != nil { |
376 | return nil, err | 807 | return nil, err |
377 | } | 808 | } |
378 | 809 | ||
379 | // Update our output | 810 | // Update our output |
380 | *n.Output = diff | 811 | *n.Output = change |
381 | |||
382 | return nil, nil | ||
383 | } | ||
384 | |||
385 | // EvalDiffDestroyModule is an EvalNode implementation that writes the diff to | ||
386 | // the full diff. | ||
387 | type EvalDiffDestroyModule struct { | ||
388 | Path []string | ||
389 | } | ||
390 | |||
391 | // TODO: test | ||
392 | func (n *EvalDiffDestroyModule) Eval(ctx EvalContext) (interface{}, error) { | ||
393 | diff, lock := ctx.Diff() | ||
394 | |||
395 | // Acquire the lock so that we can do this safely concurrently | ||
396 | lock.Lock() | ||
397 | defer lock.Unlock() | ||
398 | 812 | ||
399 | // Write the diff | 813 | if n.OutputState != nil { |
400 | modDiff := diff.ModuleByPath(n.Path) | 814 | // Record our proposed new state, which is nil because we're destroying. |
401 | if modDiff == nil { | 815 | *n.OutputState = nil |
402 | modDiff = diff.AddModule(n.Path) | ||
403 | } | 816 | } |
404 | modDiff.Destroy = true | ||
405 | 817 | ||
406 | return nil, nil | 818 | return nil, nil |
407 | } | 819 | } |
408 | 820 | ||
409 | // EvalFilterDiff is an EvalNode implementation that filters the diff | 821 | // EvalReduceDiff is an EvalNode implementation that takes a planned resource |
410 | // according to some filter. | 822 | // instance change as might be produced by EvalDiff or EvalDiffDestroy and |
411 | type EvalFilterDiff struct { | 823 | // "simplifies" it to a single atomic action to be performed by a specific |
412 | // Input and output | 824 | // graph node. |
413 | Diff **InstanceDiff | 825 | // |
414 | Output **InstanceDiff | 826 | // Callers must specify whether they are a destroy node or a regular apply |
415 | 827 | // node. If the result is NoOp then the given change requires no action for | |
416 | // Destroy, if true, will only include a destroy diff if it is set. | 828 | // the specific graph node calling this and so evaluation of the that graph |
417 | Destroy bool | 829 | // node should exit early and take no action. |
830 | // | ||
831 | // The object written to OutChange may either be identical to InChange or | ||
832 | // a new change object derived from InChange. Because of the former case, the | ||
833 | // caller must not mutate the object returned in OutChange. | ||
834 | type EvalReduceDiff struct { | ||
835 | Addr addrs.ResourceInstance | ||
836 | InChange **plans.ResourceInstanceChange | ||
837 | Destroy bool | ||
838 | OutChange **plans.ResourceInstanceChange | ||
418 | } | 839 | } |
419 | 840 | ||
420 | func (n *EvalFilterDiff) Eval(ctx EvalContext) (interface{}, error) { | 841 | // TODO: test |
421 | if *n.Diff == nil { | 842 | func (n *EvalReduceDiff) Eval(ctx EvalContext) (interface{}, error) { |
422 | return nil, nil | 843 | in := *n.InChange |
423 | } | 844 | out := in.Simplify(n.Destroy) |
424 | 845 | if n.OutChange != nil { | |
425 | input := *n.Diff | 846 | *n.OutChange = out |
426 | result := new(InstanceDiff) | 847 | } |
427 | 848 | if out.Action != in.Action { | |
428 | if n.Destroy { | 849 | if n.Destroy { |
429 | if input.GetDestroy() || input.RequiresNew() { | 850 | log.Printf("[TRACE] EvalReduceDiff: %s change simplified from %s to %s for destroy node", n.Addr, in.Action, out.Action) |
430 | result.SetDestroy(true) | 851 | } else { |
852 | log.Printf("[TRACE] EvalReduceDiff: %s change simplified from %s to %s for apply node", n.Addr, in.Action, out.Action) | ||
431 | } | 853 | } |
432 | } | 854 | } |
433 | |||
434 | if n.Output != nil { | ||
435 | *n.Output = result | ||
436 | } | ||
437 | |||
438 | return nil, nil | 855 | return nil, nil |
439 | } | 856 | } |
440 | 857 | ||
441 | // EvalReadDiff is an EvalNode implementation that writes the diff to | 858 | // EvalReadDiff is an EvalNode implementation that retrieves the planned |
442 | // the full diff. | 859 | // change for a particular resource instance object. |
443 | type EvalReadDiff struct { | 860 | type EvalReadDiff struct { |
444 | Name string | 861 | Addr addrs.ResourceInstance |
445 | Diff **InstanceDiff | 862 | DeposedKey states.DeposedKey |
863 | ProviderSchema **ProviderSchema | ||
864 | Change **plans.ResourceInstanceChange | ||
446 | } | 865 | } |
447 | 866 | ||
448 | func (n *EvalReadDiff) Eval(ctx EvalContext) (interface{}, error) { | 867 | func (n *EvalReadDiff) Eval(ctx EvalContext) (interface{}, error) { |
449 | diff, lock := ctx.Diff() | 868 | providerSchema := *n.ProviderSchema |
869 | changes := ctx.Changes() | ||
870 | addr := n.Addr.Absolute(ctx.Path()) | ||
450 | 871 | ||
451 | // Acquire the lock so that we can do this safely concurrently | 872 | schema, _ := providerSchema.SchemaForResourceAddr(n.Addr.ContainingResource()) |
452 | lock.Lock() | 873 | if schema == nil { |
453 | defer lock.Unlock() | 874 | // Should be caught during validation, so we don't bother with a pretty error here |
875 | return nil, fmt.Errorf("provider does not support resource type %q", n.Addr.Resource.Type) | ||
876 | } | ||
454 | 877 | ||
455 | // Write the diff | 878 | gen := states.CurrentGen |
456 | modDiff := diff.ModuleByPath(ctx.Path()) | 879 | if n.DeposedKey != states.NotDeposed { |
457 | if modDiff == nil { | 880 | gen = n.DeposedKey |
881 | } | ||
882 | csrc := changes.GetResourceInstanceChange(addr, gen) | ||
883 | if csrc == nil { | ||
884 | log.Printf("[TRACE] EvalReadDiff: No planned change recorded for %s", addr) | ||
458 | return nil, nil | 885 | return nil, nil |
459 | } | 886 | } |
460 | 887 | ||
461 | *n.Diff = modDiff.Resources[n.Name] | 888 | change, err := csrc.Decode(schema.ImpliedType()) |
889 | if err != nil { | ||
890 | return nil, fmt.Errorf("failed to decode planned changes for %s: %s", addr, err) | ||
891 | } | ||
892 | if n.Change != nil { | ||
893 | *n.Change = change | ||
894 | } | ||
895 | |||
896 | log.Printf("[TRACE] EvalReadDiff: Read %s change from plan for %s", change.Action, addr) | ||
462 | 897 | ||
463 | return nil, nil | 898 | return nil, nil |
464 | } | 899 | } |
465 | 900 | ||
466 | // EvalWriteDiff is an EvalNode implementation that writes the diff to | 901 | // EvalWriteDiff is an EvalNode implementation that saves a planned change |
467 | // the full diff. | 902 | // for an instance object into the set of global planned changes. |
468 | type EvalWriteDiff struct { | 903 | type EvalWriteDiff struct { |
469 | Name string | 904 | Addr addrs.ResourceInstance |
470 | Diff **InstanceDiff | 905 | DeposedKey states.DeposedKey |
906 | ProviderSchema **ProviderSchema | ||
907 | Change **plans.ResourceInstanceChange | ||
471 | } | 908 | } |
472 | 909 | ||
473 | // TODO: test | 910 | // TODO: test |
474 | func (n *EvalWriteDiff) Eval(ctx EvalContext) (interface{}, error) { | 911 | func (n *EvalWriteDiff) Eval(ctx EvalContext) (interface{}, error) { |
475 | diff, lock := ctx.Diff() | 912 | changes := ctx.Changes() |
476 | 913 | addr := n.Addr.Absolute(ctx.Path()) | |
477 | // The diff to write, if its empty it should write nil | 914 | if n.Change == nil || *n.Change == nil { |
478 | var diffVal *InstanceDiff | 915 | // Caller sets nil to indicate that we need to remove a change from |
479 | if n.Diff != nil { | 916 | // the set of changes. |
480 | diffVal = *n.Diff | 917 | gen := states.CurrentGen |
918 | if n.DeposedKey != states.NotDeposed { | ||
919 | gen = n.DeposedKey | ||
920 | } | ||
921 | changes.RemoveResourceInstanceChange(addr, gen) | ||
922 | return nil, nil | ||
481 | } | 923 | } |
482 | if diffVal.Empty() { | 924 | |
483 | diffVal = nil | 925 | providerSchema := *n.ProviderSchema |
926 | change := *n.Change | ||
927 | |||
928 | if change.Addr.String() != addr.String() || change.DeposedKey != n.DeposedKey { | ||
929 | // Should never happen, and indicates a bug in the caller. | ||
930 | panic("inconsistent address and/or deposed key in EvalWriteDiff") | ||
484 | } | 931 | } |
485 | 932 | ||
486 | // Acquire the lock so that we can do this safely concurrently | 933 | schema, _ := providerSchema.SchemaForResourceAddr(n.Addr.ContainingResource()) |
487 | lock.Lock() | 934 | if schema == nil { |
488 | defer lock.Unlock() | 935 | // Should be caught during validation, so we don't bother with a pretty error here |
936 | return nil, fmt.Errorf("provider does not support resource type %q", n.Addr.Resource.Type) | ||
937 | } | ||
489 | 938 | ||
490 | // Write the diff | 939 | csrc, err := change.Encode(schema.ImpliedType()) |
491 | modDiff := diff.ModuleByPath(ctx.Path()) | 940 | if err != nil { |
492 | if modDiff == nil { | 941 | return nil, fmt.Errorf("failed to encode planned changes for %s: %s", addr, err) |
493 | modDiff = diff.AddModule(ctx.Path()) | ||
494 | } | 942 | } |
495 | if diffVal != nil { | 943 | |
496 | modDiff.Resources[n.Name] = diffVal | 944 | changes.AppendResourceInstanceChange(csrc) |
945 | if n.DeposedKey == states.NotDeposed { | ||
946 | log.Printf("[TRACE] EvalWriteDiff: recorded %s change for %s", change.Action, addr) | ||
497 | } else { | 947 | } else { |
498 | delete(modDiff.Resources, n.Name) | 948 | log.Printf("[TRACE] EvalWriteDiff: recorded %s change for %s deposed object %s", change.Action, addr, n.DeposedKey) |
499 | } | 949 | } |
500 | 950 | ||
501 | return nil, nil | 951 | return nil, nil |