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