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