github.com/paybyphone/terraform@v0.9.5-0.20170613192930-9706042ddd51/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 // Can optionally set step.Config in addition to 410 // step.ImportState, to provide config for the import. 411 state, err = testStepImportState(opts, state, step) 412 } else { 413 state, err = testStepConfig(opts, state, step) 414 } 415 } 416 417 // If there was an error, exit 418 if err != nil { 419 // Perhaps we expected an error? Check if it matches 420 if step.ExpectError != nil { 421 if !step.ExpectError.MatchString(err.Error()) { 422 errored = true 423 t.Error(fmt.Sprintf( 424 "Step %d, expected error:\n\n%s\n\nTo match:\n\n%s\n\n", 425 i, err, step.ExpectError)) 426 break 427 } 428 } else { 429 errored = true 430 t.Error(fmt.Sprintf( 431 "Step %d error: %s", i, err)) 432 break 433 } 434 } 435 436 // If we've never checked an id-only refresh and our state isn't 437 // empty, find the first resource and test it. 438 if idRefresh && idRefreshCheck == nil && !state.Empty() { 439 // Find the first non-nil resource in the state 440 for _, m := range state.Modules { 441 if len(m.Resources) > 0 { 442 if v, ok := m.Resources[c.IDRefreshName]; ok { 443 idRefreshCheck = v 444 } 445 446 break 447 } 448 } 449 450 // If we have an instance to check for refreshes, do it 451 // immediately. We do it in the middle of another test 452 // because it shouldn't affect the overall state (refresh 453 // is read-only semantically) and we want to fail early if 454 // this fails. If refresh isn't read-only, then this will have 455 // caught a different bug. 456 if idRefreshCheck != nil { 457 log.Printf( 458 "[WARN] Test: Running ID-only refresh check on %s", 459 idRefreshCheck.Primary.ID) 460 if err := testIDOnlyRefresh(c, opts, step, idRefreshCheck); err != nil { 461 log.Printf("[ERROR] Test: ID-only test failed: %s", err) 462 t.Error(fmt.Sprintf( 463 "[ERROR] Test: ID-only test failed: %s", err)) 464 break 465 } 466 } 467 } 468 } 469 470 // If we never checked an id-only refresh, it is a failure. 471 if idRefresh { 472 if !errored && len(c.Steps) > 0 && idRefreshCheck == nil { 473 t.Error("ID-only refresh check never ran.") 474 } 475 } 476 477 // If we have a state, then run the destroy 478 if state != nil { 479 lastStep := c.Steps[len(c.Steps)-1] 480 destroyStep := TestStep{ 481 Config: lastStep.Config, 482 Check: c.CheckDestroy, 483 Destroy: true, 484 PreventPostDestroyRefresh: c.PreventPostDestroyRefresh, 485 } 486 487 log.Printf("[WARN] Test: Executing destroy step") 488 state, err := testStep(opts, state, destroyStep) 489 if err != nil { 490 t.Error(fmt.Sprintf( 491 "Error destroying resource! WARNING: Dangling resources\n"+ 492 "may exist. The full state and error is shown below.\n\n"+ 493 "Error: %s\n\nState: %s", 494 err, 495 state)) 496 } 497 } else { 498 log.Printf("[WARN] Skipping destroy test since there is no state.") 499 } 500 } 501 502 // testProviderResolver is a helper to build a ResourceProviderResolver 503 // with pre instantiated ResourceProviders, so that we can reset them for the 504 // test, while only calling the factory function once. 505 // Any errors are stored so that they can be returned by the factory in 506 // terraform to match non-test behavior. 507 func testProviderResolver(c TestCase) (terraform.ResourceProviderResolver, error) { 508 ctxProviders := c.ProviderFactories 509 if ctxProviders == nil { 510 ctxProviders = make(map[string]terraform.ResourceProviderFactory) 511 } 512 513 // add any fixed providers 514 for k, p := range c.Providers { 515 ctxProviders[k] = terraform.ResourceProviderFactoryFixed(p) 516 } 517 518 // reset the providers if needed 519 for k, pf := range ctxProviders { 520 // we can ignore any errors here, if we don't have a provider to reset 521 // the error will be handled later 522 p, err := pf() 523 if err != nil { 524 return nil, err 525 } 526 if p, ok := p.(TestProvider); ok { 527 err := p.TestReset() 528 if err != nil { 529 return nil, fmt.Errorf("[ERROR] failed to reset provider %q: %s", k, err) 530 } 531 } 532 } 533 534 return terraform.ResourceProviderResolverFixed(ctxProviders), nil 535 } 536 537 // UnitTest is a helper to force the acceptance testing harness to run in the 538 // normal unit test suite. This should only be used for resource that don't 539 // have any external dependencies. 540 func UnitTest(t TestT, c TestCase) { 541 c.IsUnitTest = true 542 Test(t, c) 543 } 544 545 func testIDOnlyRefresh(c TestCase, opts terraform.ContextOpts, step TestStep, r *terraform.ResourceState) error { 546 // TODO: We guard by this right now so master doesn't explode. We 547 // need to remove this eventually to make this part of the normal tests. 548 if os.Getenv("TF_ACC_IDONLY") == "" { 549 return nil 550 } 551 552 name := fmt.Sprintf("%s.foo", r.Type) 553 554 // Build the state. The state is just the resource with an ID. There 555 // are no attributes. We only set what is needed to perform a refresh. 556 state := terraform.NewState() 557 state.RootModule().Resources[name] = &terraform.ResourceState{ 558 Type: r.Type, 559 Primary: &terraform.InstanceState{ 560 ID: r.Primary.ID, 561 }, 562 } 563 564 // Create the config module. We use the full config because Refresh 565 // doesn't have access to it and we may need things like provider 566 // configurations. The initial implementation of id-only checks used 567 // an empty config module, but that caused the aforementioned problems. 568 mod, err := testModule(opts, step) 569 if err != nil { 570 return err 571 } 572 573 // Initialize the context 574 opts.Module = mod 575 opts.State = state 576 ctx, err := terraform.NewContext(&opts) 577 if err != nil { 578 return err 579 } 580 if ws, es := ctx.Validate(); len(ws) > 0 || len(es) > 0 { 581 if len(es) > 0 { 582 estrs := make([]string, len(es)) 583 for i, e := range es { 584 estrs[i] = e.Error() 585 } 586 return fmt.Errorf( 587 "Configuration is invalid.\n\nWarnings: %#v\n\nErrors: %#v", 588 ws, estrs) 589 } 590 591 log.Printf("[WARN] Config warnings: %#v", ws) 592 } 593 594 // Refresh! 595 state, err = ctx.Refresh() 596 if err != nil { 597 return fmt.Errorf("Error refreshing: %s", err) 598 } 599 600 // Verify attribute equivalence. 601 actualR := state.RootModule().Resources[name] 602 if actualR == nil { 603 return fmt.Errorf("Resource gone!") 604 } 605 if actualR.Primary == nil { 606 return fmt.Errorf("Resource has no primary instance") 607 } 608 actual := actualR.Primary.Attributes 609 expected := r.Primary.Attributes 610 // Remove fields we're ignoring 611 for _, v := range c.IDRefreshIgnore { 612 for k, _ := range actual { 613 if strings.HasPrefix(k, v) { 614 delete(actual, k) 615 } 616 } 617 for k, _ := range expected { 618 if strings.HasPrefix(k, v) { 619 delete(expected, k) 620 } 621 } 622 } 623 624 if !reflect.DeepEqual(actual, expected) { 625 // Determine only the different attributes 626 for k, v := range expected { 627 if av, ok := actual[k]; ok && v == av { 628 delete(expected, k) 629 delete(actual, k) 630 } 631 } 632 633 spewConf := spew.NewDefaultConfig() 634 spewConf.SortKeys = true 635 return fmt.Errorf( 636 "Attributes not equivalent. Difference is shown below. Top is actual, bottom is expected."+ 637 "\n\n%s\n\n%s", 638 spewConf.Sdump(actual), spewConf.Sdump(expected)) 639 } 640 641 return nil 642 } 643 644 func testModule( 645 opts terraform.ContextOpts, 646 step TestStep) (*module.Tree, error) { 647 if step.PreConfig != nil { 648 step.PreConfig() 649 } 650 651 cfgPath, err := ioutil.TempDir("", "tf-test") 652 if err != nil { 653 return nil, fmt.Errorf( 654 "Error creating temporary directory for config: %s", err) 655 } 656 defer os.RemoveAll(cfgPath) 657 658 // Write the configuration 659 cfgF, err := os.Create(filepath.Join(cfgPath, "main.tf")) 660 if err != nil { 661 return nil, fmt.Errorf( 662 "Error creating temporary file for config: %s", err) 663 } 664 665 _, err = io.Copy(cfgF, strings.NewReader(step.Config)) 666 cfgF.Close() 667 if err != nil { 668 return nil, fmt.Errorf( 669 "Error creating temporary file for config: %s", err) 670 } 671 672 // Parse the configuration 673 mod, err := module.NewTreeModule("", cfgPath) 674 if err != nil { 675 return nil, fmt.Errorf( 676 "Error loading configuration: %s", err) 677 } 678 679 // Load the modules 680 modStorage := &getter.FolderStorage{ 681 StorageDir: filepath.Join(cfgPath, ".tfmodules"), 682 } 683 err = mod.Load(modStorage, module.GetModeGet) 684 if err != nil { 685 return nil, fmt.Errorf("Error downloading modules: %s", err) 686 } 687 688 return mod, nil 689 } 690 691 func testResource(c TestStep, state *terraform.State) (*terraform.ResourceState, error) { 692 if c.ResourceName == "" { 693 return nil, fmt.Errorf("ResourceName must be set in TestStep") 694 } 695 696 for _, m := range state.Modules { 697 if len(m.Resources) > 0 { 698 if v, ok := m.Resources[c.ResourceName]; ok { 699 return v, nil 700 } 701 } 702 } 703 704 return nil, fmt.Errorf( 705 "Resource specified by ResourceName couldn't be found: %s", c.ResourceName) 706 } 707 708 // ComposeTestCheckFunc lets you compose multiple TestCheckFuncs into 709 // a single TestCheckFunc. 710 // 711 // As a user testing their provider, this lets you decompose your checks 712 // into smaller pieces more easily. 713 func ComposeTestCheckFunc(fs ...TestCheckFunc) TestCheckFunc { 714 return func(s *terraform.State) error { 715 for i, f := range fs { 716 if err := f(s); err != nil { 717 return fmt.Errorf("Check %d/%d error: %s", i+1, len(fs), err) 718 } 719 } 720 721 return nil 722 } 723 } 724 725 // ComposeAggregateTestCheckFunc lets you compose multiple TestCheckFuncs into 726 // a single TestCheckFunc. 727 // 728 // As a user testing their provider, this lets you decompose your checks 729 // into smaller pieces more easily. 730 // 731 // Unlike ComposeTestCheckFunc, ComposeAggergateTestCheckFunc runs _all_ of the 732 // TestCheckFuncs and aggregates failures. 733 func ComposeAggregateTestCheckFunc(fs ...TestCheckFunc) TestCheckFunc { 734 return func(s *terraform.State) error { 735 var result *multierror.Error 736 737 for i, f := range fs { 738 if err := f(s); err != nil { 739 result = multierror.Append(result, fmt.Errorf("Check %d/%d error: %s", i+1, len(fs), err)) 740 } 741 } 742 743 return result.ErrorOrNil() 744 } 745 } 746 747 // TestCheckResourceAttrSet is a TestCheckFunc which ensures a value 748 // exists in state for the given name/key combination. It is useful when 749 // testing that computed values were set, when it is not possible to 750 // know ahead of time what the values will be. 751 func TestCheckResourceAttrSet(name, key string) TestCheckFunc { 752 return func(s *terraform.State) error { 753 is, err := primaryInstanceState(s, name) 754 if err != nil { 755 return err 756 } 757 758 if val, ok := is.Attributes[key]; ok && val != "" { 759 return nil 760 } 761 762 return fmt.Errorf("%s: Attribute '%s' expected to be set", name, key) 763 } 764 } 765 766 // TestCheckResourceAttr is a TestCheckFunc which validates 767 // the value in state for the given name/key combination. 768 func TestCheckResourceAttr(name, key, value string) TestCheckFunc { 769 return func(s *terraform.State) error { 770 is, err := primaryInstanceState(s, name) 771 if err != nil { 772 return err 773 } 774 775 if v, ok := is.Attributes[key]; !ok || v != value { 776 if !ok { 777 return fmt.Errorf("%s: Attribute '%s' not found", name, key) 778 } 779 780 return fmt.Errorf( 781 "%s: Attribute '%s' expected %#v, got %#v", 782 name, 783 key, 784 value, 785 v) 786 } 787 788 return nil 789 } 790 } 791 792 // TestCheckNoResourceAttr is a TestCheckFunc which ensures that 793 // NO value exists in state for the given name/key combination. 794 func TestCheckNoResourceAttr(name, key string) TestCheckFunc { 795 return func(s *terraform.State) error { 796 is, err := primaryInstanceState(s, name) 797 if err != nil { 798 return err 799 } 800 801 if _, ok := is.Attributes[key]; ok { 802 return fmt.Errorf("%s: Attribute '%s' found when not expected", name, key) 803 } 804 805 return nil 806 } 807 } 808 809 // TestMatchResourceAttr is a TestCheckFunc which checks that the value 810 // in state for the given name/key combination matches the given regex. 811 func TestMatchResourceAttr(name, key string, r *regexp.Regexp) TestCheckFunc { 812 return func(s *terraform.State) error { 813 is, err := primaryInstanceState(s, name) 814 if err != nil { 815 return err 816 } 817 818 if !r.MatchString(is.Attributes[key]) { 819 return fmt.Errorf( 820 "%s: Attribute '%s' didn't match %q, got %#v", 821 name, 822 key, 823 r.String(), 824 is.Attributes[key]) 825 } 826 827 return nil 828 } 829 } 830 831 // TestCheckResourceAttrPtr is like TestCheckResourceAttr except the 832 // value is a pointer so that it can be updated while the test is running. 833 // It will only be dereferenced at the point this step is run. 834 func TestCheckResourceAttrPtr(name string, key string, value *string) TestCheckFunc { 835 return func(s *terraform.State) error { 836 return TestCheckResourceAttr(name, key, *value)(s) 837 } 838 } 839 840 // TestCheckResourceAttrPair is a TestCheckFunc which validates that the values 841 // in state for a pair of name/key combinations are equal. 842 func TestCheckResourceAttrPair(nameFirst, keyFirst, nameSecond, keySecond string) TestCheckFunc { 843 return func(s *terraform.State) error { 844 isFirst, err := primaryInstanceState(s, nameFirst) 845 if err != nil { 846 return err 847 } 848 vFirst, ok := isFirst.Attributes[keyFirst] 849 if !ok { 850 return fmt.Errorf("%s: Attribute '%s' not found", nameFirst, keyFirst) 851 } 852 853 isSecond, err := primaryInstanceState(s, nameSecond) 854 if err != nil { 855 return err 856 } 857 vSecond, ok := isSecond.Attributes[keySecond] 858 if !ok { 859 return fmt.Errorf("%s: Attribute '%s' not found", nameSecond, keySecond) 860 } 861 862 if vFirst != vSecond { 863 return fmt.Errorf( 864 "%s: Attribute '%s' expected %#v, got %#v", 865 nameFirst, 866 keyFirst, 867 vSecond, 868 vFirst) 869 } 870 871 return nil 872 } 873 } 874 875 // TestCheckOutput checks an output in the Terraform configuration 876 func TestCheckOutput(name, value string) TestCheckFunc { 877 return func(s *terraform.State) error { 878 ms := s.RootModule() 879 rs, ok := ms.Outputs[name] 880 if !ok { 881 return fmt.Errorf("Not found: %s", name) 882 } 883 884 if rs.Value != value { 885 return fmt.Errorf( 886 "Output '%s': expected %#v, got %#v", 887 name, 888 value, 889 rs) 890 } 891 892 return nil 893 } 894 } 895 896 func TestMatchOutput(name string, r *regexp.Regexp) TestCheckFunc { 897 return func(s *terraform.State) error { 898 ms := s.RootModule() 899 rs, ok := ms.Outputs[name] 900 if !ok { 901 return fmt.Errorf("Not found: %s", name) 902 } 903 904 if !r.MatchString(rs.Value.(string)) { 905 return fmt.Errorf( 906 "Output '%s': %#v didn't match %q", 907 name, 908 rs, 909 r.String()) 910 } 911 912 return nil 913 } 914 } 915 916 // TestT is the interface used to handle the test lifecycle of a test. 917 // 918 // Users should just use a *testing.T object, which implements this. 919 type TestT interface { 920 Error(args ...interface{}) 921 Fatal(args ...interface{}) 922 Skip(args ...interface{}) 923 } 924 925 // This is set to true by unit tests to alter some behavior 926 var testTesting = false 927 928 // primaryInstanceState returns the primary instance state for the given resource name. 929 func primaryInstanceState(s *terraform.State, name string) (*terraform.InstanceState, error) { 930 ms := s.RootModule() 931 rs, ok := ms.Resources[name] 932 if !ok { 933 return nil, fmt.Errorf("Not found: %s", name) 934 } 935 936 is := rs.Primary 937 if is == nil { 938 return nil, fmt.Errorf("No primary instance: %s", name) 939 } 940 941 return is, nil 942 }