diff --git a/helper/resource/state_shim.go b/helper/resource/state_shim.go index 13802dbc7..264928d83 100644 --- a/helper/resource/state_shim.go +++ b/helper/resource/state_shim.go @@ -4,25 +4,17 @@ import ( "fmt" "github.com/hashicorp/terraform/addrs" - "github.com/hashicorp/terraform/configs/configschema" "github.com/zclconf/go-cty/cty" "github.com/hashicorp/terraform/config/hcl2shim" + "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/states" "github.com/hashicorp/terraform/terraform" ) -func mustShimNewState(newState *states.State, schemas *terraform.Schemas) *terraform.State { - s, err := shimNewState(newState, schemas) - if err != nil { - panic(err) - } - return s -} - // shimState takes a new *states.State and reverts it to a legacy state for the provider ACC tests -func shimNewState(newState *states.State, schemas *terraform.Schemas) (*terraform.State, error) { +func shimNewState(newState *states.State, providers map[string]terraform.ResourceProvider) (*terraform.State, error) { state := terraform.NewState() // in the odd case of a nil state, let the helper packages handle it @@ -57,25 +49,10 @@ func shimNewState(newState *states.State, schemas *terraform.Schemas) (*terrafor resType := res.Addr.Type providerType := res.ProviderConfig.ProviderConfig.Type - providerSchema := schemas.Providers[providerType] - if providerSchema == nil { - return nil, fmt.Errorf("missing schema for %q", providerType) - } - - var resSchema *configschema.Block - switch res.Addr.Mode { - case addrs.ManagedResourceMode: - resSchema = providerSchema.ResourceTypes[resType] - case addrs.DataResourceMode: - resSchema = providerSchema.DataSources[resType] - } - - if resSchema == nil { - return nil, fmt.Errorf("missing resource schema for %q in %q", resType, providerType) - } + resource := getResource(providers, providerType, resType) for key, i := range res.Instances { - flatmap, err := shimmedAttributes(i.Current, resSchema.ImpliedType()) + flatmap, err := shimmedAttributes(i.Current, resource) if err != nil { return nil, fmt.Errorf("error decoding state for %q: %s", resType, err) } @@ -114,7 +91,7 @@ func shimNewState(newState *states.State, schemas *terraform.Schemas) (*terrafor // add any deposed instances for _, dep := range i.Deposed { - flatmap, err := shimmedAttributes(dep, resSchema.ImpliedType()) + flatmap, err := shimmedAttributes(dep, resource) if err != nil { return nil, fmt.Errorf("error decoding deposed state for %q: %s", resType, err) } @@ -139,17 +116,46 @@ func shimNewState(newState *states.State, schemas *terraform.Schemas) (*terrafor return state, nil } -func shimmedAttributes(instance *states.ResourceInstanceObjectSrc, ty cty.Type) (map[string]string, error) { +func getResource(providers map[string]terraform.ResourceProvider, providerName, resourceType string) *schema.Resource { + p := providers[providerName] + if p == nil { + panic(fmt.Sprintf("provider %q not found in test step", providerName)) + } + + // this is only for tests, so should only see schema.Providers + provider := p.(*schema.Provider) + + resource := provider.ResourcesMap[resourceType] + if resource != nil { + return resource + + } + + resource = provider.DataSourcesMap[resourceType] + if resource != nil { + return resource + } + + panic(fmt.Sprintf("resource %s not found in test step", resourceType)) +} + +func shimmedAttributes(instance *states.ResourceInstanceObjectSrc, res *schema.Resource) (map[string]string, error) { flatmap := instance.AttrsFlat - // if we have json attrs, they need to be decoded - if flatmap == nil { - rio, err := instance.Decode(ty) - if err != nil { - return nil, err - } - - flatmap = hcl2shim.FlatmapValueFromHCL2(rio.Value) + if flatmap != nil { + return flatmap, nil } - return flatmap, nil + + // if we have json attrs, they need to be decoded + rio, err := instance.Decode(res.CoreConfigSchema().ImpliedType()) + if err != nil { + return nil, err + } + + instanceState, err := res.ShimInstanceStateFromValue(rio.Value) + if err != nil { + return nil, err + } + + return instanceState.Attributes, nil } diff --git a/helper/resource/state_shim_test.go b/helper/resource/state_shim_test.go index 22f5b02fe..0a0147c76 100644 --- a/helper/resource/state_shim_test.go +++ b/helper/resource/state_shim_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/hashicorp/terraform/addrs" - "github.com/hashicorp/terraform/configs/configschema" + "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/states" "github.com/hashicorp/terraform/terraform" "github.com/zclconf/go-cty/cty" @@ -286,46 +286,29 @@ func TestStateShim(t *testing.T) { }, } - schemas := &terraform.Schemas{ - Providers: map[string]*terraform.ProviderSchema{ - "test": { - ResourceTypes: map[string]*configschema.Block{ - "test_thing": &configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "id": { - Type: cty.String, - Computed: true, - }, - "fizzle": { - Type: cty.String, - Optional: true, - }, - "bazzle": { - Type: cty.String, - Optional: true, - }, - }, + providers := map[string]terraform.ResourceProvider{ + "test": &schema.Provider{ + ResourcesMap: map[string]*schema.Resource{ + "test_thing": &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": {Type: schema.TypeString, Computed: true}, + "fizzle": {Type: schema.TypeString, Optional: true}, + "bazzle": {Type: schema.TypeString, Optional: true}, }, }, - DataSources: map[string]*configschema.Block{ - "test_data_thing": &configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "id": { - Type: cty.String, - Computed: true, - }, - "fuzzle": { - Type: cty.String, - Optional: true, - }, - }, + }, + DataSourcesMap: map[string]*schema.Resource{ + "test_data_thing": &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": {Type: schema.TypeString, Computed: true}, + "fuzzle": {Type: schema.TypeString, Optional: true}, }, }, }, }, } - shimmed, err := shimNewState(state, schemas) + shimmed, err := shimNewState(state, providers) if err != nil { t.Fatal(err) } diff --git a/helper/resource/testing.go b/helper/resource/testing.go index 83ef7713c..fe3df8ddf 100644 --- a/helper/resource/testing.go +++ b/helper/resource/testing.go @@ -382,6 +382,10 @@ type TestStep struct { // be refreshed and don't matter. ImportStateVerify bool ImportStateVerifyIgnore []string + + // provider s is used internally to maintain a reference to the + // underlying providers during the tests + providers map[string]terraform.ResourceProvider } // Set to a file mask in sprintf format where %s is test name @@ -476,6 +480,17 @@ func Test(t TestT, c TestCase) { c.PreCheck() } + // get instances of all providers, so we can use the individual + // resources to shim the state during the tests. + providers := make(map[string]terraform.ResourceProvider) + for name, pf := range testProviderFactories(c) { + p, err := pf() + if err != nil { + t.Fatal(err) + } + providers[name] = p + } + providerResolver, err := testProviderResolver(c) if err != nil { t.Fatal(err) @@ -491,6 +506,10 @@ func Test(t TestT, c TestCase) { idRefresh := c.IDRefreshName != "" errored := false for i, step := range c.Steps { + // insert the providers into the step so we can get the resources for + // shimming the state + step.providers = providers + var err error log.Printf("[DEBUG] Test: Executing step %d", i) @@ -600,6 +619,7 @@ func Test(t TestT, c TestCase) { Destroy: true, PreventDiskCleanup: lastStep.PreventDiskCleanup, PreventPostDestroyRefresh: c.PreventPostDestroyRefresh, + providers: providers, } log.Printf("[WARN] Test: Executing destroy step") @@ -629,12 +649,10 @@ func testProviderConfig(c TestCase) string { return strings.Join(lines, "") } -// testProviderResolver is a helper to build a ResourceProviderResolver -// with pre instantiated ResourceProviders, so that we can reset them for the -// test, while only calling the factory function once. -// Any errors are stored so that they can be returned by the factory in -// terraform to match non-test behavior. -func testProviderResolver(c TestCase) (providers.Resolver, error) { +// testProviderFactories combines the fixed Providers and +// ResourceProviderFactory functions into a single map of +// ResourceProviderFactory functions. +func testProviderFactories(c TestCase) map[string]terraform.ResourceProviderFactory { ctxProviders := make(map[string]terraform.ResourceProviderFactory) for k, pf := range c.ProviderFactories { ctxProviders[k] = pf @@ -644,6 +662,16 @@ func testProviderResolver(c TestCase) (providers.Resolver, error) { for k, p := range c.Providers { ctxProviders[k] = terraform.ResourceProviderFactoryFixed(p) } + return ctxProviders +} + +// testProviderResolver is a helper to build a ResourceProviderResolver +// with pre instantiated ResourceProviders, so that we can reset them for the +// test, while only calling the factory function once. +// Any errors are stored so that they can be returned by the factory in +// terraform to match non-test behavior. +func testProviderResolver(c TestCase) (providers.Resolver, error) { + ctxProviders := testProviderFactories(c) // wrap the old provider factories in the test grpc server so they can be // called from terraform. @@ -667,32 +695,6 @@ func testProviderResolver(c TestCase) (providers.Resolver, error) { return providers.ResolverFixed(newProviders), nil } -// testProviderFactores returns a fixed and reset factories for creating a resolver -func testProviderFactories(c TestCase) (map[string]providers.Factory, error) { - factories := c.ProviderFactories - if factories == nil { - factories = make(map[string]terraform.ResourceProviderFactory) - } - - // add any fixed providers - for k, p := range c.Providers { - factories[k] = terraform.ResourceProviderFactoryFixed(p) - } - - // wrap the providers to be GRPC mocks rather than legacy terraform.ResourceProvider - newFactories := make(map[string]providers.Factory) - for k, pf := range factories { - newFactories[k] = func() (providers.Interface, error) { - p, err := pf() - if err != nil { - return nil, err - } - return GRPCTestProvider(p), nil - } - } - return newFactories, nil -} - // UnitTest is a helper to force the acceptance testing harness to run in the // normal unit test suite. This should only be used for resource that don't // have any external dependencies. diff --git a/helper/resource/testing_config.go b/helper/resource/testing_config.go index fd5324350..7635a7057 100644 --- a/helper/resource/testing_config.go +++ b/helper/resource/testing_config.go @@ -62,14 +62,11 @@ func testStep(opts terraform.ContextOpts, state *terraform.State, step TestStep) log.Printf("[WARN] Config warnings:\n%s", stepDiags) } - // We will need access to the schemas in order to shim to the old-style - // testing API. - schemas := ctx.Schemas() - // Refresh! newState, stepDiags := ctx.Refresh() // shim the state first so the test can check the state on errors - state, err = shimNewState(newState, schemas) + + state, err = shimNewState(newState, step.providers) if err != nil { return nil, err } @@ -95,7 +92,7 @@ func testStep(opts terraform.ContextOpts, state *terraform.State, step TestStep) // Apply the diff, creating real resources. newState, stepDiags = ctx.Apply() // shim the state first so the test can check the state on errors - state, err = shimNewState(newState, schemas) + state, err = shimNewState(newState, step.providers) if err != nil { return nil, err } @@ -139,7 +136,7 @@ func testStep(opts terraform.ContextOpts, state *terraform.State, step TestStep) return state, newOperationError("follow-up refresh", stepDiags) } - state, err = shimNewState(newState, schemas) + state, err = shimNewState(newState, step.providers) if err != nil { return nil, err } @@ -190,7 +187,7 @@ func testStep(opts terraform.ContextOpts, state *terraform.State, step TestStep) // // This is here only for compatibility with existing tests that predate our // new plan and state types, and should not be used in new tests. Instead, use -// a library like "cmp" to do a deep equality check and diff on the two +// a library like "cmp" to do a deep equality and diff on the two // data structures. func legacyPlanComparisonString(state *states.State, changes *plans.Changes) string { return fmt.Sprintf( diff --git a/helper/resource/testing_import_state.go b/helper/resource/testing_import_state.go index c3e8580b1..d3029de87 100644 --- a/helper/resource/testing_import_state.go +++ b/helper/resource/testing_import_state.go @@ -62,10 +62,6 @@ func testStepImportState( return state, stepDiags.Err() } - // We will need access to the schemas in order to shim to the old-style - // testing API. - schemas := ctx.Schemas() - // The test step provides the resource address as a string, so we need // to parse it to get an addrs.AbsResourceAddress to pass in to the // import method. @@ -95,7 +91,7 @@ func testStepImportState( return state, stepDiags.Err() } - newState, err := shimNewState(importedState, schemas) + newState, err := shimNewState(importedState, step.providers) if err != nil { return nil, err }