diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/helper/resource/testing.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/helper/resource/testing.go | 790 |
1 files changed, 790 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/helper/resource/testing.go b/vendor/github.com/hashicorp/terraform/helper/resource/testing.go new file mode 100644 index 0000000..04367c5 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/resource/testing.go | |||
@@ -0,0 +1,790 @@ | |||
1 | package resource | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "io" | ||
6 | "io/ioutil" | ||
7 | "log" | ||
8 | "os" | ||
9 | "path/filepath" | ||
10 | "reflect" | ||
11 | "regexp" | ||
12 | "strings" | ||
13 | "testing" | ||
14 | |||
15 | "github.com/davecgh/go-spew/spew" | ||
16 | "github.com/hashicorp/go-getter" | ||
17 | "github.com/hashicorp/go-multierror" | ||
18 | "github.com/hashicorp/terraform/config/module" | ||
19 | "github.com/hashicorp/terraform/helper/logging" | ||
20 | "github.com/hashicorp/terraform/terraform" | ||
21 | ) | ||
22 | |||
23 | const TestEnvVar = "TF_ACC" | ||
24 | |||
25 | // TestProvider can be implemented by any ResourceProvider to provide custom | ||
26 | // reset functionality at the start of an acceptance test. | ||
27 | // The helper/schema Provider implements this interface. | ||
28 | type TestProvider interface { | ||
29 | TestReset() error | ||
30 | } | ||
31 | |||
32 | // TestCheckFunc is the callback type used with acceptance tests to check | ||
33 | // the state of a resource. The state passed in is the latest state known, | ||
34 | // or in the case of being after a destroy, it is the last known state when | ||
35 | // it was created. | ||
36 | type TestCheckFunc func(*terraform.State) error | ||
37 | |||
38 | // ImportStateCheckFunc is the check function for ImportState tests | ||
39 | type ImportStateCheckFunc func([]*terraform.InstanceState) error | ||
40 | |||
41 | // TestCase is a single acceptance test case used to test the apply/destroy | ||
42 | // lifecycle of a resource in a specific configuration. | ||
43 | // | ||
44 | // When the destroy plan is executed, the config from the last TestStep | ||
45 | // is used to plan it. | ||
46 | type TestCase struct { | ||
47 | // IsUnitTest allows a test to run regardless of the TF_ACC | ||
48 | // environment variable. This should be used with care - only for | ||
49 | // fast tests on local resources (e.g. remote state with a local | ||
50 | // backend) but can be used to increase confidence in correct | ||
51 | // operation of Terraform without waiting for a full acctest run. | ||
52 | IsUnitTest bool | ||
53 | |||
54 | // PreCheck, if non-nil, will be called before any test steps are | ||
55 | // executed. It will only be executed in the case that the steps | ||
56 | // would run, so it can be used for some validation before running | ||
57 | // acceptance tests, such as verifying that keys are setup. | ||
58 | PreCheck func() | ||
59 | |||
60 | // Providers is the ResourceProvider that will be under test. | ||
61 | // | ||
62 | // Alternately, ProviderFactories can be specified for the providers | ||
63 | // that are valid. This takes priority over Providers. | ||
64 | // | ||
65 | // The end effect of each is the same: specifying the providers that | ||
66 | // are used within the tests. | ||
67 | Providers map[string]terraform.ResourceProvider | ||
68 | ProviderFactories map[string]terraform.ResourceProviderFactory | ||
69 | |||
70 | // PreventPostDestroyRefresh can be set to true for cases where data sources | ||
71 | // are tested alongside real resources | ||
72 | PreventPostDestroyRefresh bool | ||
73 | |||
74 | // CheckDestroy is called after the resource is finally destroyed | ||
75 | // to allow the tester to test that the resource is truly gone. | ||
76 | CheckDestroy TestCheckFunc | ||
77 | |||
78 | // Steps are the apply sequences done within the context of the | ||
79 | // same state. Each step can have its own check to verify correctness. | ||
80 | Steps []TestStep | ||
81 | |||
82 | // The settings below control the "ID-only refresh test." This is | ||
83 | // an enabled-by-default test that tests that a refresh can be | ||
84 | // refreshed with only an ID to result in the same attributes. | ||
85 | // This validates completeness of Refresh. | ||
86 | // | ||
87 | // IDRefreshName is the name of the resource to check. This will | ||
88 | // default to the first non-nil primary resource in the state. | ||
89 | // | ||
90 | // IDRefreshIgnore is a list of configuration keys that will be ignored. | ||
91 | IDRefreshName string | ||
92 | IDRefreshIgnore []string | ||
93 | } | ||
94 | |||
95 | // TestStep is a single apply sequence of a test, done within the | ||
96 | // context of a state. | ||
97 | // | ||
98 | // Multiple TestSteps can be sequenced in a Test to allow testing | ||
99 | // potentially complex update logic. In general, simply create/destroy | ||
100 | // tests will only need one step. | ||
101 | type TestStep struct { | ||
102 | // ResourceName should be set to the name of the resource | ||
103 | // that is being tested. Example: "aws_instance.foo". Various test | ||
104 | // modes use this to auto-detect state information. | ||
105 | // | ||
106 | // This is only required if the test mode settings below say it is | ||
107 | // for the mode you're using. | ||
108 | ResourceName string | ||
109 | |||
110 | // PreConfig is called before the Config is applied to perform any per-step | ||
111 | // setup that needs to happen. This is called regardless of "test mode" | ||
112 | // below. | ||
113 | PreConfig func() | ||
114 | |||
115 | //--------------------------------------------------------------- | ||
116 | // Test modes. One of the following groups of settings must be | ||
117 | // set to determine what the test step will do. Ideally we would've | ||
118 | // used Go interfaces here but there are now hundreds of tests we don't | ||
119 | // want to re-type so instead we just determine which step logic | ||
120 | // to run based on what settings below are set. | ||
121 | //--------------------------------------------------------------- | ||
122 | |||
123 | //--------------------------------------------------------------- | ||
124 | // Plan, Apply testing | ||
125 | //--------------------------------------------------------------- | ||
126 | |||
127 | // Config a string of the configuration to give to Terraform. If this | ||
128 | // is set, then the TestCase will execute this step with the same logic | ||
129 | // as a `terraform apply`. | ||
130 | Config string | ||
131 | |||
132 | // Check is called after the Config is applied. Use this step to | ||
133 | // make your own API calls to check the status of things, and to | ||
134 | // inspect the format of the ResourceState itself. | ||
135 | // | ||
136 | // If an error is returned, the test will fail. In this case, a | ||
137 | // destroy plan will still be attempted. | ||
138 | // | ||
139 | // If this is nil, no check is done on this step. | ||
140 | Check TestCheckFunc | ||
141 | |||
142 | // Destroy will create a destroy plan if set to true. | ||
143 | Destroy bool | ||
144 | |||
145 | // ExpectNonEmptyPlan can be set to true for specific types of tests that are | ||
146 | // looking to verify that a diff occurs | ||
147 | ExpectNonEmptyPlan bool | ||
148 | |||
149 | // ExpectError allows the construction of test cases that we expect to fail | ||
150 | // with an error. The specified regexp must match against the error for the | ||
151 | // test to pass. | ||
152 | ExpectError *regexp.Regexp | ||
153 | |||
154 | // PlanOnly can be set to only run `plan` with this configuration, and not | ||
155 | // actually apply it. This is useful for ensuring config changes result in | ||
156 | // no-op plans | ||
157 | PlanOnly bool | ||
158 | |||
159 | // PreventPostDestroyRefresh can be set to true for cases where data sources | ||
160 | // are tested alongside real resources | ||
161 | PreventPostDestroyRefresh bool | ||
162 | |||
163 | //--------------------------------------------------------------- | ||
164 | // ImportState testing | ||
165 | //--------------------------------------------------------------- | ||
166 | |||
167 | // ImportState, if true, will test the functionality of ImportState | ||
168 | // by importing the resource with ResourceName (must be set) and the | ||
169 | // ID of that resource. | ||
170 | ImportState bool | ||
171 | |||
172 | // ImportStateId is the ID to perform an ImportState operation with. | ||
173 | // This is optional. If it isn't set, then the resource ID is automatically | ||
174 | // determined by inspecting the state for ResourceName's ID. | ||
175 | ImportStateId string | ||
176 | |||
177 | // ImportStateIdPrefix is the prefix added in front of ImportStateId. | ||
178 | // This can be useful in complex import cases, where more than one | ||
179 | // attribute needs to be passed on as the Import ID. Mainly in cases | ||
180 | // where the ID is not known, and a known prefix needs to be added to | ||
181 | // the unset ImportStateId field. | ||
182 | ImportStateIdPrefix string | ||
183 | |||
184 | // ImportStateCheck checks the results of ImportState. It should be | ||
185 | // used to verify that the resulting value of ImportState has the | ||
186 | // proper resources, IDs, and attributes. | ||
187 | ImportStateCheck ImportStateCheckFunc | ||
188 | |||
189 | // ImportStateVerify, if true, will also check that the state values | ||
190 | // that are finally put into the state after import match for all the | ||
191 | // IDs returned by the Import. | ||
192 | // | ||
193 | // ImportStateVerifyIgnore are fields that should not be verified to | ||
194 | // be equal. These can be set to ephemeral fields or fields that can't | ||
195 | // be refreshed and don't matter. | ||
196 | ImportStateVerify bool | ||
197 | ImportStateVerifyIgnore []string | ||
198 | } | ||
199 | |||
200 | // Test performs an acceptance test on a resource. | ||
201 | // | ||
202 | // Tests are not run unless an environmental variable "TF_ACC" is | ||
203 | // set to some non-empty value. This is to avoid test cases surprising | ||
204 | // a user by creating real resources. | ||
205 | // | ||
206 | // Tests will fail unless the verbose flag (`go test -v`, or explicitly | ||
207 | // the "-test.v" flag) is set. Because some acceptance tests take quite | ||
208 | // long, we require the verbose flag so users are able to see progress | ||
209 | // output. | ||
210 | func Test(t TestT, c TestCase) { | ||
211 | // We only run acceptance tests if an env var is set because they're | ||
212 | // slow and generally require some outside configuration. You can opt out | ||
213 | // of this with OverrideEnvVar on individual TestCases. | ||
214 | if os.Getenv(TestEnvVar) == "" && !c.IsUnitTest { | ||
215 | t.Skip(fmt.Sprintf( | ||
216 | "Acceptance tests skipped unless env '%s' set", | ||
217 | TestEnvVar)) | ||
218 | return | ||
219 | } | ||
220 | |||
221 | logWriter, err := logging.LogOutput() | ||
222 | if err != nil { | ||
223 | t.Error(fmt.Errorf("error setting up logging: %s", err)) | ||
224 | } | ||
225 | log.SetOutput(logWriter) | ||
226 | |||
227 | // We require verbose mode so that the user knows what is going on. | ||
228 | if !testTesting && !testing.Verbose() && !c.IsUnitTest { | ||
229 | t.Fatal("Acceptance tests must be run with the -v flag on tests") | ||
230 | return | ||
231 | } | ||
232 | |||
233 | // Run the PreCheck if we have it | ||
234 | if c.PreCheck != nil { | ||
235 | c.PreCheck() | ||
236 | } | ||
237 | |||
238 | ctxProviders, err := testProviderFactories(c) | ||
239 | if err != nil { | ||
240 | t.Fatal(err) | ||
241 | } | ||
242 | opts := terraform.ContextOpts{Providers: ctxProviders} | ||
243 | |||
244 | // A single state variable to track the lifecycle, starting with no state | ||
245 | var state *terraform.State | ||
246 | |||
247 | // Go through each step and run it | ||
248 | var idRefreshCheck *terraform.ResourceState | ||
249 | idRefresh := c.IDRefreshName != "" | ||
250 | errored := false | ||
251 | for i, step := range c.Steps { | ||
252 | var err error | ||
253 | log.Printf("[WARN] Test: Executing step %d", i) | ||
254 | |||
255 | // Determine the test mode to execute | ||
256 | if step.Config != "" { | ||
257 | state, err = testStepConfig(opts, state, step) | ||
258 | } else if step.ImportState { | ||
259 | state, err = testStepImportState(opts, state, step) | ||
260 | } else { | ||
261 | err = fmt.Errorf( | ||
262 | "unknown test mode for step. Please see TestStep docs\n\n%#v", | ||
263 | step) | ||
264 | } | ||
265 | |||
266 | // If there was an error, exit | ||
267 | if err != nil { | ||
268 | // Perhaps we expected an error? Check if it matches | ||
269 | if step.ExpectError != nil { | ||
270 | if !step.ExpectError.MatchString(err.Error()) { | ||
271 | errored = true | ||
272 | t.Error(fmt.Sprintf( | ||
273 | "Step %d, expected error:\n\n%s\n\nTo match:\n\n%s\n\n", | ||
274 | i, err, step.ExpectError)) | ||
275 | break | ||
276 | } | ||
277 | } else { | ||
278 | errored = true | ||
279 | t.Error(fmt.Sprintf( | ||
280 | "Step %d error: %s", i, err)) | ||
281 | break | ||
282 | } | ||
283 | } | ||
284 | |||
285 | // If we've never checked an id-only refresh and our state isn't | ||
286 | // empty, find the first resource and test it. | ||
287 | if idRefresh && idRefreshCheck == nil && !state.Empty() { | ||
288 | // Find the first non-nil resource in the state | ||
289 | for _, m := range state.Modules { | ||
290 | if len(m.Resources) > 0 { | ||
291 | if v, ok := m.Resources[c.IDRefreshName]; ok { | ||
292 | idRefreshCheck = v | ||
293 | } | ||
294 | |||
295 | break | ||
296 | } | ||
297 | } | ||
298 | |||
299 | // If we have an instance to check for refreshes, do it | ||
300 | // immediately. We do it in the middle of another test | ||
301 | // because it shouldn't affect the overall state (refresh | ||
302 | // is read-only semantically) and we want to fail early if | ||
303 | // this fails. If refresh isn't read-only, then this will have | ||
304 | // caught a different bug. | ||
305 | if idRefreshCheck != nil { | ||
306 | log.Printf( | ||
307 | "[WARN] Test: Running ID-only refresh check on %s", | ||
308 | idRefreshCheck.Primary.ID) | ||
309 | if err := testIDOnlyRefresh(c, opts, step, idRefreshCheck); err != nil { | ||
310 | log.Printf("[ERROR] Test: ID-only test failed: %s", err) | ||
311 | t.Error(fmt.Sprintf( | ||
312 | "[ERROR] Test: ID-only test failed: %s", err)) | ||
313 | break | ||
314 | } | ||
315 | } | ||
316 | } | ||
317 | } | ||
318 | |||
319 | // If we never checked an id-only refresh, it is a failure. | ||
320 | if idRefresh { | ||
321 | if !errored && len(c.Steps) > 0 && idRefreshCheck == nil { | ||
322 | t.Error("ID-only refresh check never ran.") | ||
323 | } | ||
324 | } | ||
325 | |||
326 | // If we have a state, then run the destroy | ||
327 | if state != nil { | ||
328 | lastStep := c.Steps[len(c.Steps)-1] | ||
329 | destroyStep := TestStep{ | ||
330 | Config: lastStep.Config, | ||
331 | Check: c.CheckDestroy, | ||
332 | Destroy: true, | ||
333 | PreventPostDestroyRefresh: c.PreventPostDestroyRefresh, | ||
334 | } | ||
335 | |||
336 | log.Printf("[WARN] Test: Executing destroy step") | ||
337 | state, err := testStep(opts, state, destroyStep) | ||
338 | if err != nil { | ||
339 | t.Error(fmt.Sprintf( | ||
340 | "Error destroying resource! WARNING: Dangling resources\n"+ | ||
341 | "may exist. The full state and error is shown below.\n\n"+ | ||
342 | "Error: %s\n\nState: %s", | ||
343 | err, | ||
344 | state)) | ||
345 | } | ||
346 | } else { | ||
347 | log.Printf("[WARN] Skipping destroy test since there is no state.") | ||
348 | } | ||
349 | } | ||
350 | |||
351 | // testProviderFactories is a helper to build the ResourceProviderFactory map | ||
352 | // with pre instantiated ResourceProviders, so that we can reset them for the | ||
353 | // test, while only calling the factory function once. | ||
354 | // Any errors are stored so that they can be returned by the factory in | ||
355 | // terraform to match non-test behavior. | ||
356 | func testProviderFactories(c TestCase) (map[string]terraform.ResourceProviderFactory, error) { | ||
357 | ctxProviders := c.ProviderFactories // make(map[string]terraform.ResourceProviderFactory) | ||
358 | if ctxProviders == nil { | ||
359 | ctxProviders = make(map[string]terraform.ResourceProviderFactory) | ||
360 | } | ||
361 | // add any fixed providers | ||
362 | for k, p := range c.Providers { | ||
363 | ctxProviders[k] = terraform.ResourceProviderFactoryFixed(p) | ||
364 | } | ||
365 | |||
366 | // reset the providers if needed | ||
367 | for k, pf := range ctxProviders { | ||
368 | // we can ignore any errors here, if we don't have a provider to reset | ||
369 | // the error will be handled later | ||
370 | p, err := pf() | ||
371 | if err != nil { | ||
372 | return nil, err | ||
373 | } | ||
374 | if p, ok := p.(TestProvider); ok { | ||
375 | err := p.TestReset() | ||
376 | if err != nil { | ||
377 | return nil, fmt.Errorf("[ERROR] failed to reset provider %q: %s", k, err) | ||
378 | } | ||
379 | } | ||
380 | } | ||
381 | |||
382 | return ctxProviders, nil | ||
383 | } | ||
384 | |||
385 | // UnitTest is a helper to force the acceptance testing harness to run in the | ||
386 | // normal unit test suite. This should only be used for resource that don't | ||
387 | // have any external dependencies. | ||
388 | func UnitTest(t TestT, c TestCase) { | ||
389 | c.IsUnitTest = true | ||
390 | Test(t, c) | ||
391 | } | ||
392 | |||
393 | func testIDOnlyRefresh(c TestCase, opts terraform.ContextOpts, step TestStep, r *terraform.ResourceState) error { | ||
394 | // TODO: We guard by this right now so master doesn't explode. We | ||
395 | // need to remove this eventually to make this part of the normal tests. | ||
396 | if os.Getenv("TF_ACC_IDONLY") == "" { | ||
397 | return nil | ||
398 | } | ||
399 | |||
400 | name := fmt.Sprintf("%s.foo", r.Type) | ||
401 | |||
402 | // Build the state. The state is just the resource with an ID. There | ||
403 | // are no attributes. We only set what is needed to perform a refresh. | ||
404 | state := terraform.NewState() | ||
405 | state.RootModule().Resources[name] = &terraform.ResourceState{ | ||
406 | Type: r.Type, | ||
407 | Primary: &terraform.InstanceState{ | ||
408 | ID: r.Primary.ID, | ||
409 | }, | ||
410 | } | ||
411 | |||
412 | // Create the config module. We use the full config because Refresh | ||
413 | // doesn't have access to it and we may need things like provider | ||
414 | // configurations. The initial implementation of id-only checks used | ||
415 | // an empty config module, but that caused the aforementioned problems. | ||
416 | mod, err := testModule(opts, step) | ||
417 | if err != nil { | ||
418 | return err | ||
419 | } | ||
420 | |||
421 | // Initialize the context | ||
422 | opts.Module = mod | ||
423 | opts.State = state | ||
424 | ctx, err := terraform.NewContext(&opts) | ||
425 | if err != nil { | ||
426 | return err | ||
427 | } | ||
428 | if ws, es := ctx.Validate(); len(ws) > 0 || len(es) > 0 { | ||
429 | if len(es) > 0 { | ||
430 | estrs := make([]string, len(es)) | ||
431 | for i, e := range es { | ||
432 | estrs[i] = e.Error() | ||
433 | } | ||
434 | return fmt.Errorf( | ||
435 | "Configuration is invalid.\n\nWarnings: %#v\n\nErrors: %#v", | ||
436 | ws, estrs) | ||
437 | } | ||
438 | |||
439 | log.Printf("[WARN] Config warnings: %#v", ws) | ||
440 | } | ||
441 | |||
442 | // Refresh! | ||
443 | state, err = ctx.Refresh() | ||
444 | if err != nil { | ||
445 | return fmt.Errorf("Error refreshing: %s", err) | ||
446 | } | ||
447 | |||
448 | // Verify attribute equivalence. | ||
449 | actualR := state.RootModule().Resources[name] | ||
450 | if actualR == nil { | ||
451 | return fmt.Errorf("Resource gone!") | ||
452 | } | ||
453 | if actualR.Primary == nil { | ||
454 | return fmt.Errorf("Resource has no primary instance") | ||
455 | } | ||
456 | actual := actualR.Primary.Attributes | ||
457 | expected := r.Primary.Attributes | ||
458 | // Remove fields we're ignoring | ||
459 | for _, v := range c.IDRefreshIgnore { | ||
460 | for k, _ := range actual { | ||
461 | if strings.HasPrefix(k, v) { | ||
462 | delete(actual, k) | ||
463 | } | ||
464 | } | ||
465 | for k, _ := range expected { | ||
466 | if strings.HasPrefix(k, v) { | ||
467 | delete(expected, k) | ||
468 | } | ||
469 | } | ||
470 | } | ||
471 | |||
472 | if !reflect.DeepEqual(actual, expected) { | ||
473 | // Determine only the different attributes | ||
474 | for k, v := range expected { | ||
475 | if av, ok := actual[k]; ok && v == av { | ||
476 | delete(expected, k) | ||
477 | delete(actual, k) | ||
478 | } | ||
479 | } | ||
480 | |||
481 | spewConf := spew.NewDefaultConfig() | ||
482 | spewConf.SortKeys = true | ||
483 | return fmt.Errorf( | ||
484 | "Attributes not equivalent. Difference is shown below. Top is actual, bottom is expected."+ | ||
485 | "\n\n%s\n\n%s", | ||
486 | spewConf.Sdump(actual), spewConf.Sdump(expected)) | ||
487 | } | ||
488 | |||
489 | return nil | ||
490 | } | ||
491 | |||
492 | func testModule( | ||
493 | opts terraform.ContextOpts, | ||
494 | step TestStep) (*module.Tree, error) { | ||
495 | if step.PreConfig != nil { | ||
496 | step.PreConfig() | ||
497 | } | ||
498 | |||
499 | cfgPath, err := ioutil.TempDir("", "tf-test") | ||
500 | if err != nil { | ||
501 | return nil, fmt.Errorf( | ||
502 | "Error creating temporary directory for config: %s", err) | ||
503 | } | ||
504 | defer os.RemoveAll(cfgPath) | ||
505 | |||
506 | // Write the configuration | ||
507 | cfgF, err := os.Create(filepath.Join(cfgPath, "main.tf")) | ||
508 | if err != nil { | ||
509 | return nil, fmt.Errorf( | ||
510 | "Error creating temporary file for config: %s", err) | ||
511 | } | ||
512 | |||
513 | _, err = io.Copy(cfgF, strings.NewReader(step.Config)) | ||
514 | cfgF.Close() | ||
515 | if err != nil { | ||
516 | return nil, fmt.Errorf( | ||
517 | "Error creating temporary file for config: %s", err) | ||
518 | } | ||
519 | |||
520 | // Parse the configuration | ||
521 | mod, err := module.NewTreeModule("", cfgPath) | ||
522 | if err != nil { | ||
523 | return nil, fmt.Errorf( | ||
524 | "Error loading configuration: %s", err) | ||
525 | } | ||
526 | |||
527 | // Load the modules | ||
528 | modStorage := &getter.FolderStorage{ | ||
529 | StorageDir: filepath.Join(cfgPath, ".tfmodules"), | ||
530 | } | ||
531 | err = mod.Load(modStorage, module.GetModeGet) | ||
532 | if err != nil { | ||
533 | return nil, fmt.Errorf("Error downloading modules: %s", err) | ||
534 | } | ||
535 | |||
536 | return mod, nil | ||
537 | } | ||
538 | |||
539 | func testResource(c TestStep, state *terraform.State) (*terraform.ResourceState, error) { | ||
540 | if c.ResourceName == "" { | ||
541 | return nil, fmt.Errorf("ResourceName must be set in TestStep") | ||
542 | } | ||
543 | |||
544 | for _, m := range state.Modules { | ||
545 | if len(m.Resources) > 0 { | ||
546 | if v, ok := m.Resources[c.ResourceName]; ok { | ||
547 | return v, nil | ||
548 | } | ||
549 | } | ||
550 | } | ||
551 | |||
552 | return nil, fmt.Errorf( | ||
553 | "Resource specified by ResourceName couldn't be found: %s", c.ResourceName) | ||
554 | } | ||
555 | |||
556 | // ComposeTestCheckFunc lets you compose multiple TestCheckFuncs into | ||
557 | // a single TestCheckFunc. | ||
558 | // | ||
559 | // As a user testing their provider, this lets you decompose your checks | ||
560 | // into smaller pieces more easily. | ||
561 | func ComposeTestCheckFunc(fs ...TestCheckFunc) TestCheckFunc { | ||
562 | return func(s *terraform.State) error { | ||
563 | for i, f := range fs { | ||
564 | if err := f(s); err != nil { | ||
565 | return fmt.Errorf("Check %d/%d error: %s", i+1, len(fs), err) | ||
566 | } | ||
567 | } | ||
568 | |||
569 | return nil | ||
570 | } | ||
571 | } | ||
572 | |||
573 | // ComposeAggregateTestCheckFunc lets you compose multiple TestCheckFuncs into | ||
574 | // a single TestCheckFunc. | ||
575 | // | ||
576 | // As a user testing their provider, this lets you decompose your checks | ||
577 | // into smaller pieces more easily. | ||
578 | // | ||
579 | // Unlike ComposeTestCheckFunc, ComposeAggergateTestCheckFunc runs _all_ of the | ||
580 | // TestCheckFuncs and aggregates failures. | ||
581 | func ComposeAggregateTestCheckFunc(fs ...TestCheckFunc) TestCheckFunc { | ||
582 | return func(s *terraform.State) error { | ||
583 | var result *multierror.Error | ||
584 | |||
585 | for i, f := range fs { | ||
586 | if err := f(s); err != nil { | ||
587 | result = multierror.Append(result, fmt.Errorf("Check %d/%d error: %s", i+1, len(fs), err)) | ||
588 | } | ||
589 | } | ||
590 | |||
591 | return result.ErrorOrNil() | ||
592 | } | ||
593 | } | ||
594 | |||
595 | // TestCheckResourceAttrSet is a TestCheckFunc which ensures a value | ||
596 | // exists in state for the given name/key combination. It is useful when | ||
597 | // testing that computed values were set, when it is not possible to | ||
598 | // know ahead of time what the values will be. | ||
599 | func TestCheckResourceAttrSet(name, key string) TestCheckFunc { | ||
600 | return func(s *terraform.State) error { | ||
601 | is, err := primaryInstanceState(s, name) | ||
602 | if err != nil { | ||
603 | return err | ||
604 | } | ||
605 | |||
606 | if val, ok := is.Attributes[key]; ok && val != "" { | ||
607 | return nil | ||
608 | } | ||
609 | |||
610 | return fmt.Errorf("%s: Attribute '%s' expected to be set", name, key) | ||
611 | } | ||
612 | } | ||
613 | |||
614 | // TestCheckResourceAttr is a TestCheckFunc which validates | ||
615 | // the value in state for the given name/key combination. | ||
616 | func TestCheckResourceAttr(name, key, value string) TestCheckFunc { | ||
617 | return func(s *terraform.State) error { | ||
618 | is, err := primaryInstanceState(s, name) | ||
619 | if err != nil { | ||
620 | return err | ||
621 | } | ||
622 | |||
623 | if v, ok := is.Attributes[key]; !ok || v != value { | ||
624 | if !ok { | ||
625 | return fmt.Errorf("%s: Attribute '%s' not found", name, key) | ||
626 | } | ||
627 | |||
628 | return fmt.Errorf( | ||
629 | "%s: Attribute '%s' expected %#v, got %#v", | ||
630 | name, | ||
631 | key, | ||
632 | value, | ||
633 | v) | ||
634 | } | ||
635 | |||
636 | return nil | ||
637 | } | ||
638 | } | ||
639 | |||
640 | // TestCheckNoResourceAttr is a TestCheckFunc which ensures that | ||
641 | // NO value exists in state for the given name/key combination. | ||
642 | func TestCheckNoResourceAttr(name, key string) TestCheckFunc { | ||
643 | return func(s *terraform.State) error { | ||
644 | is, err := primaryInstanceState(s, name) | ||
645 | if err != nil { | ||
646 | return err | ||
647 | } | ||
648 | |||
649 | if _, ok := is.Attributes[key]; ok { | ||
650 | return fmt.Errorf("%s: Attribute '%s' found when not expected", name, key) | ||
651 | } | ||
652 | |||
653 | return nil | ||
654 | } | ||
655 | } | ||
656 | |||
657 | // TestMatchResourceAttr is a TestCheckFunc which checks that the value | ||
658 | // in state for the given name/key combination matches the given regex. | ||
659 | func TestMatchResourceAttr(name, key string, r *regexp.Regexp) TestCheckFunc { | ||
660 | return func(s *terraform.State) error { | ||
661 | is, err := primaryInstanceState(s, name) | ||
662 | if err != nil { | ||
663 | return err | ||
664 | } | ||
665 | |||
666 | if !r.MatchString(is.Attributes[key]) { | ||
667 | return fmt.Errorf( | ||
668 | "%s: Attribute '%s' didn't match %q, got %#v", | ||
669 | name, | ||
670 | key, | ||
671 | r.String(), | ||
672 | is.Attributes[key]) | ||
673 | } | ||
674 | |||
675 | return nil | ||
676 | } | ||
677 | } | ||
678 | |||
679 | // TestCheckResourceAttrPtr is like TestCheckResourceAttr except the | ||
680 | // value is a pointer so that it can be updated while the test is running. | ||
681 | // It will only be dereferenced at the point this step is run. | ||
682 | func TestCheckResourceAttrPtr(name string, key string, value *string) TestCheckFunc { | ||
683 | return func(s *terraform.State) error { | ||
684 | return TestCheckResourceAttr(name, key, *value)(s) | ||
685 | } | ||
686 | } | ||
687 | |||
688 | // TestCheckResourceAttrPair is a TestCheckFunc which validates that the values | ||
689 | // in state for a pair of name/key combinations are equal. | ||
690 | func TestCheckResourceAttrPair(nameFirst, keyFirst, nameSecond, keySecond string) TestCheckFunc { | ||
691 | return func(s *terraform.State) error { | ||
692 | isFirst, err := primaryInstanceState(s, nameFirst) | ||
693 | if err != nil { | ||
694 | return err | ||
695 | } | ||
696 | vFirst, ok := isFirst.Attributes[keyFirst] | ||
697 | if !ok { | ||
698 | return fmt.Errorf("%s: Attribute '%s' not found", nameFirst, keyFirst) | ||
699 | } | ||
700 | |||
701 | isSecond, err := primaryInstanceState(s, nameSecond) | ||
702 | if err != nil { | ||
703 | return err | ||
704 | } | ||
705 | vSecond, ok := isSecond.Attributes[keySecond] | ||
706 | if !ok { | ||
707 | return fmt.Errorf("%s: Attribute '%s' not found", nameSecond, keySecond) | ||
708 | } | ||
709 | |||
710 | if vFirst != vSecond { | ||
711 | return fmt.Errorf( | ||
712 | "%s: Attribute '%s' expected %#v, got %#v", | ||
713 | nameFirst, | ||
714 | keyFirst, | ||
715 | vSecond, | ||
716 | vFirst) | ||
717 | } | ||
718 | |||
719 | return nil | ||
720 | } | ||
721 | } | ||
722 | |||
723 | // TestCheckOutput checks an output in the Terraform configuration | ||
724 | func TestCheckOutput(name, value string) TestCheckFunc { | ||
725 | return func(s *terraform.State) error { | ||
726 | ms := s.RootModule() | ||
727 | rs, ok := ms.Outputs[name] | ||
728 | if !ok { | ||
729 | return fmt.Errorf("Not found: %s", name) | ||
730 | } | ||
731 | |||
732 | if rs.Value != value { | ||
733 | return fmt.Errorf( | ||
734 | "Output '%s': expected %#v, got %#v", | ||
735 | name, | ||
736 | value, | ||
737 | rs) | ||
738 | } | ||
739 | |||
740 | return nil | ||
741 | } | ||
742 | } | ||
743 | |||
744 | func TestMatchOutput(name string, r *regexp.Regexp) TestCheckFunc { | ||
745 | return func(s *terraform.State) error { | ||
746 | ms := s.RootModule() | ||
747 | rs, ok := ms.Outputs[name] | ||
748 | if !ok { | ||
749 | return fmt.Errorf("Not found: %s", name) | ||
750 | } | ||
751 | |||
752 | if !r.MatchString(rs.Value.(string)) { | ||
753 | return fmt.Errorf( | ||
754 | "Output '%s': %#v didn't match %q", | ||
755 | name, | ||
756 | rs, | ||
757 | r.String()) | ||
758 | } | ||
759 | |||
760 | return nil | ||
761 | } | ||
762 | } | ||
763 | |||
764 | // TestT is the interface used to handle the test lifecycle of a test. | ||
765 | // | ||
766 | // Users should just use a *testing.T object, which implements this. | ||
767 | type TestT interface { | ||
768 | Error(args ...interface{}) | ||
769 | Fatal(args ...interface{}) | ||
770 | Skip(args ...interface{}) | ||
771 | } | ||
772 | |||
773 | // This is set to true by unit tests to alter some behavior | ||
774 | var testTesting = false | ||
775 | |||
776 | // primaryInstanceState returns the primary instance state for the given resource name. | ||
777 | func primaryInstanceState(s *terraform.State, name string) (*terraform.InstanceState, error) { | ||
778 | ms := s.RootModule() | ||
779 | rs, ok := ms.Resources[name] | ||
780 | if !ok { | ||
781 | return nil, fmt.Errorf("Not found: %s", name) | ||
782 | } | ||
783 | |||
784 | is := rs.Primary | ||
785 | if is == nil { | ||
786 | return nil, fmt.Errorf("No primary instance: %s", name) | ||
787 | } | ||
788 | |||
789 | return is, nil | ||
790 | } | ||