aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/terraform/helper/resource/testing_config.go
diff options
context:
space:
mode:
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.go334
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 @@
1package resource 1package resource
2 2
3import ( 3import (
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
21func testStep( 31func 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.
192func 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.
207func 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
167func testStepTaint(state *terraform.State, step TestStep) error { 391func 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()