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