github.com/richardbowden/terraform@v0.6.12-0.20160901200758-30ea22c25211/helper/resource/testing.go (about) 1 package resource 2 3 import ( 4 "fmt" 5 "io" 6 "io/ioutil" 7 "log" 8 "os" 9 "path/filepath" 10 "reflect" 11 "regexp" 12 "strings" 13 "testing" 14 15 "github.com/davecgh/go-spew/spew" 16 "github.com/hashicorp/go-getter" 17 "github.com/hashicorp/go-multierror" 18 "github.com/hashicorp/terraform/config/module" 19 "github.com/hashicorp/terraform/helper/logging" 20 "github.com/hashicorp/terraform/terraform" 21 ) 22 23 const TestEnvVar = "TF_ACC" 24 25 // TestCheckFunc is the callback type used with acceptance tests to check 26 // the state of a resource. The state passed in is the latest state known, 27 // or in the case of being after a destroy, it is the last known state when 28 // it was created. 29 type TestCheckFunc func(*terraform.State) error 30 31 // ImportStateCheckFunc is the check function for ImportState tests 32 type ImportStateCheckFunc func([]*terraform.InstanceState) error 33 34 // TestCase is a single acceptance test case used to test the apply/destroy 35 // lifecycle of a resource in a specific configuration. 36 // 37 // When the destroy plan is executed, the config from the last TestStep 38 // is used to plan it. 39 type TestCase struct { 40 // IsUnitTest allows a test to run regardless of the TF_ACC 41 // environment variable. This should be used with care - only for 42 // fast tests on local resources (e.g. remote state with a local 43 // backend) but can be used to increase confidence in correct 44 // operation of Terraform without waiting for a full acctest run. 45 IsUnitTest bool 46 47 // PreCheck, if non-nil, will be called before any test steps are 48 // executed. It will only be executed in the case that the steps 49 // would run, so it can be used for some validation before running 50 // acceptance tests, such as verifying that keys are setup. 51 PreCheck func() 52 53 // Providers is the ResourceProvider that will be under test. 54 // 55 // Alternately, ProviderFactories can be specified for the providers 56 // that are valid. This takes priority over Providers. 57 // 58 // The end effect of each is the same: specifying the providers that 59 // are used within the tests. 60 Providers map[string]terraform.ResourceProvider 61 ProviderFactories map[string]terraform.ResourceProviderFactory 62 63 // PreventPostDestroyRefresh can be set to true for cases where data sources 64 // are tested alongside real resources 65 PreventPostDestroyRefresh bool 66 67 // CheckDestroy is called after the resource is finally destroyed 68 // to allow the tester to test that the resource is truly gone. 69 CheckDestroy TestCheckFunc 70 71 // Steps are the apply sequences done within the context of the 72 // same state. Each step can have its own check to verify correctness. 73 Steps []TestStep 74 75 // The settings below control the "ID-only refresh test." This is 76 // an enabled-by-default test that tests that a refresh can be 77 // refreshed with only an ID to result in the same attributes. 78 // This validates completeness of Refresh. 79 // 80 // IDRefreshName is the name of the resource to check. This will 81 // default to the first non-nil primary resource in the state. 82 // 83 // IDRefreshIgnore is a list of configuration keys that will be ignored. 84 IDRefreshName string 85 IDRefreshIgnore []string 86 } 87 88 // TestStep is a single apply sequence of a test, done within the 89 // context of a state. 90 // 91 // Multiple TestSteps can be sequenced in a Test to allow testing 92 // potentially complex update logic. In general, simply create/destroy 93 // tests will only need one step. 94 type TestStep struct { 95 // ResourceName should be set to the name of the resource 96 // that is being tested. Example: "aws_instance.foo". Various test 97 // modes use this to auto-detect state information. 98 // 99 // This is only required if the test mode settings below say it is 100 // for the mode you're using. 101 ResourceName string 102 103 // PreConfig is called before the Config is applied to perform any per-step 104 // setup that needs to happen. This is called regardless of "test mode" 105 // below. 106 PreConfig func() 107 108 //--------------------------------------------------------------- 109 // Test modes. One of the following groups of settings must be 110 // set to determine what the test step will do. Ideally we would've 111 // used Go interfaces here but there are now hundreds of tests we don't 112 // want to re-type so instead we just determine which step logic 113 // to run based on what settings below are set. 114 //--------------------------------------------------------------- 115 116 //--------------------------------------------------------------- 117 // Plan, Apply testing 118 //--------------------------------------------------------------- 119 120 // Config a string of the configuration to give to Terraform. If this 121 // is set, then the TestCase will execute this step with the same logic 122 // as a `terraform apply`. 123 Config string 124 125 // Check is called after the Config is applied. Use this step to 126 // make your own API calls to check the status of things, and to 127 // inspect the format of the ResourceState itself. 128 // 129 // If an error is returned, the test will fail. In this case, a 130 // destroy plan will still be attempted. 131 // 132 // If this is nil, no check is done on this step. 133 Check TestCheckFunc 134 135 // Destroy will create a destroy plan if set to true. 136 Destroy bool 137 138 // ExpectNonEmptyPlan can be set to true for specific types of tests that are 139 // looking to verify that a diff occurs 140 ExpectNonEmptyPlan bool 141 142 // ExpectError allows the construction of test cases that we expect to fail 143 // with an error. The specified regexp must match against the error for the 144 // test to pass. 145 ExpectError *regexp.Regexp 146 147 // PreventPostDestroyRefresh can be set to true for cases where data sources 148 // are tested alongside real resources 149 PreventPostDestroyRefresh bool 150 151 //--------------------------------------------------------------- 152 // ImportState testing 153 //--------------------------------------------------------------- 154 155 // ImportState, if true, will test the functionality of ImportState 156 // by importing the resource with ResourceName (must be set) and the 157 // ID of that resource. 158 ImportState bool 159 160 // ImportStateId is the ID to perform an ImportState operation with. 161 // This is optional. If it isn't set, then the resource ID is automatically 162 // determined by inspecting the state for ResourceName's ID. 163 ImportStateId string 164 165 // ImportStateCheck checks the results of ImportState. It should be 166 // used to verify that the resulting value of ImportState has the 167 // proper resources, IDs, and attributes. 168 ImportStateCheck ImportStateCheckFunc 169 170 // ImportStateVerify, if true, will also check that the state values 171 // that are finally put into the state after import match for all the 172 // IDs returned by the Import. 173 // 174 // ImportStateVerifyIgnore are fields that should not be verified to 175 // be equal. These can be set to ephemeral fields or fields that can't 176 // be refreshed and don't matter. 177 ImportStateVerify bool 178 ImportStateVerifyIgnore []string 179 } 180 181 // Test performs an acceptance test on a resource. 182 // 183 // Tests are not run unless an environmental variable "TF_ACC" is 184 // set to some non-empty value. This is to avoid test cases surprising 185 // a user by creating real resources. 186 // 187 // Tests will fail unless the verbose flag (`go test -v`, or explicitly 188 // the "-test.v" flag) is set. Because some acceptance tests take quite 189 // long, we require the verbose flag so users are able to see progress 190 // output. 191 func Test(t TestT, c TestCase) { 192 // We only run acceptance tests if an env var is set because they're 193 // slow and generally require some outside configuration. You can opt out 194 // of this with OverrideEnvVar on individual TestCases. 195 if os.Getenv(TestEnvVar) == "" && !c.IsUnitTest { 196 t.Skip(fmt.Sprintf( 197 "Acceptance tests skipped unless env '%s' set", 198 TestEnvVar)) 199 return 200 } 201 202 logWriter, err := logging.LogOutput() 203 if err != nil { 204 t.Error(fmt.Errorf("error setting up logging: %s", err)) 205 } 206 log.SetOutput(logWriter) 207 208 // We require verbose mode so that the user knows what is going on. 209 if !testTesting && !testing.Verbose() && !c.IsUnitTest { 210 t.Fatal("Acceptance tests must be run with the -v flag on tests") 211 return 212 } 213 214 // Run the PreCheck if we have it 215 if c.PreCheck != nil { 216 c.PreCheck() 217 } 218 219 // Build our context options that we can 220 ctxProviders := c.ProviderFactories 221 if ctxProviders == nil { 222 ctxProviders = make(map[string]terraform.ResourceProviderFactory) 223 for k, p := range c.Providers { 224 ctxProviders[k] = terraform.ResourceProviderFactoryFixed(p) 225 } 226 } 227 opts := terraform.ContextOpts{Providers: ctxProviders} 228 229 // A single state variable to track the lifecycle, starting with no state 230 var state *terraform.State 231 232 // Go through each step and run it 233 var idRefreshCheck *terraform.ResourceState 234 idRefresh := c.IDRefreshName != "" 235 errored := false 236 for i, step := range c.Steps { 237 var err error 238 log.Printf("[WARN] Test: Executing step %d", i) 239 240 // Determine the test mode to execute 241 if step.Config != "" { 242 state, err = testStepConfig(opts, state, step) 243 } else if step.ImportState { 244 state, err = testStepImportState(opts, state, step) 245 } else { 246 err = fmt.Errorf( 247 "unknown test mode for step. Please see TestStep docs\n\n%#v", 248 step) 249 } 250 251 // If there was an error, exit 252 if err != nil { 253 // Perhaps we expected an error? Check if it matches 254 if step.ExpectError != nil { 255 if !step.ExpectError.MatchString(err.Error()) { 256 errored = true 257 t.Error(fmt.Sprintf( 258 "Step %d, expected error:\n\n%s\n\nTo match:\n\n%s\n\n", 259 i, err, step.ExpectError)) 260 break 261 } 262 } else { 263 errored = true 264 t.Error(fmt.Sprintf( 265 "Step %d error: %s", i, err)) 266 break 267 } 268 } 269 270 // If we've never checked an id-only refresh and our state isn't 271 // empty, find the first resource and test it. 272 if idRefresh && idRefreshCheck == nil && !state.Empty() { 273 // Find the first non-nil resource in the state 274 for _, m := range state.Modules { 275 if len(m.Resources) > 0 { 276 if v, ok := m.Resources[c.IDRefreshName]; ok { 277 idRefreshCheck = v 278 } 279 280 break 281 } 282 } 283 284 // If we have an instance to check for refreshes, do it 285 // immediately. We do it in the middle of another test 286 // because it shouldn't affect the overall state (refresh 287 // is read-only semantically) and we want to fail early if 288 // this fails. If refresh isn't read-only, then this will have 289 // caught a different bug. 290 if idRefreshCheck != nil { 291 log.Printf( 292 "[WARN] Test: Running ID-only refresh check on %s", 293 idRefreshCheck.Primary.ID) 294 if err := testIDOnlyRefresh(c, opts, step, idRefreshCheck); err != nil { 295 log.Printf("[ERROR] Test: ID-only test failed: %s", err) 296 t.Error(fmt.Sprintf( 297 "[ERROR] Test: ID-only test failed: %s", err)) 298 break 299 } 300 } 301 } 302 } 303 304 // If we never checked an id-only refresh, it is a failure. 305 if idRefresh { 306 if !errored && len(c.Steps) > 0 && idRefreshCheck == nil { 307 t.Error("ID-only refresh check never ran.") 308 } 309 } 310 311 // If we have a state, then run the destroy 312 if state != nil { 313 lastStep := c.Steps[len(c.Steps)-1] 314 destroyStep := TestStep{ 315 Config: lastStep.Config, 316 Check: c.CheckDestroy, 317 Destroy: true, 318 PreventPostDestroyRefresh: c.PreventPostDestroyRefresh, 319 } 320 321 log.Printf("[WARN] Test: Executing destroy step") 322 state, err := testStep(opts, state, destroyStep) 323 if err != nil { 324 t.Error(fmt.Sprintf( 325 "Error destroying resource! WARNING: Dangling resources\n"+ 326 "may exist. The full state and error is shown below.\n\n"+ 327 "Error: %s\n\nState: %s", 328 err, 329 state)) 330 } 331 } else { 332 log.Printf("[WARN] Skipping destroy test since there is no state.") 333 } 334 } 335 336 // UnitTest is a helper to force the acceptance testing harness to run in the 337 // normal unit test suite. This should only be used for resource that don't 338 // have any external dependencies. 339 func UnitTest(t TestT, c TestCase) { 340 c.IsUnitTest = true 341 Test(t, c) 342 } 343 344 func testIDOnlyRefresh(c TestCase, opts terraform.ContextOpts, step TestStep, r *terraform.ResourceState) error { 345 // TODO: We guard by this right now so master doesn't explode. We 346 // need to remove this eventually to make this part of the normal tests. 347 if os.Getenv("TF_ACC_IDONLY") == "" { 348 return nil 349 } 350 351 name := fmt.Sprintf("%s.foo", r.Type) 352 353 // Build the state. The state is just the resource with an ID. There 354 // are no attributes. We only set what is needed to perform a refresh. 355 state := terraform.NewState() 356 state.RootModule().Resources[name] = &terraform.ResourceState{ 357 Type: r.Type, 358 Primary: &terraform.InstanceState{ 359 ID: r.Primary.ID, 360 }, 361 } 362 363 // Create the config module. We use the full config because Refresh 364 // doesn't have access to it and we may need things like provider 365 // configurations. The initial implementation of id-only checks used 366 // an empty config module, but that caused the aforementioned problems. 367 mod, err := testModule(opts, step) 368 if err != nil { 369 return err 370 } 371 372 // Initialize the context 373 opts.Module = mod 374 opts.State = state 375 ctx, err := terraform.NewContext(&opts) 376 if err != nil { 377 return err 378 } 379 if ws, es := ctx.Validate(); len(ws) > 0 || len(es) > 0 { 380 if len(es) > 0 { 381 estrs := make([]string, len(es)) 382 for i, e := range es { 383 estrs[i] = e.Error() 384 } 385 return fmt.Errorf( 386 "Configuration is invalid.\n\nWarnings: %#v\n\nErrors: %#v", 387 ws, estrs) 388 } 389 390 log.Printf("[WARN] Config warnings: %#v", ws) 391 } 392 393 // Refresh! 394 state, err = ctx.Refresh() 395 if err != nil { 396 return fmt.Errorf("Error refreshing: %s", err) 397 } 398 399 // Verify attribute equivalence. 400 actualR := state.RootModule().Resources[name] 401 if actualR == nil { 402 return fmt.Errorf("Resource gone!") 403 } 404 if actualR.Primary == nil { 405 return fmt.Errorf("Resource has no primary instance") 406 } 407 actual := actualR.Primary.Attributes 408 expected := r.Primary.Attributes 409 // Remove fields we're ignoring 410 for _, v := range c.IDRefreshIgnore { 411 for k, _ := range actual { 412 if strings.HasPrefix(k, v) { 413 delete(actual, k) 414 } 415 } 416 for k, _ := range expected { 417 if strings.HasPrefix(k, v) { 418 delete(expected, k) 419 } 420 } 421 } 422 423 if !reflect.DeepEqual(actual, expected) { 424 // Determine only the different attributes 425 for k, v := range expected { 426 if av, ok := actual[k]; ok && v == av { 427 delete(expected, k) 428 delete(actual, k) 429 } 430 } 431 432 spewConf := spew.NewDefaultConfig() 433 spewConf.SortKeys = true 434 return fmt.Errorf( 435 "Attributes not equivalent. Difference is shown below. Top is actual, bottom is expected."+ 436 "\n\n%s\n\n%s", 437 spewConf.Sdump(actual), spewConf.Sdump(expected)) 438 } 439 440 return nil 441 } 442 443 func testModule( 444 opts terraform.ContextOpts, 445 step TestStep) (*module.Tree, error) { 446 if step.PreConfig != nil { 447 step.PreConfig() 448 } 449 450 cfgPath, err := ioutil.TempDir("", "tf-test") 451 if err != nil { 452 return nil, fmt.Errorf( 453 "Error creating temporary directory for config: %s", err) 454 } 455 defer os.RemoveAll(cfgPath) 456 457 // Write the configuration 458 cfgF, err := os.Create(filepath.Join(cfgPath, "main.tf")) 459 if err != nil { 460 return nil, fmt.Errorf( 461 "Error creating temporary file for config: %s", err) 462 } 463 464 _, err = io.Copy(cfgF, strings.NewReader(step.Config)) 465 cfgF.Close() 466 if err != nil { 467 return nil, fmt.Errorf( 468 "Error creating temporary file for config: %s", err) 469 } 470 471 // Parse the configuration 472 mod, err := module.NewTreeModule("", cfgPath) 473 if err != nil { 474 return nil, fmt.Errorf( 475 "Error loading configuration: %s", err) 476 } 477 478 // Load the modules 479 modStorage := &getter.FolderStorage{ 480 StorageDir: filepath.Join(cfgPath, ".tfmodules"), 481 } 482 err = mod.Load(modStorage, module.GetModeGet) 483 if err != nil { 484 return nil, fmt.Errorf("Error downloading modules: %s", err) 485 } 486 487 return mod, nil 488 } 489 490 func testResource(c TestStep, state *terraform.State) (*terraform.ResourceState, error) { 491 if c.ResourceName == "" { 492 return nil, fmt.Errorf("ResourceName must be set in TestStep") 493 } 494 495 for _, m := range state.Modules { 496 if len(m.Resources) > 0 { 497 if v, ok := m.Resources[c.ResourceName]; ok { 498 return v, nil 499 } 500 } 501 } 502 503 return nil, fmt.Errorf( 504 "Resource specified by ResourceName couldn't be found: %s", c.ResourceName) 505 } 506 507 // ComposeTestCheckFunc lets you compose multiple TestCheckFuncs into 508 // a single TestCheckFunc. 509 // 510 // As a user testing their provider, this lets you decompose your checks 511 // into smaller pieces more easily. 512 func ComposeTestCheckFunc(fs ...TestCheckFunc) TestCheckFunc { 513 return func(s *terraform.State) error { 514 for i, f := range fs { 515 if err := f(s); err != nil { 516 return fmt.Errorf("Check %d/%d error: %s", i+1, len(fs), err) 517 } 518 } 519 520 return nil 521 } 522 } 523 524 // ComposeAggregateTestCheckFunc lets you compose multiple TestCheckFuncs into 525 // a single TestCheckFunc. 526 // 527 // As a user testing their provider, this lets you decompose your checks 528 // into smaller pieces more easily. 529 // 530 // Unlike ComposeTestCheckFunc, ComposeAggergateTestCheckFunc runs _all_ of the 531 // TestCheckFuncs and aggregates failures. 532 func ComposeAggregateTestCheckFunc(fs ...TestCheckFunc) TestCheckFunc { 533 return func(s *terraform.State) error { 534 var result *multierror.Error 535 536 for i, f := range fs { 537 if err := f(s); err != nil { 538 result = multierror.Append(result, fmt.Errorf("Check %d/%d error: %s", i+1, len(fs), err)) 539 } 540 } 541 542 return result.ErrorOrNil() 543 } 544 } 545 546 // TestCheckResourceAttrSet is a TestCheckFunc which ensures a value 547 // exists in state for the given name/key combination. It is useful when 548 // testing that computed values were set, when it is not possible to 549 // know ahead of time what the values will be. 550 func TestCheckResourceAttrSet(name, key string) TestCheckFunc { 551 return func(s *terraform.State) error { 552 ms := s.RootModule() 553 rs, ok := ms.Resources[name] 554 if !ok { 555 return fmt.Errorf("Not found: %s", name) 556 } 557 558 is := rs.Primary 559 if is == nil { 560 return fmt.Errorf("No primary instance: %s", name) 561 } 562 563 if val, ok := is.Attributes[key]; ok && val != "" { 564 return nil 565 } 566 567 return fmt.Errorf("%s: Attribute '%s' expected to be set", name, key) 568 } 569 } 570 571 func TestCheckResourceAttr(name, key, value string) TestCheckFunc { 572 return func(s *terraform.State) error { 573 ms := s.RootModule() 574 rs, ok := ms.Resources[name] 575 if !ok { 576 return fmt.Errorf("Not found: %s", name) 577 } 578 579 is := rs.Primary 580 if is == nil { 581 return fmt.Errorf("No primary instance: %s", name) 582 } 583 584 if is.Attributes[key] != value { 585 return fmt.Errorf( 586 "%s: Attribute '%s' expected %#v, got %#v", 587 name, 588 key, 589 value, 590 is.Attributes[key]) 591 } 592 593 return nil 594 } 595 } 596 597 func TestMatchResourceAttr(name, key string, r *regexp.Regexp) TestCheckFunc { 598 return func(s *terraform.State) error { 599 ms := s.RootModule() 600 rs, ok := ms.Resources[name] 601 if !ok { 602 return fmt.Errorf("Not found: %s", name) 603 } 604 605 is := rs.Primary 606 if is == nil { 607 return fmt.Errorf("No primary instance: %s", name) 608 } 609 610 if !r.MatchString(is.Attributes[key]) { 611 return fmt.Errorf( 612 "%s: Attribute '%s' didn't match %q, got %#v", 613 name, 614 key, 615 r.String(), 616 is.Attributes[key]) 617 } 618 619 return nil 620 } 621 } 622 623 // TestCheckResourceAttrPtr is like TestCheckResourceAttr except the 624 // value is a pointer so that it can be updated while the test is running. 625 // It will only be dereferenced at the point this step is run. 626 func TestCheckResourceAttrPtr(name string, key string, value *string) TestCheckFunc { 627 return func(s *terraform.State) error { 628 return TestCheckResourceAttr(name, key, *value)(s) 629 } 630 } 631 632 // TestCheckOutput checks an output in the Terraform configuration 633 func TestCheckOutput(name, value string) TestCheckFunc { 634 return func(s *terraform.State) error { 635 ms := s.RootModule() 636 rs, ok := ms.Outputs[name] 637 if !ok { 638 return fmt.Errorf("Not found: %s", name) 639 } 640 641 if rs.Value != value { 642 return fmt.Errorf( 643 "Output '%s': expected %#v, got %#v", 644 name, 645 value, 646 rs) 647 } 648 649 return nil 650 } 651 } 652 653 func TestMatchOutput(name string, r *regexp.Regexp) TestCheckFunc { 654 return func(s *terraform.State) error { 655 ms := s.RootModule() 656 rs, ok := ms.Outputs[name] 657 if !ok { 658 return fmt.Errorf("Not found: %s", name) 659 } 660 661 if !r.MatchString(rs.Value.(string)) { 662 return fmt.Errorf( 663 "Output '%s': %#v didn't match %q", 664 name, 665 rs, 666 r.String()) 667 } 668 669 return nil 670 } 671 } 672 673 // TestT is the interface used to handle the test lifecycle of a test. 674 // 675 // Users should just use a *testing.T object, which implements this. 676 type TestT interface { 677 Error(args ...interface{}) 678 Fatal(args ...interface{}) 679 Skip(args ...interface{}) 680 } 681 682 // This is set to true by unit tests to alter some behavior 683 var testTesting = false