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 | |
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')
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 @@ | |||
1 | package resource | ||
2 | |||
3 | import ( | ||
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. | ||
20 | func 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 @@ | |||
1 | package resource | ||
2 | |||
3 | import ( | ||
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 | ||
17 | func 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 | |||
120 | func 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 | |||
145 | func 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 @@ | |||
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 | } | ||
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 @@ | |||
1 | package resource | 1 | package resource |
2 | 2 | ||
3 | import ( | 3 | import ( |
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 | ||
21 | func testStep( | 31 | func 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. | ||
192 | func 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. | ||
207 | func 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 | |||
167 | func testStepTaint(state *terraform.State, step TestStep) error { | 391 | func 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 { |