]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/hashicorp/terraform/helper/resource/testing.go
vendor: github.com/hashicorp/terraform/...@v0.10.0
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / helper / resource / testing.go
CommitLineData
bae9f6d2
JC
1package resource
2
3import (
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
44var flagSweep = flag.String("sweep", "", "List of Regions to run available Sweepers")
45var flagSweepRun = flag.String("sweep-run", "", "Comma seperated list of Sweeper Tests to run")
46var sweeperFuncs map[string]*Sweeper
47
48// map of sweepers that have ran, and the success/fail status based on any error
49// raised
50var 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.
55type SweeperFunc func(r string) error
56
57type 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
71func 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.
80func 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
88func 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.
121func 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.
144func 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
171const 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.
176type 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.
184type TestCheckFunc func(*terraform.State) error
185
186// ImportStateCheckFunc is the check function for ImportState tests
187type 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.
194type 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.
249type 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.
358func 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.
509func 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
523func 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.
556func UnitTest(t TestT, c TestCase) {
557 c.IsUnitTest = true
558 Test(t, c)
559}
560
561func 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
660func 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
707func 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.
729func 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.
749func 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.
767func 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.
784func 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.
810func 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.
827func 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.
850func 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.
858func 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
892func 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
912func 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.
935type 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
942var testTesting = false
943
944// primaryInstanceState returns the primary instance state for the given resource name.
945func 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}