use actual schema.Resources for state shims
Provider tests often rely on checking values contained within sets, by directly accessing their flatmapped representation. In order to provider the test harness with the expected set hashes, the sets must be generated by the schema.Resource itself. During the test we now build a fixed map of the providers, which should only contain schema.Provider instances, and pass them into each TestStep. The individual schema.Resource instances can then be pulled from the providers, and used to recreate the state from the cty.Value returned by the core operations.
This commit is contained in:
parent
35365e8ccf
commit
a7b399cb4c
|
@ -4,25 +4,17 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/configs/configschema"
|
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/config/hcl2shim"
|
"github.com/hashicorp/terraform/config/hcl2shim"
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/states"
|
"github.com/hashicorp/terraform/states"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"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
|
// 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()
|
state := terraform.NewState()
|
||||||
|
|
||||||
// in the odd case of a nil state, let the helper packages handle it
|
// 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
|
resType := res.Addr.Type
|
||||||
providerType := res.ProviderConfig.ProviderConfig.Type
|
providerType := res.ProviderConfig.ProviderConfig.Type
|
||||||
|
|
||||||
providerSchema := schemas.Providers[providerType]
|
resource := getResource(providers, providerType, resType)
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, i := range res.Instances {
|
for key, i := range res.Instances {
|
||||||
flatmap, err := shimmedAttributes(i.Current, resSchema.ImpliedType())
|
flatmap, err := shimmedAttributes(i.Current, resource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error decoding state for %q: %s", resType, err)
|
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
|
// add any deposed instances
|
||||||
for _, dep := range i.Deposed {
|
for _, dep := range i.Deposed {
|
||||||
flatmap, err := shimmedAttributes(dep, resSchema.ImpliedType())
|
flatmap, err := shimmedAttributes(dep, resource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error decoding deposed state for %q: %s", resType, err)
|
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
|
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
|
flatmap := instance.AttrsFlat
|
||||||
|
|
||||||
// if we have json attrs, they need to be decoded
|
if flatmap != nil {
|
||||||
if flatmap == nil {
|
return flatmap, nil
|
||||||
rio, err := instance.Decode(ty)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
flatmap = hcl2shim.FlatmapValueFromHCL2(rio.Value)
|
|
||||||
}
|
}
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"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/states"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
@ -286,46 +286,29 @@ func TestStateShim(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
schemas := &terraform.Schemas{
|
providers := map[string]terraform.ResourceProvider{
|
||||||
Providers: map[string]*terraform.ProviderSchema{
|
"test": &schema.Provider{
|
||||||
"test": {
|
ResourcesMap: map[string]*schema.Resource{
|
||||||
ResourceTypes: map[string]*configschema.Block{
|
"test_thing": &schema.Resource{
|
||||||
"test_thing": &configschema.Block{
|
Schema: map[string]*schema.Schema{
|
||||||
Attributes: map[string]*configschema.Attribute{
|
"id": {Type: schema.TypeString, Computed: true},
|
||||||
"id": {
|
"fizzle": {Type: schema.TypeString, Optional: true},
|
||||||
Type: cty.String,
|
"bazzle": {Type: schema.TypeString, Optional: true},
|
||||||
Computed: true,
|
|
||||||
},
|
|
||||||
"fizzle": {
|
|
||||||
Type: cty.String,
|
|
||||||
Optional: true,
|
|
||||||
},
|
|
||||||
"bazzle": {
|
|
||||||
Type: cty.String,
|
|
||||||
Optional: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
DataSources: map[string]*configschema.Block{
|
},
|
||||||
"test_data_thing": &configschema.Block{
|
DataSourcesMap: map[string]*schema.Resource{
|
||||||
Attributes: map[string]*configschema.Attribute{
|
"test_data_thing": &schema.Resource{
|
||||||
"id": {
|
Schema: map[string]*schema.Schema{
|
||||||
Type: cty.String,
|
"id": {Type: schema.TypeString, Computed: true},
|
||||||
Computed: true,
|
"fuzzle": {Type: schema.TypeString, Optional: true},
|
||||||
},
|
|
||||||
"fuzzle": {
|
|
||||||
Type: cty.String,
|
|
||||||
Optional: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
shimmed, err := shimNewState(state, schemas)
|
shimmed, err := shimNewState(state, providers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -382,6 +382,10 @@ type TestStep struct {
|
||||||
// be refreshed and don't matter.
|
// be refreshed and don't matter.
|
||||||
ImportStateVerify bool
|
ImportStateVerify bool
|
||||||
ImportStateVerifyIgnore []string
|
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
|
// 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()
|
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)
|
providerResolver, err := testProviderResolver(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -491,6 +506,10 @@ func Test(t TestT, c TestCase) {
|
||||||
idRefresh := c.IDRefreshName != ""
|
idRefresh := c.IDRefreshName != ""
|
||||||
errored := false
|
errored := false
|
||||||
for i, step := range c.Steps {
|
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
|
var err error
|
||||||
log.Printf("[DEBUG] Test: Executing step %d", i)
|
log.Printf("[DEBUG] Test: Executing step %d", i)
|
||||||
|
|
||||||
|
@ -600,6 +619,7 @@ func Test(t TestT, c TestCase) {
|
||||||
Destroy: true,
|
Destroy: true,
|
||||||
PreventDiskCleanup: lastStep.PreventDiskCleanup,
|
PreventDiskCleanup: lastStep.PreventDiskCleanup,
|
||||||
PreventPostDestroyRefresh: c.PreventPostDestroyRefresh,
|
PreventPostDestroyRefresh: c.PreventPostDestroyRefresh,
|
||||||
|
providers: providers,
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[WARN] Test: Executing destroy step")
|
log.Printf("[WARN] Test: Executing destroy step")
|
||||||
|
@ -629,12 +649,10 @@ func testProviderConfig(c TestCase) string {
|
||||||
return strings.Join(lines, "")
|
return strings.Join(lines, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// testProviderResolver is a helper to build a ResourceProviderResolver
|
// testProviderFactories combines the fixed Providers and
|
||||||
// with pre instantiated ResourceProviders, so that we can reset them for the
|
// ResourceProviderFactory functions into a single map of
|
||||||
// test, while only calling the factory function once.
|
// ResourceProviderFactory functions.
|
||||||
// Any errors are stored so that they can be returned by the factory in
|
func testProviderFactories(c TestCase) map[string]terraform.ResourceProviderFactory {
|
||||||
// terraform to match non-test behavior.
|
|
||||||
func testProviderResolver(c TestCase) (providers.Resolver, error) {
|
|
||||||
ctxProviders := make(map[string]terraform.ResourceProviderFactory)
|
ctxProviders := make(map[string]terraform.ResourceProviderFactory)
|
||||||
for k, pf := range c.ProviderFactories {
|
for k, pf := range c.ProviderFactories {
|
||||||
ctxProviders[k] = pf
|
ctxProviders[k] = pf
|
||||||
|
@ -644,6 +662,16 @@ func testProviderResolver(c TestCase) (providers.Resolver, error) {
|
||||||
for k, p := range c.Providers {
|
for k, p := range c.Providers {
|
||||||
ctxProviders[k] = terraform.ResourceProviderFactoryFixed(p)
|
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
|
// wrap the old provider factories in the test grpc server so they can be
|
||||||
// called from terraform.
|
// called from terraform.
|
||||||
|
@ -667,32 +695,6 @@ func testProviderResolver(c TestCase) (providers.Resolver, error) {
|
||||||
return providers.ResolverFixed(newProviders), nil
|
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
|
// 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
|
// normal unit test suite. This should only be used for resource that don't
|
||||||
// have any external dependencies.
|
// have any external dependencies.
|
||||||
|
|
|
@ -62,14 +62,11 @@ func testStep(opts terraform.ContextOpts, state *terraform.State, step TestStep)
|
||||||
log.Printf("[WARN] Config warnings:\n%s", stepDiags)
|
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!
|
// Refresh!
|
||||||
newState, stepDiags := ctx.Refresh()
|
newState, stepDiags := ctx.Refresh()
|
||||||
// shim the state first so the test can check the state on errors
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -95,7 +92,7 @@ func testStep(opts terraform.ContextOpts, state *terraform.State, step TestStep)
|
||||||
// Apply the diff, creating real resources.
|
// Apply the diff, creating real resources.
|
||||||
newState, stepDiags = ctx.Apply()
|
newState, stepDiags = ctx.Apply()
|
||||||
// shim the state first so the test can check the state on errors
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -139,7 +136,7 @@ func testStep(opts terraform.ContextOpts, state *terraform.State, step TestStep)
|
||||||
return state, newOperationError("follow-up refresh", stepDiags)
|
return state, newOperationError("follow-up refresh", stepDiags)
|
||||||
}
|
}
|
||||||
|
|
||||||
state, err = shimNewState(newState, schemas)
|
state, err = shimNewState(newState, step.providers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// 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
|
// 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.
|
// data structures.
|
||||||
func legacyPlanComparisonString(state *states.State, changes *plans.Changes) string {
|
func legacyPlanComparisonString(state *states.State, changes *plans.Changes) string {
|
||||||
return fmt.Sprintf(
|
return fmt.Sprintf(
|
||||||
|
|
|
@ -62,10 +62,6 @@ func testStepImportState(
|
||||||
return state, stepDiags.Err()
|
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
|
// 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
|
// to parse it to get an addrs.AbsResourceAddress to pass in to the
|
||||||
// import method.
|
// import method.
|
||||||
|
@ -95,7 +91,7 @@ func testStepImportState(
|
||||||
return state, stepDiags.Err()
|
return state, stepDiags.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
newState, err := shimNewState(importedState, schemas)
|
newState, err := shimNewState(importedState, step.providers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue