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