diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/helper/resource/testing_config.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/helper/resource/testing_config.go | 334 |
1 files changed, 279 insertions, 55 deletions
diff --git a/vendor/github.com/hashicorp/terraform/helper/resource/testing_config.go b/vendor/github.com/hashicorp/terraform/helper/resource/testing_config.go index 033f126..311fdb6 100644 --- a/vendor/github.com/hashicorp/terraform/helper/resource/testing_config.go +++ b/vendor/github.com/hashicorp/terraform/helper/resource/testing_config.go | |||
@@ -1,13 +1,23 @@ | |||
1 | package resource | 1 | package resource |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "bufio" | ||
5 | "bytes" | ||
4 | "errors" | 6 | "errors" |
5 | "fmt" | 7 | "fmt" |
6 | "log" | 8 | "log" |
9 | "sort" | ||
7 | "strings" | 10 | "strings" |
8 | 11 | ||
12 | "github.com/hashicorp/terraform/addrs" | ||
13 | "github.com/hashicorp/terraform/config" | ||
14 | "github.com/hashicorp/terraform/config/hcl2shim" | ||
15 | "github.com/hashicorp/terraform/states" | ||
16 | |||
9 | "github.com/hashicorp/errwrap" | 17 | "github.com/hashicorp/errwrap" |
18 | "github.com/hashicorp/terraform/plans" | ||
10 | "github.com/hashicorp/terraform/terraform" | 19 | "github.com/hashicorp/terraform/terraform" |
20 | "github.com/hashicorp/terraform/tfdiags" | ||
11 | ) | 21 | ) |
12 | 22 | ||
13 | // testStepConfig runs a config-mode test step | 23 | // testStepConfig runs a config-mode test step |
@@ -18,69 +28,79 @@ func testStepConfig( | |||
18 | return testStep(opts, state, step) | 28 | return testStep(opts, state, step) |
19 | } | 29 | } |
20 | 30 | ||
21 | func testStep( | 31 | func testStep(opts terraform.ContextOpts, state *terraform.State, step TestStep) (*terraform.State, error) { |
22 | opts terraform.ContextOpts, | ||
23 | state *terraform.State, | ||
24 | step TestStep) (*terraform.State, error) { | ||
25 | // Pre-taint any resources that have been defined in Taint, as long as this | ||
26 | // is not a destroy step. | ||
27 | if !step.Destroy { | 32 | if !step.Destroy { |
28 | if err := testStepTaint(state, step); err != nil { | 33 | if err := testStepTaint(state, step); err != nil { |
29 | return state, err | 34 | return state, err |
30 | } | 35 | } |
31 | } | 36 | } |
32 | 37 | ||
33 | mod, err := testModule(opts, step) | 38 | cfg, err := testConfig(opts, step) |
34 | if err != nil { | 39 | if err != nil { |
35 | return state, err | 40 | return state, err |
36 | } | 41 | } |
37 | 42 | ||
43 | var stepDiags tfdiags.Diagnostics | ||
44 | |||
38 | // Build the context | 45 | // Build the context |
39 | opts.Module = mod | 46 | opts.Config = cfg |
40 | opts.State = state | 47 | opts.State, err = terraform.ShimLegacyState(state) |
41 | opts.Destroy = step.Destroy | ||
42 | ctx, err := terraform.NewContext(&opts) | ||
43 | if err != nil { | 48 | if err != nil { |
44 | return state, fmt.Errorf("Error initializing context: %s", err) | 49 | return nil, err |
50 | } | ||
51 | |||
52 | opts.Destroy = step.Destroy | ||
53 | ctx, stepDiags := terraform.NewContext(&opts) | ||
54 | if stepDiags.HasErrors() { | ||
55 | return state, fmt.Errorf("Error initializing context: %s", stepDiags.Err()) | ||
45 | } | 56 | } |
46 | if diags := ctx.Validate(); len(diags) > 0 { | 57 | if stepDiags := ctx.Validate(); len(stepDiags) > 0 { |
47 | if diags.HasErrors() { | 58 | if stepDiags.HasErrors() { |
48 | return nil, errwrap.Wrapf("config is invalid: {{err}}", diags.Err()) | 59 | return state, errwrap.Wrapf("config is invalid: {{err}}", stepDiags.Err()) |
49 | } | 60 | } |
50 | 61 | ||
51 | log.Printf("[WARN] Config warnings:\n%s", diags) | 62 | log.Printf("[WARN] Config warnings:\n%s", stepDiags) |
52 | } | 63 | } |
53 | 64 | ||
54 | // Refresh! | 65 | // Refresh! |
55 | state, err = ctx.Refresh() | 66 | newState, stepDiags := ctx.Refresh() |
67 | // shim the state first so the test can check the state on errors | ||
68 | |||
69 | state, err = shimNewState(newState, step.providers) | ||
56 | if err != nil { | 70 | if err != nil { |
57 | return state, fmt.Errorf( | 71 | return nil, err |
58 | "Error refreshing: %s", err) | 72 | } |
73 | if stepDiags.HasErrors() { | ||
74 | return state, newOperationError("refresh", stepDiags) | ||
59 | } | 75 | } |
60 | 76 | ||
61 | // If this step is a PlanOnly step, skip over this first Plan and subsequent | 77 | // If this step is a PlanOnly step, skip over this first Plan and subsequent |
62 | // Apply, and use the follow up Plan that checks for perpetual diffs | 78 | // Apply, and use the follow up Plan that checks for perpetual diffs |
63 | if !step.PlanOnly { | 79 | if !step.PlanOnly { |
64 | // Plan! | 80 | // Plan! |
65 | if p, err := ctx.Plan(); err != nil { | 81 | if p, stepDiags := ctx.Plan(); stepDiags.HasErrors() { |
66 | return state, fmt.Errorf( | 82 | return state, newOperationError("plan", stepDiags) |
67 | "Error planning: %s", err) | ||
68 | } else { | 83 | } else { |
69 | log.Printf("[WARN] Test: Step plan: %s", p) | 84 | log.Printf("[WARN] Test: Step plan: %s", legacyPlanComparisonString(newState, p.Changes)) |
70 | } | 85 | } |
71 | 86 | ||
72 | // We need to keep a copy of the state prior to destroying | 87 | // We need to keep a copy of the state prior to destroying |
73 | // such that destroy steps can verify their behaviour in the check | 88 | // such that destroy steps can verify their behavior in the check |
74 | // function | 89 | // function |
75 | stateBeforeApplication := state.DeepCopy() | 90 | stateBeforeApplication := state.DeepCopy() |
76 | 91 | ||
77 | // Apply! | 92 | // Apply the diff, creating real resources. |
78 | state, err = ctx.Apply() | 93 | newState, stepDiags = ctx.Apply() |
94 | // shim the state first so the test can check the state on errors | ||
95 | state, err = shimNewState(newState, step.providers) | ||
79 | if err != nil { | 96 | if err != nil { |
80 | return state, fmt.Errorf("Error applying: %s", err) | 97 | return nil, err |
98 | } | ||
99 | if stepDiags.HasErrors() { | ||
100 | return state, newOperationError("apply", stepDiags) | ||
81 | } | 101 | } |
82 | 102 | ||
83 | // Check! Excitement! | 103 | // Run any configured checks |
84 | if step.Check != nil { | 104 | if step.Check != nil { |
85 | if step.Destroy { | 105 | if step.Destroy { |
86 | if err := step.Check(stateBeforeApplication); err != nil { | 106 | if err := step.Check(stateBeforeApplication); err != nil { |
@@ -96,31 +116,35 @@ func testStep( | |||
96 | 116 | ||
97 | // Now, verify that Plan is now empty and we don't have a perpetual diff issue | 117 | // Now, verify that Plan is now empty and we don't have a perpetual diff issue |
98 | // We do this with TWO plans. One without a refresh. | 118 | // We do this with TWO plans. One without a refresh. |
99 | var p *terraform.Plan | 119 | var p *plans.Plan |
100 | if p, err = ctx.Plan(); err != nil { | 120 | if p, stepDiags = ctx.Plan(); stepDiags.HasErrors() { |
101 | return state, fmt.Errorf("Error on follow-up plan: %s", err) | 121 | return state, newOperationError("follow-up plan", stepDiags) |
102 | } | 122 | } |
103 | if p.Diff != nil && !p.Diff.Empty() { | 123 | if !p.Changes.Empty() { |
104 | if step.ExpectNonEmptyPlan { | 124 | if step.ExpectNonEmptyPlan { |
105 | log.Printf("[INFO] Got non-empty plan, as expected:\n\n%s", p) | 125 | log.Printf("[INFO] Got non-empty plan, as expected:\n\n%s", legacyPlanComparisonString(newState, p.Changes)) |
106 | } else { | 126 | } else { |
107 | return state, fmt.Errorf( | 127 | return state, fmt.Errorf( |
108 | "After applying this step, the plan was not empty:\n\n%s", p) | 128 | "After applying this step, the plan was not empty:\n\n%s", legacyPlanComparisonString(newState, p.Changes)) |
109 | } | 129 | } |
110 | } | 130 | } |
111 | 131 | ||
112 | // And another after a Refresh. | 132 | // And another after a Refresh. |
113 | if !step.Destroy || (step.Destroy && !step.PreventPostDestroyRefresh) { | 133 | if !step.Destroy || (step.Destroy && !step.PreventPostDestroyRefresh) { |
114 | state, err = ctx.Refresh() | 134 | newState, stepDiags = ctx.Refresh() |
135 | if stepDiags.HasErrors() { | ||
136 | return state, newOperationError("follow-up refresh", stepDiags) | ||
137 | } | ||
138 | |||
139 | state, err = shimNewState(newState, step.providers) | ||
115 | if err != nil { | 140 | if err != nil { |
116 | return state, fmt.Errorf( | 141 | return nil, err |
117 | "Error on follow-up refresh: %s", err) | ||
118 | } | 142 | } |
119 | } | 143 | } |
120 | if p, err = ctx.Plan(); err != nil { | 144 | if p, stepDiags = ctx.Plan(); stepDiags.HasErrors() { |
121 | return state, fmt.Errorf("Error on second follow-up plan: %s", err) | 145 | return state, newOperationError("second follow-up refresh", stepDiags) |
122 | } | 146 | } |
123 | empty := p.Diff == nil || p.Diff.Empty() | 147 | empty := p.Changes.Empty() |
124 | 148 | ||
125 | // Data resources are tricky because they legitimately get instantiated | 149 | // Data resources are tricky because they legitimately get instantiated |
126 | // during refresh so that they will be already populated during the | 150 | // during refresh so that they will be already populated during the |
@@ -128,35 +152,28 @@ func testStep( | |||
128 | // config we'll end up wanting to destroy them again here. This is | 152 | // config we'll end up wanting to destroy them again here. This is |
129 | // acceptable and expected, and we'll treat it as "empty" for the | 153 | // acceptable and expected, and we'll treat it as "empty" for the |
130 | // sake of this testing. | 154 | // sake of this testing. |
131 | if step.Destroy { | 155 | if step.Destroy && !empty { |
132 | empty = true | 156 | empty = true |
133 | 157 | for _, change := range p.Changes.Resources { | |
134 | for _, moduleDiff := range p.Diff.Modules { | 158 | if change.Addr.Resource.Resource.Mode != addrs.DataResourceMode { |
135 | for k, instanceDiff := range moduleDiff.Resources { | 159 | empty = false |
136 | if !strings.HasPrefix(k, "data.") { | 160 | break |
137 | empty = false | ||
138 | break | ||
139 | } | ||
140 | |||
141 | if !instanceDiff.Destroy { | ||
142 | empty = false | ||
143 | } | ||
144 | } | 161 | } |
145 | } | 162 | } |
146 | } | 163 | } |
147 | 164 | ||
148 | if !empty { | 165 | if !empty { |
149 | if step.ExpectNonEmptyPlan { | 166 | if step.ExpectNonEmptyPlan { |
150 | log.Printf("[INFO] Got non-empty plan, as expected:\n\n%s", p) | 167 | log.Printf("[INFO] Got non-empty plan, as expected:\n\n%s", legacyPlanComparisonString(newState, p.Changes)) |
151 | } else { | 168 | } else { |
152 | return state, fmt.Errorf( | 169 | return state, fmt.Errorf( |
153 | "After applying this step and refreshing, "+ | 170 | "After applying this step and refreshing, "+ |
154 | "the plan was not empty:\n\n%s", p) | 171 | "the plan was not empty:\n\n%s", legacyPlanComparisonString(newState, p.Changes)) |
155 | } | 172 | } |
156 | } | 173 | } |
157 | 174 | ||
158 | // Made it here, but expected a non-empty plan, fail! | 175 | // Made it here, but expected a non-empty plan, fail! |
159 | if step.ExpectNonEmptyPlan && (p.Diff == nil || p.Diff.Empty()) { | 176 | if step.ExpectNonEmptyPlan && empty { |
160 | return state, fmt.Errorf("Expected a non-empty plan, but got an empty plan!") | 177 | return state, fmt.Errorf("Expected a non-empty plan, but got an empty plan!") |
161 | } | 178 | } |
162 | 179 | ||
@@ -164,6 +181,213 @@ func testStep( | |||
164 | return state, nil | 181 | return state, nil |
165 | } | 182 | } |
166 | 183 | ||
184 | // legacyPlanComparisonString produces a string representation of the changes | ||
185 | // from a plan and a given state togther, as was formerly produced by the | ||
186 | // String method of terraform.Plan. | ||
187 | // | ||
188 | // This is here only for compatibility with existing tests that predate our | ||
189 | // new plan and state types, and should not be used in new tests. Instead, use | ||
190 | // a library like "cmp" to do a deep equality and diff on the two | ||
191 | // data structures. | ||
192 | func legacyPlanComparisonString(state *states.State, changes *plans.Changes) string { | ||
193 | return fmt.Sprintf( | ||
194 | "DIFF:\n\n%s\n\nSTATE:\n\n%s", | ||
195 | legacyDiffComparisonString(changes), | ||
196 | state.String(), | ||
197 | ) | ||
198 | } | ||
199 | |||
200 | // legacyDiffComparisonString produces a string representation of the changes | ||
201 | // from a planned changes object, as was formerly produced by the String method | ||
202 | // of terraform.Diff. | ||
203 | // | ||
204 | // This is here only for compatibility with existing tests that predate our | ||
205 | // new plan types, and should not be used in new tests. Instead, use a library | ||
206 | // like "cmp" to do a deep equality check and diff on the two data structures. | ||
207 | func legacyDiffComparisonString(changes *plans.Changes) string { | ||
208 | // The old string representation of a plan was grouped by module, but | ||
209 | // our new plan structure is not grouped in that way and so we'll need | ||
210 | // to preprocess it in order to produce that grouping. | ||
211 | type ResourceChanges struct { | ||
212 | Current *plans.ResourceInstanceChangeSrc | ||
213 | Deposed map[states.DeposedKey]*plans.ResourceInstanceChangeSrc | ||
214 | } | ||
215 | byModule := map[string]map[string]*ResourceChanges{} | ||
216 | resourceKeys := map[string][]string{} | ||
217 | requiresReplace := map[string][]string{} | ||
218 | var moduleKeys []string | ||
219 | for _, rc := range changes.Resources { | ||
220 | if rc.Action == plans.NoOp { | ||
221 | // We won't mention no-op changes here at all, since the old plan | ||
222 | // model we are emulating here didn't have such a concept. | ||
223 | continue | ||
224 | } | ||
225 | moduleKey := rc.Addr.Module.String() | ||
226 | if _, exists := byModule[moduleKey]; !exists { | ||
227 | moduleKeys = append(moduleKeys, moduleKey) | ||
228 | byModule[moduleKey] = make(map[string]*ResourceChanges) | ||
229 | } | ||
230 | resourceKey := rc.Addr.Resource.String() | ||
231 | if _, exists := byModule[moduleKey][resourceKey]; !exists { | ||
232 | resourceKeys[moduleKey] = append(resourceKeys[moduleKey], resourceKey) | ||
233 | byModule[moduleKey][resourceKey] = &ResourceChanges{ | ||
234 | Deposed: make(map[states.DeposedKey]*plans.ResourceInstanceChangeSrc), | ||
235 | } | ||
236 | } | ||
237 | |||
238 | if rc.DeposedKey == states.NotDeposed { | ||
239 | byModule[moduleKey][resourceKey].Current = rc | ||
240 | } else { | ||
241 | byModule[moduleKey][resourceKey].Deposed[rc.DeposedKey] = rc | ||
242 | } | ||
243 | |||
244 | rr := []string{} | ||
245 | for _, p := range rc.RequiredReplace.List() { | ||
246 | rr = append(rr, hcl2shim.FlatmapKeyFromPath(p)) | ||
247 | } | ||
248 | requiresReplace[resourceKey] = rr | ||
249 | } | ||
250 | sort.Strings(moduleKeys) | ||
251 | for _, ks := range resourceKeys { | ||
252 | sort.Strings(ks) | ||
253 | } | ||
254 | |||
255 | var buf bytes.Buffer | ||
256 | |||
257 | for _, moduleKey := range moduleKeys { | ||
258 | rcs := byModule[moduleKey] | ||
259 | var mBuf bytes.Buffer | ||
260 | |||
261 | for _, resourceKey := range resourceKeys[moduleKey] { | ||
262 | rc := rcs[resourceKey] | ||
263 | |||
264 | forceNewAttrs := requiresReplace[resourceKey] | ||
265 | |||
266 | crud := "UPDATE" | ||
267 | if rc.Current != nil { | ||
268 | switch rc.Current.Action { | ||
269 | case plans.DeleteThenCreate: | ||
270 | crud = "DESTROY/CREATE" | ||
271 | case plans.CreateThenDelete: | ||
272 | crud = "CREATE/DESTROY" | ||
273 | case plans.Delete: | ||
274 | crud = "DESTROY" | ||
275 | case plans.Create: | ||
276 | crud = "CREATE" | ||
277 | } | ||
278 | } else { | ||
279 | // We must be working on a deposed object then, in which | ||
280 | // case destroying is the only possible action. | ||
281 | crud = "DESTROY" | ||
282 | } | ||
283 | |||
284 | extra := "" | ||
285 | if rc.Current == nil && len(rc.Deposed) > 0 { | ||
286 | extra = " (deposed only)" | ||
287 | } | ||
288 | |||
289 | fmt.Fprintf( | ||
290 | &mBuf, "%s: %s%s\n", | ||
291 | crud, resourceKey, extra, | ||
292 | ) | ||
293 | |||
294 | attrNames := map[string]bool{} | ||
295 | var oldAttrs map[string]string | ||
296 | var newAttrs map[string]string | ||
297 | if rc.Current != nil { | ||
298 | if before := rc.Current.Before; before != nil { | ||
299 | ty, err := before.ImpliedType() | ||
300 | if err == nil { | ||
301 | val, err := before.Decode(ty) | ||
302 | if err == nil { | ||
303 | oldAttrs = hcl2shim.FlatmapValueFromHCL2(val) | ||
304 | for k := range oldAttrs { | ||
305 | attrNames[k] = true | ||
306 | } | ||
307 | } | ||
308 | } | ||
309 | } | ||
310 | if after := rc.Current.After; after != nil { | ||
311 | ty, err := after.ImpliedType() | ||
312 | if err == nil { | ||
313 | val, err := after.Decode(ty) | ||
314 | if err == nil { | ||
315 | newAttrs = hcl2shim.FlatmapValueFromHCL2(val) | ||
316 | for k := range newAttrs { | ||
317 | attrNames[k] = true | ||
318 | } | ||
319 | } | ||
320 | } | ||
321 | } | ||
322 | } | ||
323 | if oldAttrs == nil { | ||
324 | oldAttrs = make(map[string]string) | ||
325 | } | ||
326 | if newAttrs == nil { | ||
327 | newAttrs = make(map[string]string) | ||
328 | } | ||
329 | |||
330 | attrNamesOrder := make([]string, 0, len(attrNames)) | ||
331 | keyLen := 0 | ||
332 | for n := range attrNames { | ||
333 | attrNamesOrder = append(attrNamesOrder, n) | ||
334 | if len(n) > keyLen { | ||
335 | keyLen = len(n) | ||
336 | } | ||
337 | } | ||
338 | sort.Strings(attrNamesOrder) | ||
339 | |||
340 | for _, attrK := range attrNamesOrder { | ||
341 | v := newAttrs[attrK] | ||
342 | u := oldAttrs[attrK] | ||
343 | |||
344 | if v == config.UnknownVariableValue { | ||
345 | v = "<computed>" | ||
346 | } | ||
347 | // NOTE: we don't support <sensitive> here because we would | ||
348 | // need schema to do that. Excluding sensitive values | ||
349 | // is now done at the UI layer, and so should not be tested | ||
350 | // at the core layer. | ||
351 | |||
352 | updateMsg := "" | ||
353 | |||
354 | // This may not be as precise as in the old diff, as it matches | ||
355 | // everything under the attribute that was originally marked as | ||
356 | // ForceNew, but should help make it easier to determine what | ||
357 | // caused replacement here. | ||
358 | for _, k := range forceNewAttrs { | ||
359 | if strings.HasPrefix(attrK, k) { | ||
360 | updateMsg = " (forces new resource)" | ||
361 | break | ||
362 | } | ||
363 | } | ||
364 | |||
365 | fmt.Fprintf( | ||
366 | &mBuf, " %s:%s %#v => %#v%s\n", | ||
367 | attrK, | ||
368 | strings.Repeat(" ", keyLen-len(attrK)), | ||
369 | u, v, | ||
370 | updateMsg, | ||
371 | ) | ||
372 | } | ||
373 | } | ||
374 | |||
375 | if moduleKey == "" { // root module | ||
376 | buf.Write(mBuf.Bytes()) | ||
377 | buf.WriteByte('\n') | ||
378 | continue | ||
379 | } | ||
380 | |||
381 | fmt.Fprintf(&buf, "%s:\n", moduleKey) | ||
382 | s := bufio.NewScanner(&mBuf) | ||
383 | for s.Scan() { | ||
384 | buf.WriteString(fmt.Sprintf(" %s\n", s.Text())) | ||
385 | } | ||
386 | } | ||
387 | |||
388 | return buf.String() | ||
389 | } | ||
390 | |||
167 | func testStepTaint(state *terraform.State, step TestStep) error { | 391 | func testStepTaint(state *terraform.State, step TestStep) error { |
168 | for _, p := range step.Taint { | 392 | for _, p := range step.Taint { |
169 | m := state.RootModule() | 393 | m := state.RootModule() |