github.com/jd3nn1s/terraform@v0.9.6-0.20170906225847-13878347b7a1/helper/resource/testing.go (about) 1 package resource 2 3 import ( 4 "flag" 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 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 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 // SkipFunc is called before applying config, but after PreConfig 312 // This is useful for defining test steps with platform-dependent checks 313 SkipFunc func() (bool, error) 314 315 //--------------------------------------------------------------- 316 // ImportState testing 317 //--------------------------------------------------------------- 318 319 // ImportState, if true, will test the functionality of ImportState 320 // by importing the resource with ResourceName (must be set) and the 321 // ID of that resource. 322 ImportState bool 323 324 // ImportStateId is the ID to perform an ImportState operation with. 325 // This is optional. If it isn't set, then the resource ID is automatically 326 // determined by inspecting the state for ResourceName's ID. 327 ImportStateId string 328 329 // ImportStateIdPrefix is the prefix added in front of ImportStateId. 330 // This can be useful in complex import cases, where more than one 331 // attribute needs to be passed on as the Import ID. Mainly in cases 332 // where the ID is not known, and a known prefix needs to be added to 333 // the unset ImportStateId field. 334 ImportStateIdPrefix string 335 336 // ImportStateCheck checks the results of ImportState. It should be 337 // used to verify that the resulting value of ImportState has the 338 // proper resources, IDs, and attributes. 339 ImportStateCheck ImportStateCheckFunc 340 341 // ImportStateVerify, if true, will also check that the state values 342 // that are finally put into the state after import match for all the 343 // IDs returned by the Import. 344 // 345 // ImportStateVerifyIgnore are fields that should not be verified to 346 // be equal. These can be set to ephemeral fields or fields that can't 347 // be refreshed and don't matter. 348 ImportStateVerify bool 349 ImportStateVerifyIgnore []string 350 } 351 352 // Test performs an acceptance test on a resource. 353 // 354 // Tests are not run unless an environmental variable "TF_ACC" is 355 // set to some non-empty value. This is to avoid test cases surprising 356 // a user by creating real resources. 357 // 358 // Tests will fail unless the verbose flag (`go test -v`, or explicitly 359 // the "-test.v" flag) is set. Because some acceptance tests take quite 360 // long, we require the verbose flag so users are able to see progress 361 // output. 362 func Test(t TestT, c TestCase) { 363 // We only run acceptance tests if an env var is set because they're 364 // slow and generally require some outside configuration. You can opt out 365 // of this with OverrideEnvVar on individual TestCases. 366 if os.Getenv(TestEnvVar) == "" && !c.IsUnitTest { 367 t.Skip(fmt.Sprintf( 368 "Acceptance tests skipped unless env '%s' set", 369 TestEnvVar)) 370 return 371 } 372 373 logWriter, err := logging.LogOutput() 374 if err != nil { 375 t.Error(fmt.Errorf("error setting up logging: %s", err)) 376 } 377 log.SetOutput(logWriter) 378 379 // We require verbose mode so that the user knows what is going on. 380 if !testTesting && !testing.Verbose() && !c.IsUnitTest { 381 t.Fatal("Acceptance tests must be run with the -v flag on tests") 382 return 383 } 384 385 // Run the PreCheck if we have it 386 if c.PreCheck != nil { 387 c.PreCheck() 388 } 389 390 providerResolver, err := testProviderResolver(c) 391 if err != nil { 392 t.Fatal(err) 393 } 394 opts := terraform.ContextOpts{ProviderResolver: providerResolver} 395 396 // A single state variable to track the lifecycle, starting with no state 397 var state *terraform.State 398 399 // Go through each step and run it 400 var idRefreshCheck *terraform.ResourceState 401 idRefresh := c.IDRefreshName != "" 402 errored := false 403 for i, step := range c.Steps { 404 var err error 405 log.Printf("[DEBUG] Test: Executing step %d", i) 406 407 if step.SkipFunc != nil { 408 skip, err := step.SkipFunc() 409 if err != nil { 410 t.Fatal(err) 411 } 412 if skip { 413 log.Printf("[WARN] Skipping step %d", i) 414 continue 415 } 416 } 417 418 if step.Config == "" && !step.ImportState { 419 err = fmt.Errorf( 420 "unknown test mode for step. Please see TestStep docs\n\n%#v", 421 step) 422 } else { 423 if step.ImportState { 424 if step.Config == "" { 425 step.Config = testProviderConfig(c) 426 } 427 428 // Can optionally set step.Config in addition to 429 // step.ImportState, to provide config for the import. 430 state, err = testStepImportState(opts, state, step) 431 } else { 432 state, err = testStepConfig(opts, state, step) 433 } 434 } 435 436 // If there was an error, exit 437 if err != nil { 438 // Perhaps we expected an error? Check if it matches 439 if step.ExpectError != nil { 440 if !step.ExpectError.MatchString(err.Error()) { 441 errored = true 442 t.Error(fmt.Sprintf( 443 "Step %d, expected error:\n\n%s\n\nTo match:\n\n%s\n\n", 444 i, err, step.ExpectError)) 445 break 446 } 447 } else { 448 errored = true 449 t.Error(fmt.Sprintf( 450 "Step %d error: %s", i, err)) 451 break 452 } 453 } 454 455 // If we've never checked an id-only refresh and our state isn't 456 // empty, find the first resource and test it. 457 if idRefresh && idRefreshCheck == nil && !state.Empty() { 458 // Find the first non-nil resource in the state 459 for _, m := range state.Modules { 460 if len(m.Resources) > 0 { 461 if v, ok := m.Resources[c.IDRefreshName]; ok { 462 idRefreshCheck = v 463 } 464 465 break 466 } 467 } 468 469 // If we have an instance to check for refreshes, do it 470 // immediately. We do it in the middle of another test 471 // because it shouldn't affect the overall state (refresh 472 // is read-only semantically) and we want to fail early if 473 // this fails. If refresh isn't read-only, then this will have 474 // caught a different bug. 475 if idRefreshCheck != nil { 476 log.Printf( 477 "[WARN] Test: Running ID-only refresh check on %s", 478 idRefreshCheck.Primary.ID) 479 if err := testIDOnlyRefresh(c, opts, step, idRefreshCheck); err != nil { 480 log.Printf("[ERROR] Test: ID-only test failed: %s", err) 481 t.Error(fmt.Sprintf( 482 "[ERROR] Test: ID-only test failed: %s", err)) 483 break 484 } 485 } 486 } 487 } 488 489 // If we never checked an id-only refresh, it is a failure. 490 if idRefresh { 491 if !errored && len(c.Steps) > 0 && idRefreshCheck == nil { 492 t.Error("ID-only refresh check never ran.") 493 } 494 } 495 496 // If we have a state, then run the destroy 497 if state != nil { 498 lastStep := c.Steps[len(c.Steps)-1] 499 destroyStep := TestStep{ 500 Config: lastStep.Config, 501 Check: c.CheckDestroy, 502 Destroy: true, 503 PreventPostDestroyRefresh: c.PreventPostDestroyRefresh, 504 } 505 506 log.Printf("[WARN] Test: Executing destroy step") 507 state, err := testStep(opts, state, destroyStep) 508 if err != nil { 509 t.Error(fmt.Sprintf( 510 "Error destroying resource! WARNING: Dangling resources\n"+ 511 "may exist. The full state and error is shown below.\n\n"+ 512 "Error: %s\n\nState: %s", 513 err, 514 state)) 515 } 516 } else { 517 log.Printf("[WARN] Skipping destroy test since there is no state.") 518 } 519 } 520 521 // testProviderConfig takes the list of Providers in a TestCase and returns a 522 // config with only empty provider blocks. This is useful for Import, where no 523 // config is provided, but the providers must be defined. 524 func testProviderConfig(c TestCase) string { 525 var lines []string 526 for p := range c.Providers { 527 lines = append(lines, fmt.Sprintf("provider %q {}\n", p)) 528 } 529 530 return strings.Join(lines, "") 531 } 532 533 // testProviderResolver is a helper to build a ResourceProviderResolver 534 // with pre instantiated ResourceProviders, so that we can reset them for the 535 // test, while only calling the factory function once. 536 // Any errors are stored so that they can be returned by the factory in 537 // terraform to match non-test behavior. 538 func testProviderResolver(c TestCase) (terraform.ResourceProviderResolver, error) { 539 ctxProviders := c.ProviderFactories 540 if ctxProviders == nil { 541 ctxProviders = make(map[string]terraform.ResourceProviderFactory) 542 } 543 544 // add any fixed providers 545 for k, p := range c.Providers { 546 ctxProviders[k] = terraform.ResourceProviderFactoryFixed(p) 547 } 548 549 // reset the providers if needed 550 for k, pf := range ctxProviders { 551 // we can ignore any errors here, if we don't have a provider to reset 552 // the error will be handled later 553 p, err := pf() 554 if err != nil { 555 return nil, err 556 } 557 if p, ok := p.(TestProvider); ok { 558 err := p.TestReset() 559 if err != nil { 560 return nil, fmt.Errorf("[ERROR] failed to reset provider %q: %s", k, err) 561 } 562 } 563 } 564 565 return terraform.ResourceProviderResolverFixed(ctxProviders), nil 566 } 567 568 // UnitTest is a helper to force the acceptance testing harness to run in the 569 // normal unit test suite. This should only be used for resource that don't 570 // have any external dependencies. 571 func UnitTest(t TestT, c TestCase) { 572 c.IsUnitTest = true 573 Test(t, c) 574 } 575 576 func testIDOnlyRefresh(c TestCase, opts terraform.ContextOpts, step TestStep, r *terraform.ResourceState) error { 577 // TODO: We guard by this right now so master doesn't explode. We 578 // need to remove this eventually to make this part of the normal tests. 579 if os.Getenv("TF_ACC_IDONLY") == "" { 580 return nil 581 } 582 583 name := fmt.Sprintf("%s.foo", r.Type) 584 585 // Build the state. The state is just the resource with an ID. There 586 // are no attributes. We only set what is needed to perform a refresh. 587 state := terraform.NewState() 588 state.RootModule().Resources[name] = &terraform.ResourceState{ 589 Type: r.Type, 590 Primary: &terraform.InstanceState{ 591 ID: r.Primary.ID, 592 }, 593 } 594 595 // Create the config module. We use the full config because Refresh 596 // doesn't have access to it and we may need things like provider 597 // configurations. The initial implementation of id-only checks used 598 // an empty config module, but that caused the aforementioned problems. 599 mod, err := testModule(opts, step) 600 if err != nil { 601 return err 602 } 603 604 // Initialize the context 605 opts.Module = mod 606 opts.State = state 607 ctx, err := terraform.NewContext(&opts) 608 if err != nil { 609 return err 610 } 611 if ws, es := ctx.Validate(); len(ws) > 0 || len(es) > 0 { 612 if len(es) > 0 { 613 estrs := make([]string, len(es)) 614 for i, e := range es { 615 estrs[i] = e.Error() 616 } 617 return fmt.Errorf( 618 "Configuration is invalid.\n\nWarnings: %#v\n\nErrors: %#v", 619 ws, estrs) 620 } 621 622 log.Printf("[WARN] Config warnings: %#v", ws) 623 } 624 625 // Refresh! 626 state, err = ctx.Refresh() 627 if err != nil { 628 return fmt.Errorf("Error refreshing: %s", err) 629 } 630 631 // Verify attribute equivalence. 632 actualR := state.RootModule().Resources[name] 633 if actualR == nil { 634 return fmt.Errorf("Resource gone!") 635 } 636 if actualR.Primary == nil { 637 return fmt.Errorf("Resource has no primary instance") 638 } 639 actual := actualR.Primary.Attributes 640 expected := r.Primary.Attributes 641 // Remove fields we're ignoring 642 for _, v := range c.IDRefreshIgnore { 643 for k, _ := range actual { 644 if strings.HasPrefix(k, v) { 645 delete(actual, k) 646 } 647 } 648 for k, _ := range expected { 649 if strings.HasPrefix(k, v) { 650 delete(expected, k) 651 } 652 } 653 } 654 655 if !reflect.DeepEqual(actual, expected) { 656 // Determine only the different attributes 657 for k, v := range expected { 658 if av, ok := actual[k]; ok && v == av { 659 delete(expected, k) 660 delete(actual, k) 661 } 662 } 663 664 spewConf := spew.NewDefaultConfig() 665 spewConf.SortKeys = true 666 return fmt.Errorf( 667 "Attributes not equivalent. Difference is shown below. Top is actual, bottom is expected."+ 668 "\n\n%s\n\n%s", 669 spewConf.Sdump(actual), spewConf.Sdump(expected)) 670 } 671 672 return nil 673 } 674 675 func testModule( 676 opts terraform.ContextOpts, 677 step TestStep) (*module.Tree, error) { 678 if step.PreConfig != nil { 679 step.PreConfig() 680 } 681 682 cfgPath, err := ioutil.TempDir("", "tf-test") 683 if err != nil { 684 return nil, fmt.Errorf( 685 "Error creating temporary directory for config: %s", err) 686 } 687 defer os.RemoveAll(cfgPath) 688 689 // Write the configuration 690 cfgF, err := os.Create(filepath.Join(cfgPath, "main.tf")) 691 if err != nil { 692 return nil, fmt.Errorf( 693 "Error creating temporary file for config: %s", err) 694 } 695 696 _, err = io.Copy(cfgF, strings.NewReader(step.Config)) 697 cfgF.Close() 698 if err != nil { 699 return nil, fmt.Errorf( 700 "Error creating temporary file for config: %s", err) 701 } 702 703 // Parse the configuration 704 mod, err := module.NewTreeModule("", cfgPath) 705 if err != nil { 706 return nil, fmt.Errorf( 707 "Error loading configuration: %s", err) 708 } 709 710 // Load the modules 711 modStorage := &getter.FolderStorage{ 712 StorageDir: filepath.Join(cfgPath, ".tfmodules"), 713 } 714 err = mod.Load(modStorage, module.GetModeGet) 715 if err != nil { 716 return nil, fmt.Errorf("Error downloading modules: %s", err) 717 } 718 719 return mod, nil 720 } 721 722 func testResource(c TestStep, state *terraform.State) (*terraform.ResourceState, error) { 723 if c.ResourceName == "" { 724 return nil, fmt.Errorf("ResourceName must be set in TestStep") 725 } 726 727 for _, m := range state.Modules { 728 if len(m.Resources) > 0 { 729 if v, ok := m.Resources[c.ResourceName]; ok { 730 return v, nil 731 } 732 } 733 } 734 735 return nil, fmt.Errorf( 736 "Resource specified by ResourceName couldn't be found: %s", c.ResourceName) 737 } 738 739 // ComposeTestCheckFunc lets you compose multiple TestCheckFuncs into 740 // a single TestCheckFunc. 741 // 742 // As a user testing their provider, this lets you decompose your checks 743 // into smaller pieces more easily. 744 func ComposeTestCheckFunc(fs ...TestCheckFunc) TestCheckFunc { 745 return func(s *terraform.State) error { 746 for i, f := range fs { 747 if err := f(s); err != nil { 748 return fmt.Errorf("Check %d/%d error: %s", i+1, len(fs), err) 749 } 750 } 751 752 return nil 753 } 754 } 755 756 // ComposeAggregateTestCheckFunc lets you compose multiple TestCheckFuncs into 757 // a single TestCheckFunc. 758 // 759 // As a user testing their provider, this lets you decompose your checks 760 // into smaller pieces more easily. 761 // 762 // Unlike ComposeTestCheckFunc, ComposeAggergateTestCheckFunc runs _all_ of the 763 // TestCheckFuncs and aggregates failures. 764 func ComposeAggregateTestCheckFunc(fs ...TestCheckFunc) TestCheckFunc { 765 return func(s *terraform.State) error { 766 var result *multierror.Error 767 768 for i, f := range fs { 769 if err := f(s); err != nil { 770 result = multierror.Append(result, fmt.Errorf("Check %d/%d error: %s", i+1, len(fs), err)) 771 } 772 } 773 774 return result.ErrorOrNil() 775 } 776 } 777 778 // TestCheckResourceAttrSet is a TestCheckFunc which ensures a value 779 // exists in state for the given name/key combination. It is useful when 780 // testing that computed values were set, when it is not possible to 781 // know ahead of time what the values will be. 782 func TestCheckResourceAttrSet(name, key string) TestCheckFunc { 783 return func(s *terraform.State) error { 784 is, err := primaryInstanceState(s, name) 785 if err != nil { 786 return err 787 } 788 789 if val, ok := is.Attributes[key]; ok && val != "" { 790 return nil 791 } 792 793 return fmt.Errorf("%s: Attribute '%s' expected to be set", name, key) 794 } 795 } 796 797 // TestCheckResourceAttr is a TestCheckFunc which validates 798 // the value in state for the given name/key combination. 799 func TestCheckResourceAttr(name, key, value string) TestCheckFunc { 800 return func(s *terraform.State) error { 801 is, err := primaryInstanceState(s, name) 802 if err != nil { 803 return err 804 } 805 806 if v, ok := is.Attributes[key]; !ok || v != value { 807 if !ok { 808 return fmt.Errorf("%s: Attribute '%s' not found", name, key) 809 } 810 811 return fmt.Errorf( 812 "%s: Attribute '%s' expected %#v, got %#v", 813 name, 814 key, 815 value, 816 v) 817 } 818 819 return nil 820 } 821 } 822 823 // TestCheckNoResourceAttr is a TestCheckFunc which ensures that 824 // NO value exists in state for the given name/key combination. 825 func TestCheckNoResourceAttr(name, key string) TestCheckFunc { 826 return func(s *terraform.State) error { 827 is, err := primaryInstanceState(s, name) 828 if err != nil { 829 return err 830 } 831 832 if _, ok := is.Attributes[key]; ok { 833 return fmt.Errorf("%s: Attribute '%s' found when not expected", name, key) 834 } 835 836 return nil 837 } 838 } 839 840 // TestMatchResourceAttr is a TestCheckFunc which checks that the value 841 // in state for the given name/key combination matches the given regex. 842 func TestMatchResourceAttr(name, key string, r *regexp.Regexp) TestCheckFunc { 843 return func(s *terraform.State) error { 844 is, err := primaryInstanceState(s, name) 845 if err != nil { 846 return err 847 } 848 849 if !r.MatchString(is.Attributes[key]) { 850 return fmt.Errorf( 851 "%s: Attribute '%s' didn't match %q, got %#v", 852 name, 853 key, 854 r.String(), 855 is.Attributes[key]) 856 } 857 858 return nil 859 } 860 } 861 862 // TestCheckResourceAttrPtr is like TestCheckResourceAttr except the 863 // value is a pointer so that it can be updated while the test is running. 864 // It will only be dereferenced at the point this step is run. 865 func TestCheckResourceAttrPtr(name string, key string, value *string) TestCheckFunc { 866 return func(s *terraform.State) error { 867 return TestCheckResourceAttr(name, key, *value)(s) 868 } 869 } 870 871 // TestCheckResourceAttrPair is a TestCheckFunc which validates that the values 872 // in state for a pair of name/key combinations are equal. 873 func TestCheckResourceAttrPair(nameFirst, keyFirst, nameSecond, keySecond string) TestCheckFunc { 874 return func(s *terraform.State) error { 875 isFirst, err := primaryInstanceState(s, nameFirst) 876 if err != nil { 877 return err 878 } 879 vFirst, ok := isFirst.Attributes[keyFirst] 880 if !ok { 881 return fmt.Errorf("%s: Attribute '%s' not found", nameFirst, keyFirst) 882 } 883 884 isSecond, err := primaryInstanceState(s, nameSecond) 885 if err != nil { 886 return err 887 } 888 vSecond, ok := isSecond.Attributes[keySecond] 889 if !ok { 890 return fmt.Errorf("%s: Attribute '%s' not found", nameSecond, keySecond) 891 } 892 893 if vFirst != vSecond { 894 return fmt.Errorf( 895 "%s: Attribute '%s' expected %#v, got %#v", 896 nameFirst, 897 keyFirst, 898 vSecond, 899 vFirst) 900 } 901 902 return nil 903 } 904 } 905 906 // TestCheckOutput checks an output in the Terraform configuration 907 func TestCheckOutput(name, value string) TestCheckFunc { 908 return func(s *terraform.State) error { 909 ms := s.RootModule() 910 rs, ok := ms.Outputs[name] 911 if !ok { 912 return fmt.Errorf("Not found: %s", name) 913 } 914 915 if rs.Value != value { 916 return fmt.Errorf( 917 "Output '%s': expected %#v, got %#v", 918 name, 919 value, 920 rs) 921 } 922 923 return nil 924 } 925 } 926 927 func TestMatchOutput(name string, r *regexp.Regexp) TestCheckFunc { 928 return func(s *terraform.State) error { 929 ms := s.RootModule() 930 rs, ok := ms.Outputs[name] 931 if !ok { 932 return fmt.Errorf("Not found: %s", name) 933 } 934 935 if !r.MatchString(rs.Value.(string)) { 936 return fmt.Errorf( 937 "Output '%s': %#v didn't match %q", 938 name, 939 rs, 940 r.String()) 941 } 942 943 return nil 944 } 945 } 946 947 // TestT is the interface used to handle the test lifecycle of a test. 948 // 949 // Users should just use a *testing.T object, which implements this. 950 type TestT interface { 951 Error(args ...interface{}) 952 Fatal(args ...interface{}) 953 Skip(args ...interface{}) 954 } 955 956 // This is set to true by unit tests to alter some behavior 957 var testTesting = false 958 959 // primaryInstanceState returns the primary instance state for the given resource name. 960 func primaryInstanceState(s *terraform.State, name string) (*terraform.InstanceState, error) { 961 ms := s.RootModule() 962 rs, ok := ms.Resources[name] 963 if !ok { 964 return nil, fmt.Errorf("Not found: %s", name) 965 } 966 967 is := rs.Primary 968 if is == nil { 969 return nil, fmt.Errorf("No primary instance: %s", name) 970 } 971 972 return is, nil 973 }