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