aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/terraform/helper/resource
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/helper/resource')
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/resource/grpc_test_provider.go43
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/resource/state.go2
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/resource/state_shim.go163
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/resource/testing.go298
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/resource/testing_config.go334
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/resource/testing_import_state.go113
6 files changed, 803 insertions, 150 deletions
diff --git a/vendor/github.com/hashicorp/terraform/helper/resource/grpc_test_provider.go b/vendor/github.com/hashicorp/terraform/helper/resource/grpc_test_provider.go
new file mode 100644
index 0000000..0742e99
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/helper/resource/grpc_test_provider.go
@@ -0,0 +1,43 @@
1package resource
2
3import (
4 "context"
5 "net"
6 "time"
7
8 "github.com/hashicorp/terraform/helper/plugin"
9 proto "github.com/hashicorp/terraform/internal/tfplugin5"
10 tfplugin "github.com/hashicorp/terraform/plugin"
11 "github.com/hashicorp/terraform/providers"
12 "github.com/hashicorp/terraform/terraform"
13 "google.golang.org/grpc"
14 "google.golang.org/grpc/test/bufconn"
15)
16
17// GRPCTestProvider takes a legacy ResourceProvider, wraps it in the new GRPC
18// shim and starts it in a grpc server using an inmem connection. It returns a
19// GRPCClient for this new server to test the shimmed resource provider.
20func GRPCTestProvider(rp terraform.ResourceProvider) providers.Interface {
21 listener := bufconn.Listen(256 * 1024)
22 grpcServer := grpc.NewServer()
23
24 p := plugin.NewGRPCProviderServerShim(rp)
25 proto.RegisterProviderServer(grpcServer, p)
26
27 go grpcServer.Serve(listener)
28
29 conn, err := grpc.Dial("", grpc.WithDialer(func(string, time.Duration) (net.Conn, error) {
30 return listener.Dial()
31 }), grpc.WithInsecure())
32 if err != nil {
33 panic(err)
34 }
35
36 var pp tfplugin.GRPCProviderPlugin
37 client, _ := pp.GRPCClient(context.Background(), nil, conn)
38
39 grpcClient := client.(*tfplugin.GRPCProvider)
40 grpcClient.TestServer = grpcServer
41
42 return grpcClient
43}
diff --git a/vendor/github.com/hashicorp/terraform/helper/resource/state.go b/vendor/github.com/hashicorp/terraform/helper/resource/state.go
index c34e21b..88a8396 100644
--- a/vendor/github.com/hashicorp/terraform/helper/resource/state.go
+++ b/vendor/github.com/hashicorp/terraform/helper/resource/state.go
@@ -38,7 +38,7 @@ type StateChangeConf struct {
38// specified in the configuration using the specified Refresh() func, 38// specified in the configuration using the specified Refresh() func,
39// waiting the number of seconds specified in the timeout configuration. 39// waiting the number of seconds specified in the timeout configuration.
40// 40//
41// If the Refresh function returns a error, exit immediately with that error. 41// If the Refresh function returns an error, exit immediately with that error.
42// 42//
43// If the Refresh function returns a state other than the Target state or one 43// If the Refresh function returns a state other than the Target state or one
44// listed in Pending, return immediately with an error. 44// listed in Pending, return immediately with an error.
diff --git a/vendor/github.com/hashicorp/terraform/helper/resource/state_shim.go b/vendor/github.com/hashicorp/terraform/helper/resource/state_shim.go
new file mode 100644
index 0000000..b2aff99
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/helper/resource/state_shim.go
@@ -0,0 +1,163 @@
1package resource
2
3import (
4 "fmt"
5
6 "github.com/hashicorp/terraform/addrs"
7 "github.com/zclconf/go-cty/cty"
8
9 "github.com/hashicorp/terraform/config/hcl2shim"
10 "github.com/hashicorp/terraform/helper/schema"
11
12 "github.com/hashicorp/terraform/states"
13 "github.com/hashicorp/terraform/terraform"
14)
15
16// shimState takes a new *states.State and reverts it to a legacy state for the provider ACC tests
17func shimNewState(newState *states.State, providers map[string]terraform.ResourceProvider) (*terraform.State, error) {
18 state := terraform.NewState()
19
20 // in the odd case of a nil state, let the helper packages handle it
21 if newState == nil {
22 return nil, nil
23 }
24
25 for _, newMod := range newState.Modules {
26 mod := state.AddModule(newMod.Addr)
27
28 for name, out := range newMod.OutputValues {
29 outputType := ""
30 val := hcl2shim.ConfigValueFromHCL2(out.Value)
31 ty := out.Value.Type()
32 switch {
33 case ty == cty.String:
34 outputType = "string"
35 case ty.IsTupleType() || ty.IsListType():
36 outputType = "list"
37 case ty.IsMapType():
38 outputType = "map"
39 }
40
41 mod.Outputs[name] = &terraform.OutputState{
42 Type: outputType,
43 Value: val,
44 Sensitive: out.Sensitive,
45 }
46 }
47
48 for _, res := range newMod.Resources {
49 resType := res.Addr.Type
50 providerType := res.ProviderConfig.ProviderConfig.Type
51
52 resource := getResource(providers, providerType, res.Addr)
53
54 for key, i := range res.Instances {
55 flatmap, err := shimmedAttributes(i.Current, resource)
56 if err != nil {
57 return nil, fmt.Errorf("error decoding state for %q: %s", resType, err)
58 }
59
60 resState := &terraform.ResourceState{
61 Type: resType,
62 Primary: &terraform.InstanceState{
63 ID: flatmap["id"],
64 Attributes: flatmap,
65 Tainted: i.Current.Status == states.ObjectTainted,
66 },
67 Provider: res.ProviderConfig.String(),
68 }
69 if i.Current.SchemaVersion != 0 {
70 resState.Primary.Meta = map[string]interface{}{
71 "schema_version": i.Current.SchemaVersion,
72 }
73 }
74
75 for _, dep := range i.Current.Dependencies {
76 resState.Dependencies = append(resState.Dependencies, dep.String())
77 }
78
79 // convert the indexes to the old style flapmap indexes
80 idx := ""
81 switch key.(type) {
82 case addrs.IntKey:
83 // don't add numeric index values to resources with a count of 0
84 if len(res.Instances) > 1 {
85 idx = fmt.Sprintf(".%d", key)
86 }
87 case addrs.StringKey:
88 idx = "." + key.String()
89 }
90
91 mod.Resources[res.Addr.String()+idx] = resState
92
93 // add any deposed instances
94 for _, dep := range i.Deposed {
95 flatmap, err := shimmedAttributes(dep, resource)
96 if err != nil {
97 return nil, fmt.Errorf("error decoding deposed state for %q: %s", resType, err)
98 }
99
100 deposed := &terraform.InstanceState{
101 ID: flatmap["id"],
102 Attributes: flatmap,
103 Tainted: dep.Status == states.ObjectTainted,
104 }
105 if dep.SchemaVersion != 0 {
106 deposed.Meta = map[string]interface{}{
107 "schema_version": dep.SchemaVersion,
108 }
109 }
110
111 resState.Deposed = append(resState.Deposed, deposed)
112 }
113 }
114 }
115 }
116
117 return state, nil
118}
119
120func getResource(providers map[string]terraform.ResourceProvider, providerName string, addr addrs.Resource) *schema.Resource {
121 p := providers[providerName]
122 if p == nil {
123 panic(fmt.Sprintf("provider %q not found in test step", providerName))
124 }
125
126 // this is only for tests, so should only see schema.Providers
127 provider := p.(*schema.Provider)
128
129 switch addr.Mode {
130 case addrs.ManagedResourceMode:
131 resource := provider.ResourcesMap[addr.Type]
132 if resource != nil {
133 return resource
134 }
135 case addrs.DataResourceMode:
136 resource := provider.DataSourcesMap[addr.Type]
137 if resource != nil {
138 return resource
139 }
140 }
141
142 panic(fmt.Sprintf("resource %s not found in test step", addr.Type))
143}
144
145func shimmedAttributes(instance *states.ResourceInstanceObjectSrc, res *schema.Resource) (map[string]string, error) {
146 flatmap := instance.AttrsFlat
147 if flatmap != nil {
148 return flatmap, nil
149 }
150
151 // if we have json attrs, they need to be decoded
152 rio, err := instance.Decode(res.CoreConfigSchema().ImpliedType())
153 if err != nil {
154 return nil, err
155 }
156
157 instanceState, err := res.ShimInstanceStateFromValue(rio.Value)
158 if err != nil {
159 return nil, err
160 }
161
162 return instanceState.Attributes, nil
163}
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 @@
1package resource 1package resource
2 2
3import ( 3import (
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 656func testProviderFactories(c TestCase) map[string]terraform.ResourceProviderFactory {
627// terraform to match non-test behavior. 657 ctxProviders := make(map[string]terraform.ResourceProviderFactory)
628func 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.
674func 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
759func testModule(opts terraform.ContextOpts, step TestStep) (*module.Tree, error) { 807func 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
810func testResource(c TestStep, state *terraform.State) (*terraform.ResourceState, error) { 857func 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
883func TestCheckModuleResourceAttrSet(mp []string, name string, key string) TestCheckFunc { 930func 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
917func TestCheckModuleResourceAttr(mp []string, name string, key string, value string) TestCheckFunc { 965func 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
928func testCheckResourceAttr(is *terraform.InstanceState, name string, key string, value string) error { 977func 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
959func TestCheckModuleNoResourceAttr(mp []string, name string, key string) TestCheckFunc { 1020func 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
970func testCheckNoResourceAttr(is *terraform.InstanceState, name string, key string) error { 1032func 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
993func TestModuleMatchResourceAttr(mp []string, name string, key string, r *regexp.Regexp) TestCheckFunc { 1068func 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
1054func TestCheckModuleResourceAttrPair(mpFirst []string, nameFirst string, keyFirst string, mpSecond []string, nameSecond string, keySecond string) TestCheckFunc { 1130func 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
1070func testCheckResourceAttrPair(isFirst *terraform.InstanceState, nameFirst string, keyFirst string, isSecond *terraform.InstanceState, nameSecond string, keySecond string) error { 1148func 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.
1166func modulePathPrimaryInstanceState(s *terraform.State, mp []string, name string) (*terraform.InstanceState, error) { 1262func 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.
1281type operationError struct {
1282 OpName string
1283 Diags tfdiags.Diagnostics
1284}
1285
1286func 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.
1292func (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().
1299func (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.
1313func detailedErrorMessage(err error) string {
1314 switch tErr := err.(type) {
1315 case operationError:
1316 return tErr.ErrorDetail()
1317 default:
1318 return err.Error()
1319 }
1320}
diff --git a/vendor/github.com/hashicorp/terraform/helper/resource/testing_config.go b/vendor/github.com/hashicorp/terraform/helper/resource/testing_config.go
index 033f126..311fdb6 100644
--- a/vendor/github.com/hashicorp/terraform/helper/resource/testing_config.go
+++ b/vendor/github.com/hashicorp/terraform/helper/resource/testing_config.go
@@ -1,13 +1,23 @@
1package resource 1package resource
2 2
3import ( 3import (
4 "bufio"
5 "bytes"
4 "errors" 6 "errors"
5 "fmt" 7 "fmt"
6 "log" 8 "log"
9 "sort"
7 "strings" 10 "strings"
8 11
12 "github.com/hashicorp/terraform/addrs"
13 "github.com/hashicorp/terraform/config"
14 "github.com/hashicorp/terraform/config/hcl2shim"
15 "github.com/hashicorp/terraform/states"
16
9 "github.com/hashicorp/errwrap" 17 "github.com/hashicorp/errwrap"
18 "github.com/hashicorp/terraform/plans"
10 "github.com/hashicorp/terraform/terraform" 19 "github.com/hashicorp/terraform/terraform"
20 "github.com/hashicorp/terraform/tfdiags"
11) 21)
12 22
13// testStepConfig runs a config-mode test step 23// testStepConfig runs a config-mode test step
@@ -18,69 +28,79 @@ func testStepConfig(
18 return testStep(opts, state, step) 28 return testStep(opts, state, step)
19} 29}
20 30
21func testStep( 31func testStep(opts terraform.ContextOpts, state *terraform.State, step TestStep) (*terraform.State, error) {
22 opts terraform.ContextOpts,
23 state *terraform.State,
24 step TestStep) (*terraform.State, error) {
25 // Pre-taint any resources that have been defined in Taint, as long as this
26 // is not a destroy step.
27 if !step.Destroy { 32 if !step.Destroy {
28 if err := testStepTaint(state, step); err != nil { 33 if err := testStepTaint(state, step); err != nil {
29 return state, err 34 return state, err
30 } 35 }
31 } 36 }
32 37
33 mod, err := testModule(opts, step) 38 cfg, err := testConfig(opts, step)
34 if err != nil { 39 if err != nil {
35 return state, err 40 return state, err
36 } 41 }
37 42
43 var stepDiags tfdiags.Diagnostics
44
38 // Build the context 45 // Build the context
39 opts.Module = mod 46 opts.Config = cfg
40 opts.State = state 47 opts.State, err = terraform.ShimLegacyState(state)
41 opts.Destroy = step.Destroy
42 ctx, err := terraform.NewContext(&opts)
43 if err != nil { 48 if err != nil {
44 return state, fmt.Errorf("Error initializing context: %s", err) 49 return nil, err
50 }
51
52 opts.Destroy = step.Destroy
53 ctx, stepDiags := terraform.NewContext(&opts)
54 if stepDiags.HasErrors() {
55 return state, fmt.Errorf("Error initializing context: %s", stepDiags.Err())
45 } 56 }
46 if diags := ctx.Validate(); len(diags) > 0 { 57 if stepDiags := ctx.Validate(); len(stepDiags) > 0 {
47 if diags.HasErrors() { 58 if stepDiags.HasErrors() {
48 return nil, errwrap.Wrapf("config is invalid: {{err}}", diags.Err()) 59 return state, errwrap.Wrapf("config is invalid: {{err}}", stepDiags.Err())
49 } 60 }
50 61
51 log.Printf("[WARN] Config warnings:\n%s", diags) 62 log.Printf("[WARN] Config warnings:\n%s", stepDiags)
52 } 63 }
53 64
54 // Refresh! 65 // Refresh!
55 state, err = ctx.Refresh() 66 newState, stepDiags := ctx.Refresh()
67 // shim the state first so the test can check the state on errors
68
69 state, err = shimNewState(newState, step.providers)
56 if err != nil { 70 if err != nil {
57 return state, fmt.Errorf( 71 return nil, err
58 "Error refreshing: %s", err) 72 }
73 if stepDiags.HasErrors() {
74 return state, newOperationError("refresh", stepDiags)
59 } 75 }
60 76
61 // If this step is a PlanOnly step, skip over this first Plan and subsequent 77 // If this step is a PlanOnly step, skip over this first Plan and subsequent
62 // Apply, and use the follow up Plan that checks for perpetual diffs 78 // Apply, and use the follow up Plan that checks for perpetual diffs
63 if !step.PlanOnly { 79 if !step.PlanOnly {
64 // Plan! 80 // Plan!
65 if p, err := ctx.Plan(); err != nil { 81 if p, stepDiags := ctx.Plan(); stepDiags.HasErrors() {
66 return state, fmt.Errorf( 82 return state, newOperationError("plan", stepDiags)
67 "Error planning: %s", err)
68 } else { 83 } else {
69 log.Printf("[WARN] Test: Step plan: %s", p) 84 log.Printf("[WARN] Test: Step plan: %s", legacyPlanComparisonString(newState, p.Changes))
70 } 85 }
71 86
72 // We need to keep a copy of the state prior to destroying 87 // We need to keep a copy of the state prior to destroying
73 // such that destroy steps can verify their behaviour in the check 88 // such that destroy steps can verify their behavior in the check
74 // function 89 // function
75 stateBeforeApplication := state.DeepCopy() 90 stateBeforeApplication := state.DeepCopy()
76 91
77 // Apply! 92 // Apply the diff, creating real resources.
78 state, err = ctx.Apply() 93 newState, stepDiags = ctx.Apply()
94 // shim the state first so the test can check the state on errors
95 state, err = shimNewState(newState, step.providers)
79 if err != nil { 96 if err != nil {
80 return state, fmt.Errorf("Error applying: %s", err) 97 return nil, err
98 }
99 if stepDiags.HasErrors() {
100 return state, newOperationError("apply", stepDiags)
81 } 101 }
82 102
83 // Check! Excitement! 103 // Run any configured checks
84 if step.Check != nil { 104 if step.Check != nil {
85 if step.Destroy { 105 if step.Destroy {
86 if err := step.Check(stateBeforeApplication); err != nil { 106 if err := step.Check(stateBeforeApplication); err != nil {
@@ -96,31 +116,35 @@ func testStep(
96 116
97 // Now, verify that Plan is now empty and we don't have a perpetual diff issue 117 // Now, verify that Plan is now empty and we don't have a perpetual diff issue
98 // We do this with TWO plans. One without a refresh. 118 // We do this with TWO plans. One without a refresh.
99 var p *terraform.Plan 119 var p *plans.Plan
100 if p, err = ctx.Plan(); err != nil { 120 if p, stepDiags = ctx.Plan(); stepDiags.HasErrors() {
101 return state, fmt.Errorf("Error on follow-up plan: %s", err) 121 return state, newOperationError("follow-up plan", stepDiags)
102 } 122 }
103 if p.Diff != nil && !p.Diff.Empty() { 123 if !p.Changes.Empty() {
104 if step.ExpectNonEmptyPlan { 124 if step.ExpectNonEmptyPlan {
105 log.Printf("[INFO] Got non-empty plan, as expected:\n\n%s", p) 125 log.Printf("[INFO] Got non-empty plan, as expected:\n\n%s", legacyPlanComparisonString(newState, p.Changes))
106 } else { 126 } else {
107 return state, fmt.Errorf( 127 return state, fmt.Errorf(
108 "After applying this step, the plan was not empty:\n\n%s", p) 128 "After applying this step, the plan was not empty:\n\n%s", legacyPlanComparisonString(newState, p.Changes))
109 } 129 }
110 } 130 }
111 131
112 // And another after a Refresh. 132 // And another after a Refresh.
113 if !step.Destroy || (step.Destroy && !step.PreventPostDestroyRefresh) { 133 if !step.Destroy || (step.Destroy && !step.PreventPostDestroyRefresh) {
114 state, err = ctx.Refresh() 134 newState, stepDiags = ctx.Refresh()
135 if stepDiags.HasErrors() {
136 return state, newOperationError("follow-up refresh", stepDiags)
137 }
138
139 state, err = shimNewState(newState, step.providers)
115 if err != nil { 140 if err != nil {
116 return state, fmt.Errorf( 141 return nil, err
117 "Error on follow-up refresh: %s", err)
118 } 142 }
119 } 143 }
120 if p, err = ctx.Plan(); err != nil { 144 if p, stepDiags = ctx.Plan(); stepDiags.HasErrors() {
121 return state, fmt.Errorf("Error on second follow-up plan: %s", err) 145 return state, newOperationError("second follow-up refresh", stepDiags)
122 } 146 }
123 empty := p.Diff == nil || p.Diff.Empty() 147 empty := p.Changes.Empty()
124 148
125 // Data resources are tricky because they legitimately get instantiated 149 // Data resources are tricky because they legitimately get instantiated
126 // during refresh so that they will be already populated during the 150 // during refresh so that they will be already populated during the
@@ -128,35 +152,28 @@ func testStep(
128 // config we'll end up wanting to destroy them again here. This is 152 // config we'll end up wanting to destroy them again here. This is
129 // acceptable and expected, and we'll treat it as "empty" for the 153 // acceptable and expected, and we'll treat it as "empty" for the
130 // sake of this testing. 154 // sake of this testing.
131 if step.Destroy { 155 if step.Destroy && !empty {
132 empty = true 156 empty = true
133 157 for _, change := range p.Changes.Resources {
134 for _, moduleDiff := range p.Diff.Modules { 158 if change.Addr.Resource.Resource.Mode != addrs.DataResourceMode {
135 for k, instanceDiff := range moduleDiff.Resources { 159 empty = false
136 if !strings.HasPrefix(k, "data.") { 160 break
137 empty = false
138 break
139 }
140
141 if !instanceDiff.Destroy {
142 empty = false
143 }
144 } 161 }
145 } 162 }
146 } 163 }
147 164
148 if !empty { 165 if !empty {
149 if step.ExpectNonEmptyPlan { 166 if step.ExpectNonEmptyPlan {
150 log.Printf("[INFO] Got non-empty plan, as expected:\n\n%s", p) 167 log.Printf("[INFO] Got non-empty plan, as expected:\n\n%s", legacyPlanComparisonString(newState, p.Changes))
151 } else { 168 } else {
152 return state, fmt.Errorf( 169 return state, fmt.Errorf(
153 "After applying this step and refreshing, "+ 170 "After applying this step and refreshing, "+
154 "the plan was not empty:\n\n%s", p) 171 "the plan was not empty:\n\n%s", legacyPlanComparisonString(newState, p.Changes))
155 } 172 }
156 } 173 }
157 174
158 // Made it here, but expected a non-empty plan, fail! 175 // Made it here, but expected a non-empty plan, fail!
159 if step.ExpectNonEmptyPlan && (p.Diff == nil || p.Diff.Empty()) { 176 if step.ExpectNonEmptyPlan && empty {
160 return state, fmt.Errorf("Expected a non-empty plan, but got an empty plan!") 177 return state, fmt.Errorf("Expected a non-empty plan, but got an empty plan!")
161 } 178 }
162 179
@@ -164,6 +181,213 @@ func testStep(
164 return state, nil 181 return state, nil
165} 182}
166 183
184// legacyPlanComparisonString produces a string representation of the changes
185// from a plan and a given state togther, as was formerly produced by the
186// String method of terraform.Plan.
187//
188// This is here only for compatibility with existing tests that predate our
189// new plan and state types, and should not be used in new tests. Instead, use
190// a library like "cmp" to do a deep equality and diff on the two
191// data structures.
192func legacyPlanComparisonString(state *states.State, changes *plans.Changes) string {
193 return fmt.Sprintf(
194 "DIFF:\n\n%s\n\nSTATE:\n\n%s",
195 legacyDiffComparisonString(changes),
196 state.String(),
197 )
198}
199
200// legacyDiffComparisonString produces a string representation of the changes
201// from a planned changes object, as was formerly produced by the String method
202// of terraform.Diff.
203//
204// This is here only for compatibility with existing tests that predate our
205// new plan types, and should not be used in new tests. Instead, use a library
206// like "cmp" to do a deep equality check and diff on the two data structures.
207func legacyDiffComparisonString(changes *plans.Changes) string {
208 // The old string representation of a plan was grouped by module, but
209 // our new plan structure is not grouped in that way and so we'll need
210 // to preprocess it in order to produce that grouping.
211 type ResourceChanges struct {
212 Current *plans.ResourceInstanceChangeSrc
213 Deposed map[states.DeposedKey]*plans.ResourceInstanceChangeSrc
214 }
215 byModule := map[string]map[string]*ResourceChanges{}
216 resourceKeys := map[string][]string{}
217 requiresReplace := map[string][]string{}
218 var moduleKeys []string
219 for _, rc := range changes.Resources {
220 if rc.Action == plans.NoOp {
221 // We won't mention no-op changes here at all, since the old plan
222 // model we are emulating here didn't have such a concept.
223 continue
224 }
225 moduleKey := rc.Addr.Module.String()
226 if _, exists := byModule[moduleKey]; !exists {
227 moduleKeys = append(moduleKeys, moduleKey)
228 byModule[moduleKey] = make(map[string]*ResourceChanges)
229 }
230 resourceKey := rc.Addr.Resource.String()
231 if _, exists := byModule[moduleKey][resourceKey]; !exists {
232 resourceKeys[moduleKey] = append(resourceKeys[moduleKey], resourceKey)
233 byModule[moduleKey][resourceKey] = &ResourceChanges{
234 Deposed: make(map[states.DeposedKey]*plans.ResourceInstanceChangeSrc),
235 }
236 }
237
238 if rc.DeposedKey == states.NotDeposed {
239 byModule[moduleKey][resourceKey].Current = rc
240 } else {
241 byModule[moduleKey][resourceKey].Deposed[rc.DeposedKey] = rc
242 }
243
244 rr := []string{}
245 for _, p := range rc.RequiredReplace.List() {
246 rr = append(rr, hcl2shim.FlatmapKeyFromPath(p))
247 }
248 requiresReplace[resourceKey] = rr
249 }
250 sort.Strings(moduleKeys)
251 for _, ks := range resourceKeys {
252 sort.Strings(ks)
253 }
254
255 var buf bytes.Buffer
256
257 for _, moduleKey := range moduleKeys {
258 rcs := byModule[moduleKey]
259 var mBuf bytes.Buffer
260
261 for _, resourceKey := range resourceKeys[moduleKey] {
262 rc := rcs[resourceKey]
263
264 forceNewAttrs := requiresReplace[resourceKey]
265
266 crud := "UPDATE"
267 if rc.Current != nil {
268 switch rc.Current.Action {
269 case plans.DeleteThenCreate:
270 crud = "DESTROY/CREATE"
271 case plans.CreateThenDelete:
272 crud = "CREATE/DESTROY"
273 case plans.Delete:
274 crud = "DESTROY"
275 case plans.Create:
276 crud = "CREATE"
277 }
278 } else {
279 // We must be working on a deposed object then, in which
280 // case destroying is the only possible action.
281 crud = "DESTROY"
282 }
283
284 extra := ""
285 if rc.Current == nil && len(rc.Deposed) > 0 {
286 extra = " (deposed only)"
287 }
288
289 fmt.Fprintf(
290 &mBuf, "%s: %s%s\n",
291 crud, resourceKey, extra,
292 )
293
294 attrNames := map[string]bool{}
295 var oldAttrs map[string]string
296 var newAttrs map[string]string
297 if rc.Current != nil {
298 if before := rc.Current.Before; before != nil {
299 ty, err := before.ImpliedType()
300 if err == nil {
301 val, err := before.Decode(ty)
302 if err == nil {
303 oldAttrs = hcl2shim.FlatmapValueFromHCL2(val)
304 for k := range oldAttrs {
305 attrNames[k] = true
306 }
307 }
308 }
309 }
310 if after := rc.Current.After; after != nil {
311 ty, err := after.ImpliedType()
312 if err == nil {
313 val, err := after.Decode(ty)
314 if err == nil {
315 newAttrs = hcl2shim.FlatmapValueFromHCL2(val)
316 for k := range newAttrs {
317 attrNames[k] = true
318 }
319 }
320 }
321 }
322 }
323 if oldAttrs == nil {
324 oldAttrs = make(map[string]string)
325 }
326 if newAttrs == nil {
327 newAttrs = make(map[string]string)
328 }
329
330 attrNamesOrder := make([]string, 0, len(attrNames))
331 keyLen := 0
332 for n := range attrNames {
333 attrNamesOrder = append(attrNamesOrder, n)
334 if len(n) > keyLen {
335 keyLen = len(n)
336 }
337 }
338 sort.Strings(attrNamesOrder)
339
340 for _, attrK := range attrNamesOrder {
341 v := newAttrs[attrK]
342 u := oldAttrs[attrK]
343
344 if v == config.UnknownVariableValue {
345 v = "<computed>"
346 }
347 // NOTE: we don't support <sensitive> here because we would
348 // need schema to do that. Excluding sensitive values
349 // is now done at the UI layer, and so should not be tested
350 // at the core layer.
351
352 updateMsg := ""
353
354 // This may not be as precise as in the old diff, as it matches
355 // everything under the attribute that was originally marked as
356 // ForceNew, but should help make it easier to determine what
357 // caused replacement here.
358 for _, k := range forceNewAttrs {
359 if strings.HasPrefix(attrK, k) {
360 updateMsg = " (forces new resource)"
361 break
362 }
363 }
364
365 fmt.Fprintf(
366 &mBuf, " %s:%s %#v => %#v%s\n",
367 attrK,
368 strings.Repeat(" ", keyLen-len(attrK)),
369 u, v,
370 updateMsg,
371 )
372 }
373 }
374
375 if moduleKey == "" { // root module
376 buf.Write(mBuf.Bytes())
377 buf.WriteByte('\n')
378 continue
379 }
380
381 fmt.Fprintf(&buf, "%s:\n", moduleKey)
382 s := bufio.NewScanner(&mBuf)
383 for s.Scan() {
384 buf.WriteString(fmt.Sprintf(" %s\n", s.Text()))
385 }
386 }
387
388 return buf.String()
389}
390
167func testStepTaint(state *terraform.State, step TestStep) error { 391func testStepTaint(state *terraform.State, step TestStep) error {
168 for _, p := range step.Taint { 392 for _, p := range step.Taint {
169 m := state.RootModule() 393 m := state.RootModule()
diff --git a/vendor/github.com/hashicorp/terraform/helper/resource/testing_import_state.go b/vendor/github.com/hashicorp/terraform/helper/resource/testing_import_state.go
index 94fef3c..e1b7aea 100644
--- a/vendor/github.com/hashicorp/terraform/helper/resource/testing_import_state.go
+++ b/vendor/github.com/hashicorp/terraform/helper/resource/testing_import_state.go
@@ -7,6 +7,12 @@ import (
7 "strings" 7 "strings"
8 8
9 "github.com/davecgh/go-spew/spew" 9 "github.com/davecgh/go-spew/spew"
10 "github.com/hashicorp/hcl2/hcl"
11 "github.com/hashicorp/hcl2/hcl/hclsyntax"
12
13 "github.com/hashicorp/terraform/addrs"
14 "github.com/hashicorp/terraform/helper/schema"
15 "github.com/hashicorp/terraform/states"
10 "github.com/hashicorp/terraform/terraform" 16 "github.com/hashicorp/terraform/terraform"
11) 17)
12 18
@@ -15,6 +21,7 @@ func testStepImportState(
15 opts terraform.ContextOpts, 21 opts terraform.ContextOpts,
16 state *terraform.State, 22 state *terraform.State,
17 step TestStep) (*terraform.State, error) { 23 step TestStep) (*terraform.State, error) {
24
18 // Determine the ID to import 25 // Determine the ID to import
19 var importId string 26 var importId string
20 switch { 27 switch {
@@ -41,33 +48,53 @@ func testStepImportState(
41 48
42 // Setup the context. We initialize with an empty state. We use the 49 // Setup the context. We initialize with an empty state. We use the
43 // full config for provider configurations. 50 // full config for provider configurations.
44 mod, err := testModule(opts, step) 51 cfg, err := testConfig(opts, step)
45 if err != nil { 52 if err != nil {
46 return state, err 53 return state, err
47 } 54 }
48 55
49 opts.Module = mod 56 opts.Config = cfg
50 opts.State = terraform.NewState() 57
51 ctx, err := terraform.NewContext(&opts) 58 // import tests start with empty state
52 if err != nil { 59 opts.State = states.NewState()
53 return state, err 60
61 ctx, stepDiags := terraform.NewContext(&opts)
62 if stepDiags.HasErrors() {
63 return state, stepDiags.Err()
54 } 64 }
55 65
56 // Do the import! 66 // The test step provides the resource address as a string, so we need
57 newState, err := ctx.Import(&terraform.ImportOpts{ 67 // to parse it to get an addrs.AbsResourceAddress to pass in to the
68 // import method.
69 traversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(step.ResourceName), "", hcl.Pos{})
70 if hclDiags.HasErrors() {
71 return nil, hclDiags
72 }
73 importAddr, stepDiags := addrs.ParseAbsResourceInstance(traversal)
74 if stepDiags.HasErrors() {
75 return nil, stepDiags.Err()
76 }
77
78 // Do the import
79 importedState, stepDiags := ctx.Import(&terraform.ImportOpts{
58 // Set the module so that any provider config is loaded 80 // Set the module so that any provider config is loaded
59 Module: mod, 81 Config: cfg,
60 82
61 Targets: []*terraform.ImportTarget{ 83 Targets: []*terraform.ImportTarget{
62 &terraform.ImportTarget{ 84 &terraform.ImportTarget{
63 Addr: step.ResourceName, 85 Addr: importAddr,
64 ID: importId, 86 ID: importId,
65 }, 87 },
66 }, 88 },
67 }) 89 })
90 if stepDiags.HasErrors() {
91 log.Printf("[ERROR] Test: ImportState failure: %s", stepDiags.Err())
92 return state, stepDiags.Err()
93 }
94
95 newState, err := shimNewState(importedState, step.providers)
68 if err != nil { 96 if err != nil {
69 log.Printf("[ERROR] Test: ImportState failure: %s", err) 97 return nil, err
70 return state, err
71 } 98 }
72 99
73 // Go through the new state and verify 100 // Go through the new state and verify
@@ -75,7 +102,9 @@ func testStepImportState(
75 var states []*terraform.InstanceState 102 var states []*terraform.InstanceState
76 for _, r := range newState.RootModule().Resources { 103 for _, r := range newState.RootModule().Resources {
77 if r.Primary != nil { 104 if r.Primary != nil {
78 states = append(states, r.Primary) 105 is := r.Primary.DeepCopy()
106 is.Ephemeral.Type = r.Type // otherwise the check function cannot see the type
107 states = append(states, is)
79 } 108 }
80 } 109 }
81 if err := step.ImportStateCheck(states); err != nil { 110 if err := step.ImportStateCheck(states); err != nil {
@@ -102,30 +131,84 @@ func testStepImportState(
102 r.Primary.ID) 131 r.Primary.ID)
103 } 132 }
104 133
134 // We'll try our best to find the schema for this resource type
135 // so we can ignore Removed fields during validation. If we fail
136 // to find the schema then we won't ignore them and so the test
137 // will need to rely on explicit ImportStateVerifyIgnore, though
138 // this shouldn't happen in any reasonable case.
139 var rsrcSchema *schema.Resource
140 if providerAddr, diags := addrs.ParseAbsProviderConfigStr(r.Provider); !diags.HasErrors() {
141 providerType := providerAddr.ProviderConfig.Type
142 if provider, ok := step.providers[providerType]; ok {
143 if provider, ok := provider.(*schema.Provider); ok {
144 rsrcSchema = provider.ResourcesMap[r.Type]
145 }
146 }
147 }
148
149 // don't add empty flatmapped containers, so we can more easily
150 // compare the attributes
151 skipEmpty := func(k, v string) bool {
152 if strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%") {
153 if v == "0" {
154 return true
155 }
156 }
157 return false
158 }
159
105 // Compare their attributes 160 // Compare their attributes
106 actual := make(map[string]string) 161 actual := make(map[string]string)
107 for k, v := range r.Primary.Attributes { 162 for k, v := range r.Primary.Attributes {
163 if skipEmpty(k, v) {
164 continue
165 }
108 actual[k] = v 166 actual[k] = v
109 } 167 }
168
110 expected := make(map[string]string) 169 expected := make(map[string]string)
111 for k, v := range oldR.Primary.Attributes { 170 for k, v := range oldR.Primary.Attributes {
171 if skipEmpty(k, v) {
172 continue
173 }
112 expected[k] = v 174 expected[k] = v
113 } 175 }
114 176
115 // Remove fields we're ignoring 177 // Remove fields we're ignoring
116 for _, v := range step.ImportStateVerifyIgnore { 178 for _, v := range step.ImportStateVerifyIgnore {
117 for k, _ := range actual { 179 for k := range actual {
118 if strings.HasPrefix(k, v) { 180 if strings.HasPrefix(k, v) {
119 delete(actual, k) 181 delete(actual, k)
120 } 182 }
121 } 183 }
122 for k, _ := range expected { 184 for k := range expected {
123 if strings.HasPrefix(k, v) { 185 if strings.HasPrefix(k, v) {
124 delete(expected, k) 186 delete(expected, k)
125 } 187 }
126 } 188 }
127 } 189 }
128 190
191 // Also remove any attributes that are marked as "Removed" in the
192 // schema, if we have a schema to check that against.
193 if rsrcSchema != nil {
194 for k := range actual {
195 for _, schema := range rsrcSchema.SchemasForFlatmapPath(k) {
196 if schema.Removed != "" {
197 delete(actual, k)
198 break
199 }
200 }
201 }
202 for k := range expected {
203 for _, schema := range rsrcSchema.SchemasForFlatmapPath(k) {
204 if schema.Removed != "" {
205 delete(expected, k)
206 break
207 }
208 }
209 }
210 }
211
129 if !reflect.DeepEqual(actual, expected) { 212 if !reflect.DeepEqual(actual, expected) {
130 // Determine only the different attributes 213 // Determine only the different attributes
131 for k, v := range expected { 214 for k, v := range expected {