github.com/ben-turner/terraform@v0.11.8-0.20180503104400-0cc9e050ecd4/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 //--------------------------------------------------------------- 270 // Test modes. One of the following groups of settings must be 271 // set to determine what the test step will do. Ideally we would've 272 // used Go interfaces here but there are now hundreds of tests we don't 273 // want to re-type so instead we just determine which step logic 274 // to run based on what settings below are set. 275 //--------------------------------------------------------------- 276 277 //--------------------------------------------------------------- 278 // Plan, Apply testing 279 //--------------------------------------------------------------- 280 281 // Config a string of the configuration to give to Terraform. If this 282 // is set, then the TestCase will execute this step with the same logic 283 // as a `terraform apply`. 284 Config string 285 286 // Check is called after the Config is applied. Use this step to 287 // make your own API calls to check the status of things, and to 288 // inspect the format of the ResourceState itself. 289 // 290 // If an error is returned, the test will fail. In this case, a 291 // destroy plan will still be attempted. 292 // 293 // If this is nil, no check is done on this step. 294 Check TestCheckFunc 295 296 // Destroy will create a destroy plan if set to true. 297 Destroy bool 298 299 // ExpectNonEmptyPlan can be set to true for specific types of tests that are 300 // looking to verify that a diff occurs 301 ExpectNonEmptyPlan bool 302 303 // ExpectError allows the construction of test cases that we expect to fail 304 // with an error. The specified regexp must match against the error for the 305 // test to pass. 306 ExpectError *regexp.Regexp 307 308 // PlanOnly can be set to only run `plan` with this configuration, and not 309 // actually apply it. This is useful for ensuring config changes result in 310 // no-op plans 311 PlanOnly bool 312 313 // PreventDiskCleanup can be set to true for testing terraform modules which 314 // require access to disk at runtime. Note that this will leave files in the 315 // temp folder 316 PreventDiskCleanup bool 317 318 // PreventPostDestroyRefresh can be set to true for cases where data sources 319 // are tested alongside real resources 320 PreventPostDestroyRefresh bool 321 322 // SkipFunc is called before applying config, but after PreConfig 323 // This is useful for defining test steps with platform-dependent checks 324 SkipFunc func() (bool, error) 325 326 //--------------------------------------------------------------- 327 // ImportState testing 328 //--------------------------------------------------------------- 329 330 // ImportState, if true, will test the functionality of ImportState 331 // by importing the resource with ResourceName (must be set) and the 332 // ID of that resource. 333 ImportState bool 334 335 // ImportStateId is the ID to perform an ImportState operation with. 336 // This is optional. If it isn't set, then the resource ID is automatically 337 // determined by inspecting the state for ResourceName's ID. 338 ImportStateId string 339 340 // ImportStateIdPrefix is the prefix added in front of ImportStateId. 341 // This can be useful in complex import cases, where more than one 342 // attribute needs to be passed on as the Import ID. Mainly in cases 343 // where the ID is not known, and a known prefix needs to be added to 344 // the unset ImportStateId field. 345 ImportStateIdPrefix string 346 347 // ImportStateIdFunc is a function that can be used to dynamically generate 348 // the ID for the ImportState tests. It is sent the state, which can be 349 // checked to derive the attributes necessary and generate the string in the 350 // desired format. 351 ImportStateIdFunc ImportStateIdFunc 352 353 // ImportStateCheck checks the results of ImportState. It should be 354 // used to verify that the resulting value of ImportState has the 355 // proper resources, IDs, and attributes. 356 ImportStateCheck ImportStateCheckFunc 357 358 // ImportStateVerify, if true, will also check that the state values 359 // that are finally put into the state after import match for all the 360 // IDs returned by the Import. 361 // 362 // ImportStateVerifyIgnore are fields that should not be verified to 363 // be equal. These can be set to ephemeral fields or fields that can't 364 // be refreshed and don't matter. 365 ImportStateVerify bool 366 ImportStateVerifyIgnore []string 367 } 368 369 // Set to a file mask in sprintf format where %s is test name 370 const EnvLogPathMask = "TF_LOG_PATH_MASK" 371 372 func LogOutput(t TestT) (logOutput io.Writer, err error) { 373 logOutput = ioutil.Discard 374 375 logLevel := logging.LogLevel() 376 if logLevel == "" { 377 return 378 } 379 380 logOutput = os.Stderr 381 382 if logPath := os.Getenv(logging.EnvLogFile); logPath != "" { 383 var err error 384 logOutput, err = os.OpenFile(logPath, syscall.O_CREAT|syscall.O_RDWR|syscall.O_APPEND, 0666) 385 if err != nil { 386 return nil, err 387 } 388 } 389 390 if logPathMask := os.Getenv(EnvLogPathMask); logPathMask != "" { 391 // Escape special characters which may appear if we have subtests 392 testName := strings.Replace(t.Name(), "/", "__", -1) 393 394 logPath := fmt.Sprintf(logPathMask, testName) 395 var err error 396 logOutput, err = os.OpenFile(logPath, syscall.O_CREAT|syscall.O_RDWR|syscall.O_APPEND, 0666) 397 if err != nil { 398 return nil, err 399 } 400 } 401 402 // This was the default since the beginning 403 logOutput = &logutils.LevelFilter{ 404 Levels: logging.ValidLevels, 405 MinLevel: logutils.LogLevel(logLevel), 406 Writer: logOutput, 407 } 408 409 return 410 } 411 412 // Test performs an acceptance test on a resource. 413 // 414 // Tests are not run unless an environmental variable "TF_ACC" is 415 // set to some non-empty value. This is to avoid test cases surprising 416 // a user by creating real resources. 417 // 418 // Tests will fail unless the verbose flag (`go test -v`, or explicitly 419 // the "-test.v" flag) is set. Because some acceptance tests take quite 420 // long, we require the verbose flag so users are able to see progress 421 // output. 422 func Test(t TestT, c TestCase) { 423 // We only run acceptance tests if an env var is set because they're 424 // slow and generally require some outside configuration. You can opt out 425 // of this with OverrideEnvVar on individual TestCases. 426 if os.Getenv(TestEnvVar) == "" && !c.IsUnitTest { 427 t.Skip(fmt.Sprintf( 428 "Acceptance tests skipped unless env '%s' set", 429 TestEnvVar)) 430 return 431 } 432 433 logWriter, err := LogOutput(t) 434 if err != nil { 435 t.Error(fmt.Errorf("error setting up logging: %s", err)) 436 } 437 log.SetOutput(logWriter) 438 439 // We require verbose mode so that the user knows what is going on. 440 if !testTesting && !testing.Verbose() && !c.IsUnitTest { 441 t.Fatal("Acceptance tests must be run with the -v flag on tests") 442 return 443 } 444 445 // Run the PreCheck if we have it 446 if c.PreCheck != nil { 447 c.PreCheck() 448 } 449 450 providerResolver, err := testProviderResolver(c) 451 if err != nil { 452 t.Fatal(err) 453 } 454 opts := terraform.ContextOpts{ProviderResolver: providerResolver} 455 456 // A single state variable to track the lifecycle, starting with no state 457 var state *terraform.State 458 459 // Go through each step and run it 460 var idRefreshCheck *terraform.ResourceState 461 idRefresh := c.IDRefreshName != "" 462 errored := false 463 for i, step := range c.Steps { 464 var err error 465 log.Printf("[DEBUG] Test: Executing step %d", i) 466 467 if step.SkipFunc != nil { 468 skip, err := step.SkipFunc() 469 if err != nil { 470 t.Fatal(err) 471 } 472 if skip { 473 log.Printf("[WARN] Skipping step %d", i) 474 continue 475 } 476 } 477 478 if step.Config == "" && !step.ImportState { 479 err = fmt.Errorf( 480 "unknown test mode for step. Please see TestStep docs\n\n%#v", 481 step) 482 } else { 483 if step.ImportState { 484 if step.Config == "" { 485 step.Config = testProviderConfig(c) 486 } 487 488 // Can optionally set step.Config in addition to 489 // step.ImportState, to provide config for the import. 490 state, err = testStepImportState(opts, state, step) 491 } else { 492 state, err = testStepConfig(opts, state, step) 493 } 494 } 495 496 // If we expected an error, but did not get one, fail 497 if err == nil && step.ExpectError != nil { 498 errored = true 499 t.Error(fmt.Sprintf( 500 "Step %d, no error received, but expected a match to:\n\n%s\n\n", 501 i, step.ExpectError)) 502 break 503 } 504 505 // If there was an error, exit 506 if err != nil { 507 // Perhaps we expected an error? Check if it matches 508 if step.ExpectError != nil { 509 if !step.ExpectError.MatchString(err.Error()) { 510 errored = true 511 t.Error(fmt.Sprintf( 512 "Step %d, expected error:\n\n%s\n\nTo match:\n\n%s\n\n", 513 i, err, step.ExpectError)) 514 break 515 } 516 } else { 517 errored = true 518 t.Error(fmt.Sprintf( 519 "Step %d error: %s", i, err)) 520 break 521 } 522 } 523 524 // If we've never checked an id-only refresh and our state isn't 525 // empty, find the first resource and test it. 526 if idRefresh && idRefreshCheck == nil && !state.Empty() { 527 // Find the first non-nil resource in the state 528 for _, m := range state.Modules { 529 if len(m.Resources) > 0 { 530 if v, ok := m.Resources[c.IDRefreshName]; ok { 531 idRefreshCheck = v 532 } 533 534 break 535 } 536 } 537 538 // If we have an instance to check for refreshes, do it 539 // immediately. We do it in the middle of another test 540 // because it shouldn't affect the overall state (refresh 541 // is read-only semantically) and we want to fail early if 542 // this fails. If refresh isn't read-only, then this will have 543 // caught a different bug. 544 if idRefreshCheck != nil { 545 log.Printf( 546 "[WARN] Test: Running ID-only refresh check on %s", 547 idRefreshCheck.Primary.ID) 548 if err := testIDOnlyRefresh(c, opts, step, idRefreshCheck); err != nil { 549 log.Printf("[ERROR] Test: ID-only test failed: %s", err) 550 t.Error(fmt.Sprintf( 551 "[ERROR] Test: ID-only test failed: %s", err)) 552 break 553 } 554 } 555 } 556 } 557 558 // If we never checked an id-only refresh, it is a failure. 559 if idRefresh { 560 if !errored && len(c.Steps) > 0 && idRefreshCheck == nil { 561 t.Error("ID-only refresh check never ran.") 562 } 563 } 564 565 // If we have a state, then run the destroy 566 if state != nil { 567 lastStep := c.Steps[len(c.Steps)-1] 568 destroyStep := TestStep{ 569 Config: lastStep.Config, 570 Check: c.CheckDestroy, 571 Destroy: true, 572 PreventDiskCleanup: lastStep.PreventDiskCleanup, 573 PreventPostDestroyRefresh: c.PreventPostDestroyRefresh, 574 } 575 576 log.Printf("[WARN] Test: Executing destroy step") 577 state, err := testStep(opts, state, destroyStep) 578 if err != nil { 579 t.Error(fmt.Sprintf( 580 "Error destroying resource! WARNING: Dangling resources\n"+ 581 "may exist. The full state and error is shown below.\n\n"+ 582 "Error: %s\n\nState: %s", 583 err, 584 state)) 585 } 586 } else { 587 log.Printf("[WARN] Skipping destroy test since there is no state.") 588 } 589 } 590 591 // testProviderConfig takes the list of Providers in a TestCase and returns a 592 // config with only empty provider blocks. This is useful for Import, where no 593 // config is provided, but the providers must be defined. 594 func testProviderConfig(c TestCase) string { 595 var lines []string 596 for p := range c.Providers { 597 lines = append(lines, fmt.Sprintf("provider %q {}\n", p)) 598 } 599 600 return strings.Join(lines, "") 601 } 602 603 // testProviderResolver is a helper to build a ResourceProviderResolver 604 // with pre instantiated ResourceProviders, so that we can reset them for the 605 // test, while only calling the factory function once. 606 // Any errors are stored so that they can be returned by the factory in 607 // terraform to match non-test behavior. 608 func testProviderResolver(c TestCase) (terraform.ResourceProviderResolver, error) { 609 ctxProviders := c.ProviderFactories 610 if ctxProviders == nil { 611 ctxProviders = make(map[string]terraform.ResourceProviderFactory) 612 } 613 614 // add any fixed providers 615 for k, p := range c.Providers { 616 ctxProviders[k] = terraform.ResourceProviderFactoryFixed(p) 617 } 618 619 // reset the providers if needed 620 for k, pf := range ctxProviders { 621 // we can ignore any errors here, if we don't have a provider to reset 622 // the error will be handled later 623 p, err := pf() 624 if err != nil { 625 return nil, err 626 } 627 if p, ok := p.(TestProvider); ok { 628 err := p.TestReset() 629 if err != nil { 630 return nil, fmt.Errorf("[ERROR] failed to reset provider %q: %s", k, err) 631 } 632 } 633 } 634 635 return terraform.ResourceProviderResolverFixed(ctxProviders), nil 636 } 637 638 // UnitTest is a helper to force the acceptance testing harness to run in the 639 // normal unit test suite. This should only be used for resource that don't 640 // have any external dependencies. 641 func UnitTest(t TestT, c TestCase) { 642 c.IsUnitTest = true 643 Test(t, c) 644 } 645 646 func testIDOnlyRefresh(c TestCase, opts terraform.ContextOpts, step TestStep, r *terraform.ResourceState) error { 647 // TODO: We guard by this right now so master doesn't explode. We 648 // need to remove this eventually to make this part of the normal tests. 649 if os.Getenv("TF_ACC_IDONLY") == "" { 650 return nil 651 } 652 653 name := fmt.Sprintf("%s.foo", r.Type) 654 655 // Build the state. The state is just the resource with an ID. There 656 // are no attributes. We only set what is needed to perform a refresh. 657 state := terraform.NewState() 658 state.RootModule().Resources[name] = &terraform.ResourceState{ 659 Type: r.Type, 660 Primary: &terraform.InstanceState{ 661 ID: r.Primary.ID, 662 }, 663 } 664 665 // Create the config module. We use the full config because Refresh 666 // doesn't have access to it and we may need things like provider 667 // configurations. The initial implementation of id-only checks used 668 // an empty config module, but that caused the aforementioned problems. 669 mod, err := testModule(opts, step) 670 if err != nil { 671 return err 672 } 673 674 // Initialize the context 675 opts.Module = mod 676 opts.State = state 677 ctx, err := terraform.NewContext(&opts) 678 if err != nil { 679 return err 680 } 681 if diags := ctx.Validate(); len(diags) > 0 { 682 if diags.HasErrors() { 683 return errwrap.Wrapf("config is invalid: {{err}}", diags.Err()) 684 } 685 686 log.Printf("[WARN] Config warnings:\n%s", diags.Err().Error()) 687 } 688 689 // Refresh! 690 state, err = ctx.Refresh() 691 if err != nil { 692 return fmt.Errorf("Error refreshing: %s", err) 693 } 694 695 // Verify attribute equivalence. 696 actualR := state.RootModule().Resources[name] 697 if actualR == nil { 698 return fmt.Errorf("Resource gone!") 699 } 700 if actualR.Primary == nil { 701 return fmt.Errorf("Resource has no primary instance") 702 } 703 actual := actualR.Primary.Attributes 704 expected := r.Primary.Attributes 705 // Remove fields we're ignoring 706 for _, v := range c.IDRefreshIgnore { 707 for k, _ := range actual { 708 if strings.HasPrefix(k, v) { 709 delete(actual, k) 710 } 711 } 712 for k, _ := range expected { 713 if strings.HasPrefix(k, v) { 714 delete(expected, k) 715 } 716 } 717 } 718 719 if !reflect.DeepEqual(actual, expected) { 720 // Determine only the different attributes 721 for k, v := range expected { 722 if av, ok := actual[k]; ok && v == av { 723 delete(expected, k) 724 delete(actual, k) 725 } 726 } 727 728 spewConf := spew.NewDefaultConfig() 729 spewConf.SortKeys = true 730 return fmt.Errorf( 731 "Attributes not equivalent. Difference is shown below. Top is actual, bottom is expected."+ 732 "\n\n%s\n\n%s", 733 spewConf.Sdump(actual), spewConf.Sdump(expected)) 734 } 735 736 return nil 737 } 738 739 func testModule(opts terraform.ContextOpts, step TestStep) (*module.Tree, error) { 740 if step.PreConfig != nil { 741 step.PreConfig() 742 } 743 744 cfgPath, err := ioutil.TempDir("", "tf-test") 745 if err != nil { 746 return nil, fmt.Errorf( 747 "Error creating temporary directory for config: %s", err) 748 } 749 750 if step.PreventDiskCleanup { 751 log.Printf("[INFO] Skipping defer os.RemoveAll call") 752 } else { 753 defer os.RemoveAll(cfgPath) 754 } 755 756 // Write the configuration 757 cfgF, err := os.Create(filepath.Join(cfgPath, "main.tf")) 758 if err != nil { 759 return nil, fmt.Errorf( 760 "Error creating temporary file for config: %s", err) 761 } 762 763 _, err = io.Copy(cfgF, strings.NewReader(step.Config)) 764 cfgF.Close() 765 if err != nil { 766 return nil, fmt.Errorf( 767 "Error creating temporary file for config: %s", err) 768 } 769 770 // Parse the configuration 771 mod, err := module.NewTreeModule("", cfgPath) 772 if err != nil { 773 return nil, fmt.Errorf( 774 "Error loading configuration: %s", err) 775 } 776 777 // Load the modules 778 modStorage := &module.Storage{ 779 StorageDir: filepath.Join(cfgPath, ".tfmodules"), 780 Mode: module.GetModeGet, 781 } 782 err = mod.Load(modStorage) 783 if err != nil { 784 return nil, fmt.Errorf("Error downloading modules: %s", err) 785 } 786 787 return mod, nil 788 } 789 790 func testResource(c TestStep, state *terraform.State) (*terraform.ResourceState, error) { 791 if c.ResourceName == "" { 792 return nil, fmt.Errorf("ResourceName must be set in TestStep") 793 } 794 795 for _, m := range state.Modules { 796 if len(m.Resources) > 0 { 797 if v, ok := m.Resources[c.ResourceName]; ok { 798 return v, nil 799 } 800 } 801 } 802 803 return nil, fmt.Errorf( 804 "Resource specified by ResourceName couldn't be found: %s", c.ResourceName) 805 } 806 807 // ComposeTestCheckFunc lets you compose multiple TestCheckFuncs into 808 // a single TestCheckFunc. 809 // 810 // As a user testing their provider, this lets you decompose your checks 811 // into smaller pieces more easily. 812 func ComposeTestCheckFunc(fs ...TestCheckFunc) TestCheckFunc { 813 return func(s *terraform.State) error { 814 for i, f := range fs { 815 if err := f(s); err != nil { 816 return fmt.Errorf("Check %d/%d error: %s", i+1, len(fs), err) 817 } 818 } 819 820 return nil 821 } 822 } 823 824 // ComposeAggregateTestCheckFunc lets you compose multiple TestCheckFuncs into 825 // a single TestCheckFunc. 826 // 827 // As a user testing their provider, this lets you decompose your checks 828 // into smaller pieces more easily. 829 // 830 // Unlike ComposeTestCheckFunc, ComposeAggergateTestCheckFunc runs _all_ of the 831 // TestCheckFuncs and aggregates failures. 832 func ComposeAggregateTestCheckFunc(fs ...TestCheckFunc) TestCheckFunc { 833 return func(s *terraform.State) error { 834 var result *multierror.Error 835 836 for i, f := range fs { 837 if err := f(s); err != nil { 838 result = multierror.Append(result, fmt.Errorf("Check %d/%d error: %s", i+1, len(fs), err)) 839 } 840 } 841 842 return result.ErrorOrNil() 843 } 844 } 845 846 // TestCheckResourceAttrSet is a TestCheckFunc which ensures a value 847 // exists in state for the given name/key combination. It is useful when 848 // testing that computed values were set, when it is not possible to 849 // know ahead of time what the values will be. 850 func TestCheckResourceAttrSet(name, key string) TestCheckFunc { 851 return func(s *terraform.State) error { 852 is, err := primaryInstanceState(s, name) 853 if err != nil { 854 return err 855 } 856 857 return testCheckResourceAttrSet(is, name, key) 858 } 859 } 860 861 // TestCheckModuleResourceAttrSet - as per TestCheckResourceAttrSet but with 862 // support for non-root modules 863 func TestCheckModuleResourceAttrSet(mp []string, name string, key string) TestCheckFunc { 864 return func(s *terraform.State) error { 865 is, err := modulePathPrimaryInstanceState(s, mp, name) 866 if err != nil { 867 return err 868 } 869 870 return testCheckResourceAttrSet(is, name, key) 871 } 872 } 873 874 func testCheckResourceAttrSet(is *terraform.InstanceState, name string, key string) error { 875 if val, ok := is.Attributes[key]; !ok || val == "" { 876 return fmt.Errorf("%s: Attribute '%s' expected to be set", name, key) 877 } 878 879 return nil 880 } 881 882 // TestCheckResourceAttr is a TestCheckFunc which validates 883 // the value in state for the given name/key combination. 884 func TestCheckResourceAttr(name, key, value string) TestCheckFunc { 885 return func(s *terraform.State) error { 886 is, err := primaryInstanceState(s, name) 887 if err != nil { 888 return err 889 } 890 891 return testCheckResourceAttr(is, name, key, value) 892 } 893 } 894 895 // TestCheckModuleResourceAttr - as per TestCheckResourceAttr but with 896 // support for non-root modules 897 func TestCheckModuleResourceAttr(mp []string, name string, key string, value string) TestCheckFunc { 898 return func(s *terraform.State) error { 899 is, err := modulePathPrimaryInstanceState(s, mp, name) 900 if err != nil { 901 return err 902 } 903 904 return testCheckResourceAttr(is, name, key, value) 905 } 906 } 907 908 func testCheckResourceAttr(is *terraform.InstanceState, name string, key string, value string) error { 909 if v, ok := is.Attributes[key]; !ok || v != value { 910 if !ok { 911 return fmt.Errorf("%s: Attribute '%s' not found", name, key) 912 } 913 914 return fmt.Errorf( 915 "%s: Attribute '%s' expected %#v, got %#v", 916 name, 917 key, 918 value, 919 v) 920 } 921 return nil 922 } 923 924 // TestCheckNoResourceAttr is a TestCheckFunc which ensures that 925 // NO value exists in state for the given name/key combination. 926 func TestCheckNoResourceAttr(name, key string) TestCheckFunc { 927 return func(s *terraform.State) error { 928 is, err := primaryInstanceState(s, name) 929 if err != nil { 930 return err 931 } 932 933 return testCheckNoResourceAttr(is, name, key) 934 } 935 } 936 937 // TestCheckModuleNoResourceAttr - as per TestCheckNoResourceAttr but with 938 // support for non-root modules 939 func TestCheckModuleNoResourceAttr(mp []string, name string, key string) TestCheckFunc { 940 return func(s *terraform.State) error { 941 is, err := modulePathPrimaryInstanceState(s, mp, name) 942 if err != nil { 943 return err 944 } 945 946 return testCheckNoResourceAttr(is, name, key) 947 } 948 } 949 950 func testCheckNoResourceAttr(is *terraform.InstanceState, name string, key string) error { 951 if _, ok := is.Attributes[key]; ok { 952 return fmt.Errorf("%s: Attribute '%s' found when not expected", name, key) 953 } 954 955 return nil 956 } 957 958 // TestMatchResourceAttr is a TestCheckFunc which checks that the value 959 // in state for the given name/key combination matches the given regex. 960 func TestMatchResourceAttr(name, key string, r *regexp.Regexp) TestCheckFunc { 961 return func(s *terraform.State) error { 962 is, err := primaryInstanceState(s, name) 963 if err != nil { 964 return err 965 } 966 967 return testMatchResourceAttr(is, name, key, r) 968 } 969 } 970 971 // TestModuleMatchResourceAttr - as per TestMatchResourceAttr but with 972 // support for non-root modules 973 func TestModuleMatchResourceAttr(mp []string, name string, key string, r *regexp.Regexp) TestCheckFunc { 974 return func(s *terraform.State) error { 975 is, err := modulePathPrimaryInstanceState(s, mp, name) 976 if err != nil { 977 return err 978 } 979 980 return testMatchResourceAttr(is, name, key, r) 981 } 982 } 983 984 func testMatchResourceAttr(is *terraform.InstanceState, name string, key string, r *regexp.Regexp) error { 985 if !r.MatchString(is.Attributes[key]) { 986 return fmt.Errorf( 987 "%s: Attribute '%s' didn't match %q, got %#v", 988 name, 989 key, 990 r.String(), 991 is.Attributes[key]) 992 } 993 994 return nil 995 } 996 997 // TestCheckResourceAttrPtr is like TestCheckResourceAttr except the 998 // value is a pointer so that it can be updated while the test is running. 999 // It will only be dereferenced at the point this step is run. 1000 func TestCheckResourceAttrPtr(name string, key string, value *string) TestCheckFunc { 1001 return func(s *terraform.State) error { 1002 return TestCheckResourceAttr(name, key, *value)(s) 1003 } 1004 } 1005 1006 // TestCheckModuleResourceAttrPtr - as per TestCheckResourceAttrPtr but with 1007 // support for non-root modules 1008 func TestCheckModuleResourceAttrPtr(mp []string, name string, key string, value *string) TestCheckFunc { 1009 return func(s *terraform.State) error { 1010 return TestCheckModuleResourceAttr(mp, name, key, *value)(s) 1011 } 1012 } 1013 1014 // TestCheckResourceAttrPair is a TestCheckFunc which validates that the values 1015 // in state for a pair of name/key combinations are equal. 1016 func TestCheckResourceAttrPair(nameFirst, keyFirst, nameSecond, keySecond string) TestCheckFunc { 1017 return func(s *terraform.State) error { 1018 isFirst, err := primaryInstanceState(s, nameFirst) 1019 if err != nil { 1020 return err 1021 } 1022 1023 isSecond, err := primaryInstanceState(s, nameSecond) 1024 if err != nil { 1025 return err 1026 } 1027 1028 return testCheckResourceAttrPair(isFirst, nameFirst, keyFirst, isSecond, nameSecond, keySecond) 1029 } 1030 } 1031 1032 // TestCheckModuleResourceAttrPair - as per TestCheckResourceAttrPair but with 1033 // support for non-root modules 1034 func TestCheckModuleResourceAttrPair(mpFirst []string, nameFirst string, keyFirst string, mpSecond []string, nameSecond string, keySecond string) TestCheckFunc { 1035 return func(s *terraform.State) error { 1036 isFirst, err := modulePathPrimaryInstanceState(s, mpFirst, nameFirst) 1037 if err != nil { 1038 return err 1039 } 1040 1041 isSecond, err := modulePathPrimaryInstanceState(s, mpSecond, nameSecond) 1042 if err != nil { 1043 return err 1044 } 1045 1046 return testCheckResourceAttrPair(isFirst, nameFirst, keyFirst, isSecond, nameSecond, keySecond) 1047 } 1048 } 1049 1050 func testCheckResourceAttrPair(isFirst *terraform.InstanceState, nameFirst string, keyFirst string, isSecond *terraform.InstanceState, nameSecond string, keySecond string) error { 1051 vFirst, ok := isFirst.Attributes[keyFirst] 1052 if !ok { 1053 return fmt.Errorf("%s: Attribute '%s' not found", nameFirst, keyFirst) 1054 } 1055 1056 vSecond, ok := isSecond.Attributes[keySecond] 1057 if !ok { 1058 return fmt.Errorf("%s: Attribute '%s' not found", nameSecond, keySecond) 1059 } 1060 1061 if vFirst != vSecond { 1062 return fmt.Errorf( 1063 "%s: Attribute '%s' expected %#v, got %#v", 1064 nameFirst, 1065 keyFirst, 1066 vSecond, 1067 vFirst) 1068 } 1069 1070 return nil 1071 } 1072 1073 // TestCheckOutput checks an output in the Terraform configuration 1074 func TestCheckOutput(name, value string) TestCheckFunc { 1075 return func(s *terraform.State) error { 1076 ms := s.RootModule() 1077 rs, ok := ms.Outputs[name] 1078 if !ok { 1079 return fmt.Errorf("Not found: %s", name) 1080 } 1081 1082 if rs.Value != value { 1083 return fmt.Errorf( 1084 "Output '%s': expected %#v, got %#v", 1085 name, 1086 value, 1087 rs) 1088 } 1089 1090 return nil 1091 } 1092 } 1093 1094 func TestMatchOutput(name string, r *regexp.Regexp) 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 !r.MatchString(rs.Value.(string)) { 1103 return fmt.Errorf( 1104 "Output '%s': %#v didn't match %q", 1105 name, 1106 rs, 1107 r.String()) 1108 } 1109 1110 return nil 1111 } 1112 } 1113 1114 // TestT is the interface used to handle the test lifecycle of a test. 1115 // 1116 // Users should just use a *testing.T object, which implements this. 1117 type TestT interface { 1118 Error(args ...interface{}) 1119 Fatal(args ...interface{}) 1120 Skip(args ...interface{}) 1121 Name() string 1122 } 1123 1124 // This is set to true by unit tests to alter some behavior 1125 var testTesting = false 1126 1127 // modulePrimaryInstanceState returns the instance state for the given resource 1128 // name in a ModuleState 1129 func modulePrimaryInstanceState(s *terraform.State, ms *terraform.ModuleState, name string) (*terraform.InstanceState, error) { 1130 rs, ok := ms.Resources[name] 1131 if !ok { 1132 return nil, fmt.Errorf("Not found: %s in %s", name, ms.Path) 1133 } 1134 1135 is := rs.Primary 1136 if is == nil { 1137 return nil, fmt.Errorf("No primary instance: %s in %s", name, ms.Path) 1138 } 1139 1140 return is, nil 1141 } 1142 1143 // modulePathPrimaryInstanceState returns the primary instance state for the 1144 // given resource name in a given module path. 1145 func modulePathPrimaryInstanceState(s *terraform.State, mp []string, name string) (*terraform.InstanceState, error) { 1146 ms := s.ModuleByPath(mp) 1147 if ms == nil { 1148 return nil, fmt.Errorf("No module found at: %s", mp) 1149 } 1150 1151 return modulePrimaryInstanceState(s, ms, name) 1152 } 1153 1154 // primaryInstanceState returns the primary instance state for the given 1155 // resource name in the root module. 1156 func primaryInstanceState(s *terraform.State, name string) (*terraform.InstanceState, error) { 1157 ms := s.RootModule() 1158 return modulePrimaryInstanceState(s, ms, name) 1159 }