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