diff options
author | Nathan Dench <ndenc2@gmail.com> | 2019-05-24 15:16:44 +1000 |
---|---|---|
committer | Nathan Dench <ndenc2@gmail.com> | 2019-05-24 15:16:44 +1000 |
commit | 107c1cdb09c575aa2f61d97f48d8587eb6bada4c (patch) | |
tree | ca7d008643efc555c388baeaf1d986e0b6b3e28c /vendor/github.com/hashicorp/terraform/helper/resource/testing.go | |
parent | 844b5a68d8af4791755b8f0ad293cc99f5959183 (diff) | |
download | terraform-provider-statuscake-107c1cdb09c575aa2f61d97f48d8587eb6bada4c.tar.gz terraform-provider-statuscake-107c1cdb09c575aa2f61d97f48d8587eb6bada4c.tar.zst terraform-provider-statuscake-107c1cdb09c575aa2f61d97f48d8587eb6bada4c.zip |
Upgrade to 0.12
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/helper/resource/testing.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/helper/resource/testing.go | 298 |
1 files changed, 219 insertions, 79 deletions
diff --git a/vendor/github.com/hashicorp/terraform/helper/resource/testing.go b/vendor/github.com/hashicorp/terraform/helper/resource/testing.go index b97673f..aa7454d 100644 --- a/vendor/github.com/hashicorp/terraform/helper/resource/testing.go +++ b/vendor/github.com/hashicorp/terraform/helper/resource/testing.go | |||
@@ -1,6 +1,7 @@ | |||
1 | package resource | 1 | package resource |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "bytes" | ||
4 | "flag" | 5 | "flag" |
5 | "fmt" | 6 | "fmt" |
6 | "io" | 7 | "io" |
@@ -18,9 +19,18 @@ import ( | |||
18 | "github.com/hashicorp/errwrap" | 19 | "github.com/hashicorp/errwrap" |
19 | "github.com/hashicorp/go-multierror" | 20 | "github.com/hashicorp/go-multierror" |
20 | "github.com/hashicorp/logutils" | 21 | "github.com/hashicorp/logutils" |
21 | "github.com/hashicorp/terraform/config/module" | 22 | "github.com/mitchellh/colorstring" |
23 | |||
24 | "github.com/hashicorp/terraform/addrs" | ||
25 | "github.com/hashicorp/terraform/command/format" | ||
26 | "github.com/hashicorp/terraform/configs" | ||
27 | "github.com/hashicorp/terraform/configs/configload" | ||
22 | "github.com/hashicorp/terraform/helper/logging" | 28 | "github.com/hashicorp/terraform/helper/logging" |
29 | "github.com/hashicorp/terraform/internal/initwd" | ||
30 | "github.com/hashicorp/terraform/providers" | ||
31 | "github.com/hashicorp/terraform/states" | ||
23 | "github.com/hashicorp/terraform/terraform" | 32 | "github.com/hashicorp/terraform/terraform" |
33 | "github.com/hashicorp/terraform/tfdiags" | ||
24 | ) | 34 | ) |
25 | 35 | ||
26 | // flagSweep is a flag available when running tests on the command line. It | 36 | // flagSweep is a flag available when running tests on the command line. It |
@@ -373,6 +383,10 @@ type TestStep struct { | |||
373 | // be refreshed and don't matter. | 383 | // be refreshed and don't matter. |
374 | ImportStateVerify bool | 384 | ImportStateVerify bool |
375 | ImportStateVerifyIgnore []string | 385 | ImportStateVerifyIgnore []string |
386 | |||
387 | // provider s is used internally to maintain a reference to the | ||
388 | // underlying providers during the tests | ||
389 | providers map[string]terraform.ResourceProvider | ||
376 | } | 390 | } |
377 | 391 | ||
378 | // Set to a file mask in sprintf format where %s is test name | 392 | // Set to a file mask in sprintf format where %s is test name |
@@ -467,10 +481,22 @@ func Test(t TestT, c TestCase) { | |||
467 | c.PreCheck() | 481 | c.PreCheck() |
468 | } | 482 | } |
469 | 483 | ||
484 | // get instances of all providers, so we can use the individual | ||
485 | // resources to shim the state during the tests. | ||
486 | providers := make(map[string]terraform.ResourceProvider) | ||
487 | for name, pf := range testProviderFactories(c) { | ||
488 | p, err := pf() | ||
489 | if err != nil { | ||
490 | t.Fatal(err) | ||
491 | } | ||
492 | providers[name] = p | ||
493 | } | ||
494 | |||
470 | providerResolver, err := testProviderResolver(c) | 495 | providerResolver, err := testProviderResolver(c) |
471 | if err != nil { | 496 | if err != nil { |
472 | t.Fatal(err) | 497 | t.Fatal(err) |
473 | } | 498 | } |
499 | |||
474 | opts := terraform.ContextOpts{ProviderResolver: providerResolver} | 500 | opts := terraform.ContextOpts{ProviderResolver: providerResolver} |
475 | 501 | ||
476 | // A single state variable to track the lifecycle, starting with no state | 502 | // A single state variable to track the lifecycle, starting with no state |
@@ -481,6 +507,10 @@ func Test(t TestT, c TestCase) { | |||
481 | idRefresh := c.IDRefreshName != "" | 507 | idRefresh := c.IDRefreshName != "" |
482 | errored := false | 508 | errored := false |
483 | for i, step := range c.Steps { | 509 | for i, step := range c.Steps { |
510 | // insert the providers into the step so we can get the resources for | ||
511 | // shimming the state | ||
512 | step.providers = providers | ||
513 | |||
484 | var err error | 514 | var err error |
485 | log.Printf("[DEBUG] Test: Executing step %d", i) | 515 | log.Printf("[DEBUG] Test: Executing step %d", i) |
486 | 516 | ||
@@ -535,8 +565,7 @@ func Test(t TestT, c TestCase) { | |||
535 | } | 565 | } |
536 | } else { | 566 | } else { |
537 | errored = true | 567 | errored = true |
538 | t.Error(fmt.Sprintf( | 568 | t.Error(fmt.Sprintf("Step %d error: %s", i, detailedErrorMessage(err))) |
539 | "Step %d error: %s", i, err)) | ||
540 | break | 569 | break |
541 | } | 570 | } |
542 | } | 571 | } |
@@ -591,6 +620,7 @@ func Test(t TestT, c TestCase) { | |||
591 | Destroy: true, | 620 | Destroy: true, |
592 | PreventDiskCleanup: lastStep.PreventDiskCleanup, | 621 | PreventDiskCleanup: lastStep.PreventDiskCleanup, |
593 | PreventPostDestroyRefresh: c.PreventPostDestroyRefresh, | 622 | PreventPostDestroyRefresh: c.PreventPostDestroyRefresh, |
623 | providers: providers, | ||
594 | } | 624 | } |
595 | 625 | ||
596 | log.Printf("[WARN] Test: Executing destroy step") | 626 | log.Printf("[WARN] Test: Executing destroy step") |
@@ -620,39 +650,50 @@ func testProviderConfig(c TestCase) string { | |||
620 | return strings.Join(lines, "") | 650 | return strings.Join(lines, "") |
621 | } | 651 | } |
622 | 652 | ||
623 | // testProviderResolver is a helper to build a ResourceProviderResolver | 653 | // testProviderFactories combines the fixed Providers and |
624 | // with pre instantiated ResourceProviders, so that we can reset them for the | 654 | // ResourceProviderFactory functions into a single map of |
625 | // test, while only calling the factory function once. | 655 | // ResourceProviderFactory functions. |
626 | // Any errors are stored so that they can be returned by the factory in | 656 | func testProviderFactories(c TestCase) map[string]terraform.ResourceProviderFactory { |
627 | // terraform to match non-test behavior. | 657 | ctxProviders := make(map[string]terraform.ResourceProviderFactory) |
628 | func testProviderResolver(c TestCase) (terraform.ResourceProviderResolver, error) { | 658 | for k, pf := range c.ProviderFactories { |
629 | ctxProviders := c.ProviderFactories | 659 | ctxProviders[k] = pf |
630 | if ctxProviders == nil { | ||
631 | ctxProviders = make(map[string]terraform.ResourceProviderFactory) | ||
632 | } | 660 | } |
633 | 661 | ||
634 | // add any fixed providers | 662 | // add any fixed providers |
635 | for k, p := range c.Providers { | 663 | for k, p := range c.Providers { |
636 | ctxProviders[k] = terraform.ResourceProviderFactoryFixed(p) | 664 | ctxProviders[k] = terraform.ResourceProviderFactoryFixed(p) |
637 | } | 665 | } |
666 | return ctxProviders | ||
667 | } | ||
668 | |||
669 | // testProviderResolver is a helper to build a ResourceProviderResolver | ||
670 | // with pre instantiated ResourceProviders, so that we can reset them for the | ||
671 | // test, while only calling the factory function once. | ||
672 | // Any errors are stored so that they can be returned by the factory in | ||
673 | // terraform to match non-test behavior. | ||
674 | func testProviderResolver(c TestCase) (providers.Resolver, error) { | ||
675 | ctxProviders := testProviderFactories(c) | ||
676 | |||
677 | // wrap the old provider factories in the test grpc server so they can be | ||
678 | // called from terraform. | ||
679 | newProviders := make(map[string]providers.Factory) | ||
638 | 680 | ||
639 | // reset the providers if needed | ||
640 | for k, pf := range ctxProviders { | 681 | for k, pf := range ctxProviders { |
641 | // we can ignore any errors here, if we don't have a provider to reset | 682 | factory := pf // must copy to ensure each closure sees its own value |
642 | // the error will be handled later | 683 | newProviders[k] = func() (providers.Interface, error) { |
643 | p, err := pf() | 684 | p, err := factory() |
644 | if err != nil { | ||
645 | return nil, err | ||
646 | } | ||
647 | if p, ok := p.(TestProvider); ok { | ||
648 | err := p.TestReset() | ||
649 | if err != nil { | 685 | if err != nil { |
650 | return nil, fmt.Errorf("[ERROR] failed to reset provider %q: %s", k, err) | 686 | return nil, err |
651 | } | 687 | } |
688 | |||
689 | // The provider is wrapped in a GRPCTestProvider so that it can be | ||
690 | // passed back to terraform core as a providers.Interface, rather | ||
691 | // than the legacy ResourceProvider. | ||
692 | return GRPCTestProvider(p), nil | ||
652 | } | 693 | } |
653 | } | 694 | } |
654 | 695 | ||
655 | return terraform.ResourceProviderResolverFixed(ctxProviders), nil | 696 | return providers.ResolverFixed(newProviders), nil |
656 | } | 697 | } |
657 | 698 | ||
658 | // UnitTest is a helper to force the acceptance testing harness to run in the | 699 | // UnitTest is a helper to force the acceptance testing harness to run in the |
@@ -670,33 +711,40 @@ func testIDOnlyRefresh(c TestCase, opts terraform.ContextOpts, step TestStep, r | |||
670 | return nil | 711 | return nil |
671 | } | 712 | } |
672 | 713 | ||
673 | name := fmt.Sprintf("%s.foo", r.Type) | 714 | addr := addrs.Resource{ |
715 | Mode: addrs.ManagedResourceMode, | ||
716 | Type: r.Type, | ||
717 | Name: "foo", | ||
718 | }.Instance(addrs.NoKey) | ||
719 | absAddr := addr.Absolute(addrs.RootModuleInstance) | ||
674 | 720 | ||
675 | // Build the state. The state is just the resource with an ID. There | 721 | // Build the state. The state is just the resource with an ID. There |
676 | // are no attributes. We only set what is needed to perform a refresh. | 722 | // are no attributes. We only set what is needed to perform a refresh. |
677 | state := terraform.NewState() | 723 | state := states.NewState() |
678 | state.RootModule().Resources[name] = &terraform.ResourceState{ | 724 | state.RootModule().SetResourceInstanceCurrent( |
679 | Type: r.Type, | 725 | addr, |
680 | Primary: &terraform.InstanceState{ | 726 | &states.ResourceInstanceObjectSrc{ |
681 | ID: r.Primary.ID, | 727 | AttrsFlat: r.Primary.Attributes, |
728 | Status: states.ObjectReady, | ||
682 | }, | 729 | }, |
683 | } | 730 | addrs.ProviderConfig{Type: "placeholder"}.Absolute(addrs.RootModuleInstance), |
731 | ) | ||
684 | 732 | ||
685 | // Create the config module. We use the full config because Refresh | 733 | // Create the config module. We use the full config because Refresh |
686 | // doesn't have access to it and we may need things like provider | 734 | // doesn't have access to it and we may need things like provider |
687 | // configurations. The initial implementation of id-only checks used | 735 | // configurations. The initial implementation of id-only checks used |
688 | // an empty config module, but that caused the aforementioned problems. | 736 | // an empty config module, but that caused the aforementioned problems. |
689 | mod, err := testModule(opts, step) | 737 | cfg, err := testConfig(opts, step) |
690 | if err != nil { | 738 | if err != nil { |
691 | return err | 739 | return err |
692 | } | 740 | } |
693 | 741 | ||
694 | // Initialize the context | 742 | // Initialize the context |
695 | opts.Module = mod | 743 | opts.Config = cfg |
696 | opts.State = state | 744 | opts.State = state |
697 | ctx, err := terraform.NewContext(&opts) | 745 | ctx, ctxDiags := terraform.NewContext(&opts) |
698 | if err != nil { | 746 | if ctxDiags.HasErrors() { |
699 | return err | 747 | return ctxDiags.Err() |
700 | } | 748 | } |
701 | if diags := ctx.Validate(); len(diags) > 0 { | 749 | if diags := ctx.Validate(); len(diags) > 0 { |
702 | if diags.HasErrors() { | 750 | if diags.HasErrors() { |
@@ -707,20 +755,20 @@ func testIDOnlyRefresh(c TestCase, opts terraform.ContextOpts, step TestStep, r | |||
707 | } | 755 | } |
708 | 756 | ||
709 | // Refresh! | 757 | // Refresh! |
710 | state, err = ctx.Refresh() | 758 | state, refreshDiags := ctx.Refresh() |
711 | if err != nil { | 759 | if refreshDiags.HasErrors() { |
712 | return fmt.Errorf("Error refreshing: %s", err) | 760 | return refreshDiags.Err() |
713 | } | 761 | } |
714 | 762 | ||
715 | // Verify attribute equivalence. | 763 | // Verify attribute equivalence. |
716 | actualR := state.RootModule().Resources[name] | 764 | actualR := state.ResourceInstance(absAddr) |
717 | if actualR == nil { | 765 | if actualR == nil { |
718 | return fmt.Errorf("Resource gone!") | 766 | return fmt.Errorf("Resource gone!") |
719 | } | 767 | } |
720 | if actualR.Primary == nil { | 768 | if actualR.Current == nil { |
721 | return fmt.Errorf("Resource has no primary instance") | 769 | return fmt.Errorf("Resource has no primary instance") |
722 | } | 770 | } |
723 | actual := actualR.Primary.Attributes | 771 | actual := actualR.Current.AttrsFlat |
724 | expected := r.Primary.Attributes | 772 | expected := r.Primary.Attributes |
725 | // Remove fields we're ignoring | 773 | // Remove fields we're ignoring |
726 | for _, v := range c.IDRefreshIgnore { | 774 | for _, v := range c.IDRefreshIgnore { |
@@ -756,15 +804,14 @@ func testIDOnlyRefresh(c TestCase, opts terraform.ContextOpts, step TestStep, r | |||
756 | return nil | 804 | return nil |
757 | } | 805 | } |
758 | 806 | ||
759 | func testModule(opts terraform.ContextOpts, step TestStep) (*module.Tree, error) { | 807 | func testConfig(opts terraform.ContextOpts, step TestStep) (*configs.Config, error) { |
760 | if step.PreConfig != nil { | 808 | if step.PreConfig != nil { |
761 | step.PreConfig() | 809 | step.PreConfig() |
762 | } | 810 | } |
763 | 811 | ||
764 | cfgPath, err := ioutil.TempDir("", "tf-test") | 812 | cfgPath, err := ioutil.TempDir("", "tf-test") |
765 | if err != nil { | 813 | if err != nil { |
766 | return nil, fmt.Errorf( | 814 | return nil, fmt.Errorf("Error creating temporary directory for config: %s", err) |
767 | "Error creating temporary directory for config: %s", err) | ||
768 | } | 815 | } |
769 | 816 | ||
770 | if step.PreventDiskCleanup { | 817 | if step.PreventDiskCleanup { |
@@ -773,38 +820,38 @@ func testModule(opts terraform.ContextOpts, step TestStep) (*module.Tree, error) | |||
773 | defer os.RemoveAll(cfgPath) | 820 | defer os.RemoveAll(cfgPath) |
774 | } | 821 | } |
775 | 822 | ||
776 | // Write the configuration | 823 | // Write the main configuration file |
777 | cfgF, err := os.Create(filepath.Join(cfgPath, "main.tf")) | 824 | err = ioutil.WriteFile(filepath.Join(cfgPath, "main.tf"), []byte(step.Config), os.ModePerm) |
778 | if err != nil { | 825 | if err != nil { |
779 | return nil, fmt.Errorf( | 826 | return nil, fmt.Errorf("Error creating temporary file for config: %s", err) |
780 | "Error creating temporary file for config: %s", err) | ||
781 | } | 827 | } |
782 | 828 | ||
783 | _, err = io.Copy(cfgF, strings.NewReader(step.Config)) | 829 | // Create directory for our child modules, if any. |
784 | cfgF.Close() | 830 | modulesDir := filepath.Join(cfgPath, ".modules") |
831 | err = os.Mkdir(modulesDir, os.ModePerm) | ||
785 | if err != nil { | 832 | if err != nil { |
786 | return nil, fmt.Errorf( | 833 | return nil, fmt.Errorf("Error creating child modules directory: %s", err) |
787 | "Error creating temporary file for config: %s", err) | ||
788 | } | 834 | } |
789 | 835 | ||
790 | // Parse the configuration | 836 | inst := initwd.NewModuleInstaller(modulesDir, nil) |
791 | mod, err := module.NewTreeModule("", cfgPath) | 837 | _, installDiags := inst.InstallModules(cfgPath, true, initwd.ModuleInstallHooksImpl{}) |
792 | if err != nil { | 838 | if installDiags.HasErrors() { |
793 | return nil, fmt.Errorf( | 839 | return nil, installDiags.Err() |
794 | "Error loading configuration: %s", err) | ||
795 | } | 840 | } |
796 | 841 | ||
797 | // Load the modules | 842 | loader, err := configload.NewLoader(&configload.Config{ |
798 | modStorage := &module.Storage{ | 843 | ModulesDir: modulesDir, |
799 | StorageDir: filepath.Join(cfgPath, ".tfmodules"), | 844 | }) |
800 | Mode: module.GetModeGet, | ||
801 | } | ||
802 | err = mod.Load(modStorage) | ||
803 | if err != nil { | 845 | if err != nil { |
804 | return nil, fmt.Errorf("Error downloading modules: %s", err) | 846 | return nil, fmt.Errorf("failed to create config loader: %s", err) |
847 | } | ||
848 | |||
849 | config, configDiags := loader.LoadConfig(cfgPath) | ||
850 | if configDiags.HasErrors() { | ||
851 | return nil, configDiags | ||
805 | } | 852 | } |
806 | 853 | ||
807 | return mod, nil | 854 | return config, nil |
808 | } | 855 | } |
809 | 856 | ||
810 | func testResource(c TestStep, state *terraform.State) (*terraform.ResourceState, error) { | 857 | func testResource(c TestStep, state *terraform.State) (*terraform.ResourceState, error) { |
@@ -881,8 +928,9 @@ func TestCheckResourceAttrSet(name, key string) TestCheckFunc { | |||
881 | // TestCheckModuleResourceAttrSet - as per TestCheckResourceAttrSet but with | 928 | // TestCheckModuleResourceAttrSet - as per TestCheckResourceAttrSet but with |
882 | // support for non-root modules | 929 | // support for non-root modules |
883 | func TestCheckModuleResourceAttrSet(mp []string, name string, key string) TestCheckFunc { | 930 | func TestCheckModuleResourceAttrSet(mp []string, name string, key string) TestCheckFunc { |
931 | mpt := addrs.Module(mp).UnkeyedInstanceShim() | ||
884 | return func(s *terraform.State) error { | 932 | return func(s *terraform.State) error { |
885 | is, err := modulePathPrimaryInstanceState(s, mp, name) | 933 | is, err := modulePathPrimaryInstanceState(s, mpt, name) |
886 | if err != nil { | 934 | if err != nil { |
887 | return err | 935 | return err |
888 | } | 936 | } |
@@ -915,8 +963,9 @@ func TestCheckResourceAttr(name, key, value string) TestCheckFunc { | |||
915 | // TestCheckModuleResourceAttr - as per TestCheckResourceAttr but with | 963 | // TestCheckModuleResourceAttr - as per TestCheckResourceAttr but with |
916 | // support for non-root modules | 964 | // support for non-root modules |
917 | func TestCheckModuleResourceAttr(mp []string, name string, key string, value string) TestCheckFunc { | 965 | func TestCheckModuleResourceAttr(mp []string, name string, key string, value string) TestCheckFunc { |
966 | mpt := addrs.Module(mp).UnkeyedInstanceShim() | ||
918 | return func(s *terraform.State) error { | 967 | return func(s *terraform.State) error { |
919 | is, err := modulePathPrimaryInstanceState(s, mp, name) | 968 | is, err := modulePathPrimaryInstanceState(s, mpt, name) |
920 | if err != nil { | 969 | if err != nil { |
921 | return err | 970 | return err |
922 | } | 971 | } |
@@ -926,7 +975,19 @@ func TestCheckModuleResourceAttr(mp []string, name string, key string, value str | |||
926 | } | 975 | } |
927 | 976 | ||
928 | func testCheckResourceAttr(is *terraform.InstanceState, name string, key string, value string) error { | 977 | func testCheckResourceAttr(is *terraform.InstanceState, name string, key string, value string) error { |
978 | // Empty containers may be elided from the state. | ||
979 | // If the intent here is to check for an empty container, allow the key to | ||
980 | // also be non-existent. | ||
981 | emptyCheck := false | ||
982 | if value == "0" && (strings.HasSuffix(key, ".#") || strings.HasSuffix(key, ".%")) { | ||
983 | emptyCheck = true | ||
984 | } | ||
985 | |||
929 | if v, ok := is.Attributes[key]; !ok || v != value { | 986 | if v, ok := is.Attributes[key]; !ok || v != value { |
987 | if emptyCheck && !ok { | ||
988 | return nil | ||
989 | } | ||
990 | |||
930 | if !ok { | 991 | if !ok { |
931 | return fmt.Errorf("%s: Attribute '%s' not found", name, key) | 992 | return fmt.Errorf("%s: Attribute '%s' not found", name, key) |
932 | } | 993 | } |
@@ -957,8 +1018,9 @@ func TestCheckNoResourceAttr(name, key string) TestCheckFunc { | |||
957 | // TestCheckModuleNoResourceAttr - as per TestCheckNoResourceAttr but with | 1018 | // TestCheckModuleNoResourceAttr - as per TestCheckNoResourceAttr but with |
958 | // support for non-root modules | 1019 | // support for non-root modules |
959 | func TestCheckModuleNoResourceAttr(mp []string, name string, key string) TestCheckFunc { | 1020 | func TestCheckModuleNoResourceAttr(mp []string, name string, key string) TestCheckFunc { |
1021 | mpt := addrs.Module(mp).UnkeyedInstanceShim() | ||
960 | return func(s *terraform.State) error { | 1022 | return func(s *terraform.State) error { |
961 | is, err := modulePathPrimaryInstanceState(s, mp, name) | 1023 | is, err := modulePathPrimaryInstanceState(s, mpt, name) |
962 | if err != nil { | 1024 | if err != nil { |
963 | return err | 1025 | return err |
964 | } | 1026 | } |
@@ -968,7 +1030,20 @@ func TestCheckModuleNoResourceAttr(mp []string, name string, key string) TestChe | |||
968 | } | 1030 | } |
969 | 1031 | ||
970 | func testCheckNoResourceAttr(is *terraform.InstanceState, name string, key string) error { | 1032 | func testCheckNoResourceAttr(is *terraform.InstanceState, name string, key string) error { |
971 | if _, ok := is.Attributes[key]; ok { | 1033 | // Empty containers may sometimes be included in the state. |
1034 | // If the intent here is to check for an empty container, allow the value to | ||
1035 | // also be "0". | ||
1036 | emptyCheck := false | ||
1037 | if strings.HasSuffix(key, ".#") || strings.HasSuffix(key, ".%") { | ||
1038 | emptyCheck = true | ||
1039 | } | ||
1040 | |||
1041 | val, exists := is.Attributes[key] | ||
1042 | if emptyCheck && val == "0" { | ||
1043 | return nil | ||
1044 | } | ||
1045 | |||
1046 | if exists { | ||
972 | return fmt.Errorf("%s: Attribute '%s' found when not expected", name, key) | 1047 | return fmt.Errorf("%s: Attribute '%s' found when not expected", name, key) |
973 | } | 1048 | } |
974 | 1049 | ||
@@ -991,8 +1066,9 @@ func TestMatchResourceAttr(name, key string, r *regexp.Regexp) TestCheckFunc { | |||
991 | // TestModuleMatchResourceAttr - as per TestMatchResourceAttr but with | 1066 | // TestModuleMatchResourceAttr - as per TestMatchResourceAttr but with |
992 | // support for non-root modules | 1067 | // support for non-root modules |
993 | func TestModuleMatchResourceAttr(mp []string, name string, key string, r *regexp.Regexp) TestCheckFunc { | 1068 | func TestModuleMatchResourceAttr(mp []string, name string, key string, r *regexp.Regexp) TestCheckFunc { |
1069 | mpt := addrs.Module(mp).UnkeyedInstanceShim() | ||
994 | return func(s *terraform.State) error { | 1070 | return func(s *terraform.State) error { |
995 | is, err := modulePathPrimaryInstanceState(s, mp, name) | 1071 | is, err := modulePathPrimaryInstanceState(s, mpt, name) |
996 | if err != nil { | 1072 | if err != nil { |
997 | return err | 1073 | return err |
998 | } | 1074 | } |
@@ -1052,13 +1128,15 @@ func TestCheckResourceAttrPair(nameFirst, keyFirst, nameSecond, keySecond string | |||
1052 | // TestCheckModuleResourceAttrPair - as per TestCheckResourceAttrPair but with | 1128 | // TestCheckModuleResourceAttrPair - as per TestCheckResourceAttrPair but with |
1053 | // support for non-root modules | 1129 | // support for non-root modules |
1054 | func TestCheckModuleResourceAttrPair(mpFirst []string, nameFirst string, keyFirst string, mpSecond []string, nameSecond string, keySecond string) TestCheckFunc { | 1130 | func TestCheckModuleResourceAttrPair(mpFirst []string, nameFirst string, keyFirst string, mpSecond []string, nameSecond string, keySecond string) TestCheckFunc { |
1131 | mptFirst := addrs.Module(mpFirst).UnkeyedInstanceShim() | ||
1132 | mptSecond := addrs.Module(mpSecond).UnkeyedInstanceShim() | ||
1055 | return func(s *terraform.State) error { | 1133 | return func(s *terraform.State) error { |
1056 | isFirst, err := modulePathPrimaryInstanceState(s, mpFirst, nameFirst) | 1134 | isFirst, err := modulePathPrimaryInstanceState(s, mptFirst, nameFirst) |
1057 | if err != nil { | 1135 | if err != nil { |
1058 | return err | 1136 | return err |
1059 | } | 1137 | } |
1060 | 1138 | ||
1061 | isSecond, err := modulePathPrimaryInstanceState(s, mpSecond, nameSecond) | 1139 | isSecond, err := modulePathPrimaryInstanceState(s, mptSecond, nameSecond) |
1062 | if err != nil { | 1140 | if err != nil { |
1063 | return err | 1141 | return err |
1064 | } | 1142 | } |
@@ -1068,14 +1146,32 @@ func TestCheckModuleResourceAttrPair(mpFirst []string, nameFirst string, keyFirs | |||
1068 | } | 1146 | } |
1069 | 1147 | ||
1070 | func testCheckResourceAttrPair(isFirst *terraform.InstanceState, nameFirst string, keyFirst string, isSecond *terraform.InstanceState, nameSecond string, keySecond string) error { | 1148 | func testCheckResourceAttrPair(isFirst *terraform.InstanceState, nameFirst string, keyFirst string, isSecond *terraform.InstanceState, nameSecond string, keySecond string) error { |
1071 | vFirst, ok := isFirst.Attributes[keyFirst] | 1149 | vFirst, okFirst := isFirst.Attributes[keyFirst] |
1072 | if !ok { | 1150 | vSecond, okSecond := isSecond.Attributes[keySecond] |
1073 | return fmt.Errorf("%s: Attribute '%s' not found", nameFirst, keyFirst) | 1151 | |
1152 | // Container count values of 0 should not be relied upon, and not reliably | ||
1153 | // maintained by helper/schema. For the purpose of tests, consider unset and | ||
1154 | // 0 to be equal. | ||
1155 | if len(keyFirst) > 2 && len(keySecond) > 2 && keyFirst[len(keyFirst)-2:] == keySecond[len(keySecond)-2:] && | ||
1156 | (strings.HasSuffix(keyFirst, ".#") || strings.HasSuffix(keyFirst, ".%")) { | ||
1157 | // they have the same suffix, and it is a collection count key. | ||
1158 | if vFirst == "0" || vFirst == "" { | ||
1159 | okFirst = false | ||
1160 | } | ||
1161 | if vSecond == "0" || vSecond == "" { | ||
1162 | okSecond = false | ||
1163 | } | ||
1074 | } | 1164 | } |
1075 | 1165 | ||
1076 | vSecond, ok := isSecond.Attributes[keySecond] | 1166 | if okFirst != okSecond { |
1077 | if !ok { | 1167 | if !okFirst { |
1078 | return fmt.Errorf("%s: Attribute '%s' not found", nameSecond, keySecond) | 1168 | return fmt.Errorf("%s: Attribute %q not set, but %q is set in %s as %q", nameFirst, keyFirst, keySecond, nameSecond, vSecond) |
1169 | } | ||
1170 | return fmt.Errorf("%s: Attribute %q is %q, but %q is not set in %s", nameFirst, keyFirst, vFirst, keySecond, nameSecond) | ||
1171 | } | ||
1172 | if !(okFirst || okSecond) { | ||
1173 | // If they both don't exist then they are equally unset, so that's okay. | ||
1174 | return nil | ||
1079 | } | 1175 | } |
1080 | 1176 | ||
1081 | if vFirst != vSecond { | 1177 | if vFirst != vSecond { |
@@ -1163,7 +1259,7 @@ func modulePrimaryInstanceState(s *terraform.State, ms *terraform.ModuleState, n | |||
1163 | 1259 | ||
1164 | // modulePathPrimaryInstanceState returns the primary instance state for the | 1260 | // modulePathPrimaryInstanceState returns the primary instance state for the |
1165 | // given resource name in a given module path. | 1261 | // given resource name in a given module path. |
1166 | func modulePathPrimaryInstanceState(s *terraform.State, mp []string, name string) (*terraform.InstanceState, error) { | 1262 | func modulePathPrimaryInstanceState(s *terraform.State, mp addrs.ModuleInstance, name string) (*terraform.InstanceState, error) { |
1167 | ms := s.ModuleByPath(mp) | 1263 | ms := s.ModuleByPath(mp) |
1168 | if ms == nil { | 1264 | if ms == nil { |
1169 | return nil, fmt.Errorf("No module found at: %s", mp) | 1265 | return nil, fmt.Errorf("No module found at: %s", mp) |
@@ -1178,3 +1274,47 @@ func primaryInstanceState(s *terraform.State, name string) (*terraform.InstanceS | |||
1178 | ms := s.RootModule() | 1274 | ms := s.RootModule() |
1179 | return modulePrimaryInstanceState(s, ms, name) | 1275 | return modulePrimaryInstanceState(s, ms, name) |
1180 | } | 1276 | } |
1277 | |||
1278 | // operationError is a specialized implementation of error used to describe | ||
1279 | // failures during one of the several operations performed for a particular | ||
1280 | // test case. | ||
1281 | type operationError struct { | ||
1282 | OpName string | ||
1283 | Diags tfdiags.Diagnostics | ||
1284 | } | ||
1285 | |||
1286 | func newOperationError(opName string, diags tfdiags.Diagnostics) error { | ||
1287 | return operationError{opName, diags} | ||
1288 | } | ||
1289 | |||
1290 | // Error returns a terse error string containing just the basic diagnostic | ||
1291 | // messages, for situations where normal Go error behavior is appropriate. | ||
1292 | func (err operationError) Error() string { | ||
1293 | return fmt.Sprintf("errors during %s: %s", err.OpName, err.Diags.Err().Error()) | ||
1294 | } | ||
1295 | |||
1296 | // ErrorDetail is like Error except it includes verbosely-rendered diagnostics | ||
1297 | // similar to what would come from a normal Terraform run, which include | ||
1298 | // additional context not included in Error(). | ||
1299 | func (err operationError) ErrorDetail() string { | ||
1300 | var buf bytes.Buffer | ||
1301 | fmt.Fprintf(&buf, "errors during %s:", err.OpName) | ||
1302 | clr := &colorstring.Colorize{Disable: true, Colors: colorstring.DefaultColors} | ||
1303 | for _, diag := range err.Diags { | ||
1304 | diagStr := format.Diagnostic(diag, nil, clr, 78) | ||
1305 | buf.WriteByte('\n') | ||
1306 | buf.WriteString(diagStr) | ||
1307 | } | ||
1308 | return buf.String() | ||
1309 | } | ||
1310 | |||
1311 | // detailedErrorMessage is a helper for calling ErrorDetail on an error if | ||
1312 | // it is an operationError or just taking Error otherwise. | ||
1313 | func detailedErrorMessage(err error) string { | ||
1314 | switch tErr := err.(type) { | ||
1315 | case operationError: | ||
1316 | return tErr.ErrorDetail() | ||
1317 | default: | ||
1318 | return err.Error() | ||
1319 | } | ||
1320 | } | ||