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