]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
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 | } |