github.com/ticketmaster/terraform@v0.10.0-beta2.0.20170711045249-a12daf5aba4f/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 //--------------------------------------------------------------- 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 386 providerResolver, err := testProviderResolver(c) 387 if err != nil { 388 t.Fatal(err) 389 } 390 opts := terraform.ContextOpts{ProviderResolver: providerResolver} 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 403 if step.Config == "" && !step.ImportState { 404 err = fmt.Errorf( 405 "unknown test mode for step. Please see TestStep docs\n\n%#v", 406 step) 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 } 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 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 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. 523 func testProviderResolver(c TestCase) (terraform.ResourceProviderResolver, error) { 524 ctxProviders := c.ProviderFactories 525 if ctxProviders == nil { 526 ctxProviders = make(map[string]terraform.ResourceProviderFactory) 527 } 528 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 550 return terraform.ResourceProviderResolverFixed(ctxProviders), nil 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 }