core: Stop loading provider schema during graph walk

We now fetch all of the necessary schemas during context creation, so we
can just thread that repository of schemas through into EvalContext and
Evaluator and access the schemas as needed without any further fetching.

This requires updating a few tests to have a valid Provider address in
their state objects, because we need that in order to trigger the loading
of the relevant schema.
This commit is contained in:
Martin Atkins 2018-06-01 12:36:55 -07:00
parent 555cd977f8
commit d961b1de1b
7 changed files with 60 additions and 121 deletions

View File

@ -285,6 +285,7 @@ func TestContext2Apply_resourceDependsOnModuleStateOnly(t *testing.T) {
ID: "bar", ID: "bar",
}, },
Dependencies: []string{"module.child"}, Dependencies: []string{"module.child"},
Provider: "provider.aws",
}, },
}, },
}, },
@ -296,6 +297,7 @@ func TestContext2Apply_resourceDependsOnModuleStateOnly(t *testing.T) {
Primary: &InstanceState{ Primary: &InstanceState{
ID: "bar", ID: "bar",
}, },
Provider: "provider.aws",
}, },
}, },
}, },
@ -1330,6 +1332,7 @@ func testContext2Apply_destroyDependsOnStateOnly(t *testing.T) {
ID: "foo", ID: "foo",
Attributes: map[string]string{}, Attributes: map[string]string{},
}, },
Provider: "provider.aws",
}, },
"aws_instance.bar": &ResourceState{ "aws_instance.bar": &ResourceState{
@ -1339,6 +1342,7 @@ func testContext2Apply_destroyDependsOnStateOnly(t *testing.T) {
Attributes: map[string]string{}, Attributes: map[string]string{},
}, },
Dependencies: []string{"aws_instance.foo"}, Dependencies: []string{"aws_instance.foo"},
Provider: "provider.aws",
}, },
}, },
}, },
@ -1408,6 +1412,7 @@ func testContext2Apply_destroyDependsOnStateOnlyModule(t *testing.T) {
ID: "foo", ID: "foo",
Attributes: map[string]string{}, Attributes: map[string]string{},
}, },
Provider: "provider.aws",
}, },
"aws_instance.bar": &ResourceState{ "aws_instance.bar": &ResourceState{
@ -1417,6 +1422,7 @@ func testContext2Apply_destroyDependsOnStateOnlyModule(t *testing.T) {
Attributes: map[string]string{}, Attributes: map[string]string{},
}, },
Dependencies: []string{"aws_instance.foo"}, Dependencies: []string{"aws_instance.foo"},
Provider: "provider.aws",
}, },
}, },
}, },
@ -6158,6 +6164,7 @@ func TestContext2Apply_destroyNestedModule(t *testing.T) {
Primary: &InstanceState{ Primary: &InstanceState{
ID: "bar", ID: "bar",
}, },
Provider: "provider.aws",
}, },
}, },
}, },
@ -6207,6 +6214,7 @@ func TestContext2Apply_destroyDeeplyNestedModule(t *testing.T) {
Primary: &InstanceState{ Primary: &InstanceState{
ID: "bar", ID: "bar",
}, },
Provider: "provider.aws",
}, },
}, },
}, },
@ -6977,6 +6985,7 @@ func TestContext2Apply_hookOrphan(t *testing.T) {
Primary: &InstanceState{ Primary: &InstanceState{
ID: "bar", ID: "bar",
}, },
Provider: "provider.aws",
}, },
}, },
}, },

View File

@ -853,6 +853,7 @@ func TestContext2Refresh_dataOrphan(t *testing.T) {
Primary: &InstanceState{ Primary: &InstanceState{
ID: "foo", ID: "foo",
}, },
Provider: "provider.null",
}, },
}, },
}, },
@ -1224,6 +1225,7 @@ func TestContext2Refresh_orphanModule(t *testing.T) {
"module.child", "module.child",
"module.child", "module.child",
}, },
Provider: "provider.aws",
}, },
}, },
}, },
@ -1241,6 +1243,7 @@ func TestContext2Refresh_orphanModule(t *testing.T) {
Dependencies: []string{ Dependencies: []string{
"module.grandchild", "module.grandchild",
}, },
Provider: "provider.aws",
}, },
}, },
Outputs: map[string]*OutputState{ Outputs: map[string]*OutputState{
@ -1262,6 +1265,7 @@ func TestContext2Refresh_orphanModule(t *testing.T) {
Primary: &InstanceState{ Primary: &InstanceState{
ID: "i-cde345", ID: "i-cde345",
}, },
Provider: "provider.aws",
}, },
}, },
Outputs: map[string]*OutputState{ Outputs: map[string]*OutputState{

View File

@ -28,6 +28,15 @@ type BuiltinEvalContext struct {
// eval context. // eval context.
Evaluator *Evaluator Evaluator *Evaluator
// Schemas is a repository of all of the schemas we should need to
// decode configuration blocks and expressions. This must be constructed by
// the caller to include schemas for all of the providers, resource types,
// data sources and provisioners used by the given configuration and
// state.
//
// This must not be mutated during evaluation.
Schemas *Schemas
// VariableValues contains the variable values across all modules. This // VariableValues contains the variable values across all modules. This
// structure is shared across the entire containing context, and so it // structure is shared across the entire containing context, and so it
// may be accessed only when holding VariableValuesLock. // may be accessed only when holding VariableValuesLock.
@ -41,11 +50,9 @@ type BuiltinEvalContext struct {
Hooks []Hook Hooks []Hook
InputValue UIInput InputValue UIInput
ProviderCache map[string]ResourceProvider ProviderCache map[string]ResourceProvider
ProviderSchemas map[string]*ProviderSchema
ProviderInputConfig map[string]map[string]cty.Value ProviderInputConfig map[string]map[string]cty.Value
ProviderLock *sync.Mutex ProviderLock *sync.Mutex
ProvisionerCache map[string]ResourceProvisioner ProvisionerCache map[string]ResourceProvisioner
ProvisionerSchemas map[string]*configschema.Block
ProvisionerLock *sync.Mutex ProvisionerLock *sync.Mutex
DiffValue *Diff DiffValue *Diff
DiffLock *sync.RWMutex DiffLock *sync.RWMutex
@ -115,34 +122,6 @@ func (ctx *BuiltinEvalContext) InitProvider(typeName string, addr addrs.Provider
log.Printf("[TRACE] BuiltinEvalContext: Initialized %q provider for %s", typeName, absAddr) log.Printf("[TRACE] BuiltinEvalContext: Initialized %q provider for %s", typeName, absAddr)
ctx.ProviderCache[key] = p ctx.ProviderCache[key] = p
// Also fetch and cache the provider's schema.
// FIXME: This is using a non-ideal provider API that requires us to
// request specific resource types, but we actually just want _all_ the
// resource types, so we'll list these first. Once the provider API is
// updated we'll get enough data to populate this whole structure in
// a single call.
resourceTypes := p.Resources()
dataSources := p.DataSources()
resourceTypeNames := make([]string, len(resourceTypes))
for i, t := range resourceTypes {
resourceTypeNames[i] = t.Name
}
dataSourceNames := make([]string, len(dataSources))
for i, t := range dataSources {
dataSourceNames[i] = t.Name
}
schema, err := p.GetSchema(&ProviderSchemaRequest{
DataSources: dataSourceNames,
ResourceTypes: resourceTypeNames,
})
if err != nil {
return nil, fmt.Errorf("error fetching schema for %s: %s", key, err)
}
if ctx.ProviderSchemas == nil {
ctx.ProviderSchemas = make(map[string]*ProviderSchema)
}
ctx.ProviderSchemas[typeName] = schema
return p, nil return p, nil
} }
@ -158,10 +137,7 @@ func (ctx *BuiltinEvalContext) Provider(addr addrs.AbsProviderConfig) ResourcePr
func (ctx *BuiltinEvalContext) ProviderSchema(addr addrs.AbsProviderConfig) *ProviderSchema { func (ctx *BuiltinEvalContext) ProviderSchema(addr addrs.AbsProviderConfig) *ProviderSchema {
ctx.once.Do(ctx.init) ctx.once.Do(ctx.init)
ctx.ProviderLock.Lock() return ctx.Schemas.ProviderSchema(addr.ProviderConfig.Type)
defer ctx.ProviderLock.Unlock()
return ctx.ProviderSchemas[addr.ProviderConfig.Type]
} }
func (ctx *BuiltinEvalContext) CloseProvider(addr addrs.ProviderConfig) error { func (ctx *BuiltinEvalContext) CloseProvider(addr addrs.ProviderConfig) error {
@ -250,16 +226,6 @@ func (ctx *BuiltinEvalContext) InitProvisioner(n string) (ResourceProvisioner, e
ctx.ProvisionerCache[key] = p ctx.ProvisionerCache[key] = p
// Also fetch the provisioner's schema
schema, err := p.GetConfigSchema()
if err != nil {
return nil, fmt.Errorf("error getting schema for provisioner %q: %s", n, err)
}
if ctx.ProvisionerSchemas == nil {
ctx.ProvisionerSchemas = make(map[string]*configschema.Block)
}
ctx.ProvisionerSchemas[n] = schema
return p, nil return p, nil
} }
@ -276,10 +242,7 @@ func (ctx *BuiltinEvalContext) Provisioner(n string) ResourceProvisioner {
func (ctx *BuiltinEvalContext) ProvisionerSchema(n string) *configschema.Block { func (ctx *BuiltinEvalContext) ProvisionerSchema(n string) *configschema.Block {
ctx.once.Do(ctx.init) ctx.once.Do(ctx.init)
ctx.ProvisionerLock.Lock() return ctx.Schemas.ProvisionerConfig(n)
defer ctx.ProvisionerLock.Unlock()
return ctx.ProvisionerSchemas[n]
} }
func (ctx *BuiltinEvalContext) CloseProvisioner(n string) error { func (ctx *BuiltinEvalContext) CloseProvisioner(n string) error {

View File

@ -7,9 +7,6 @@ import (
"github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/addrs"
"github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty"
"github.com/davecgh/go-spew/spew"
"github.com/hashicorp/terraform/config/configschema"
) )
func TestBuiltinEvalContextProviderInput(t *testing.T) { func TestBuiltinEvalContextProviderInput(t *testing.T) {
@ -61,15 +58,6 @@ func TestBuildingEvalContextInitProvider(t *testing.T) {
SchemaAvailable: true, SchemaAvailable: true,
}, },
}, },
GetSchemaReturn: &ProviderSchema{
Provider: &configschema.Block{},
ResourceTypes: map[string]*configschema.Block{
"test_thing": &configschema.Block{},
},
DataSources: map[string]*configschema.Block{
"test_thing": &configschema.Block{},
},
},
} }
ctx := testBuiltinEvalContext(t) ctx := testBuiltinEvalContext(t)
@ -92,32 +80,6 @@ func TestBuildingEvalContextInitProvider(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("error initializing provider test.foo: %s", err) t.Fatalf("error initializing provider test.foo: %s", err)
} }
{
got := testP.GetSchemaRequest
want := &ProviderSchemaRequest{
DataSources: []string{"test_thing"},
ResourceTypes: []string{"test_thing"},
}
if !reflect.DeepEqual(got, want) {
t.Errorf("wrong schema request\ngot: %swant: %s", spew.Sdump(got), spew.Sdump(want))
}
}
{
schema := ctx.ProviderSchema(providerAddrDefault.Absolute(addrs.RootModuleInstance))
if got, want := schema, testP.GetSchemaReturn; !reflect.DeepEqual(got, want) {
t.Errorf("wrong schema\ngot: %swant: %s", spew.Sdump(got), spew.Sdump(want))
}
}
{
schema := ctx.ProviderSchema(providerAddrAlias.Absolute(addrs.RootModuleInstance))
if got, want := schema, testP.GetSchemaReturn; !reflect.DeepEqual(got, want) {
t.Errorf("wrong schema\ngot: %swant: %s", spew.Sdump(got), spew.Sdump(want))
}
}
} }
func testBuiltinEvalContext(t *testing.T) *BuiltinEvalContext { func testBuiltinEvalContext(t *testing.T) *BuiltinEvalContext {

View File

@ -44,11 +44,13 @@ type Evaluator struct {
VariableValues map[string]map[string]cty.Value VariableValues map[string]map[string]cty.Value
VariableValuesLock *sync.Mutex VariableValuesLock *sync.Mutex
// ProviderSchemas is a map of schemas for all provider configurations // Schemas is a repository of all of the schemas we should need to
// that have been initialized so far. This is mutated concurrently, so // evaluate expressions. This must be constructed by the caller to
// it must be accessed only while holding ProvidersLock. // include schemas for all of the providers, resource types, data sources
ProviderSchemas map[string]*ProviderSchema // and provisioners used by the given configuration and state.
ProvidersLock *sync.Mutex //
// This must not be mutated during evaluation.
Schemas *Schemas
// State is the current state. During some operations this structure // State is the current state. During some operations this structure
// is mutated concurrently, and so it must be accessed only while holding // is mutated concurrently, and so it must be accessed only while holding
@ -694,23 +696,18 @@ func (d *evaluationStateData) getResourceInstancePending(addr addrs.ResourceInst
} }
func (d *evaluationStateData) getResourceSchema(addr addrs.Resource, providerAddr addrs.AbsProviderConfig) *configschema.Block { func (d *evaluationStateData) getResourceSchema(addr addrs.Resource, providerAddr addrs.AbsProviderConfig) *configschema.Block {
d.Evaluator.ProvidersLock.Lock() providerType := providerAddr.ProviderConfig.Type
defer d.Evaluator.ProvidersLock.Unlock() typeName := addr.Type
schemas := d.Evaluator.Schemas
log.Printf("[TRACE] Need provider schema for %s", providerAddr)
providerSchema := d.Evaluator.ProviderSchemas[providerAddr.ProviderConfig.Type]
if providerSchema == nil {
return nil
}
var schema *configschema.Block
switch addr.Mode { switch addr.Mode {
case addrs.ManagedResourceMode: case addrs.ManagedResourceMode:
schema = providerSchema.ResourceTypes[addr.Type] return schemas.ResourceTypeConfig(providerType, typeName)
case addrs.DataResourceMode: case addrs.DataResourceMode:
schema = providerSchema.DataSources[addr.Type] return schemas.DataSourceConfig(providerType, typeName)
default:
log.Printf("[WARN] Don't know how to fetch schema for resource %s", providerAddr)
return nil
} }
return schema
} }
func (d *evaluationStateData) GetTerraformAttr(addr addrs.TerraformAttr, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { func (d *evaluationStateData) GetTerraformAttr(addr addrs.TerraformAttr, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {

View File

@ -65,8 +65,7 @@ func (w *ContextGraphWalker) EnterPath(path addrs.ModuleInstance) EvalContext {
Operation: w.Operation, Operation: w.Operation,
State: w.Context.state, State: w.Context.state,
StateLock: &w.Context.stateLock, StateLock: &w.Context.stateLock,
ProviderSchemas: w.providerSchemas, Schemas: w.Context.schemas,
ProvidersLock: &w.providerLock,
VariableValues: w.variableValues, VariableValues: w.variableValues,
VariableValuesLock: &w.variableValuesLock, VariableValuesLock: &w.variableValuesLock,
} }
@ -77,9 +76,9 @@ func (w *ContextGraphWalker) EnterPath(path addrs.ModuleInstance) EvalContext {
Hooks: w.Context.hooks, Hooks: w.Context.hooks,
InputValue: w.Context.uiInput, InputValue: w.Context.uiInput,
Components: w.Context.components, Components: w.Context.components,
Schemas: w.Context.schemas,
ProviderCache: w.providerCache, ProviderCache: w.providerCache,
ProviderInputConfig: w.Context.providerInputConfig, ProviderInputConfig: w.Context.providerInputConfig,
ProviderSchemas: w.providerSchemas,
ProviderLock: &w.providerLock, ProviderLock: &w.providerLock,
ProvisionerCache: w.provisionerCache, ProvisionerCache: w.provisionerCache,
ProvisionerLock: &w.provisionerLock, ProvisionerLock: &w.provisionerLock,

View File

@ -17,11 +17,23 @@ type Schemas struct {
provisioners map[string]*configschema.Block provisioners map[string]*configschema.Block
} }
// ProviderSchema returns the entire ProviderSchema object that was produced
// by the plugin for the given provider, or nil if no such schema is available.
//
// It's usually better to go use the more precise methods offered by type
// Schemas to handle this detail automatically.
func (ss *Schemas) ProviderSchema(typeName string) *ProviderSchema {
if ss.providers == nil {
return nil
}
return ss.providers[typeName]
}
// ProviderConfig returns the schema for the provider configuration of the // ProviderConfig returns the schema for the provider configuration of the
// given provider type, or nil if no such schema is available. // given provider type, or nil if no such schema is available.
func (ss *Schemas) ProviderConfig(typeName string) *configschema.Block { func (ss *Schemas) ProviderConfig(typeName string) *configschema.Block {
ps, exists := ss.providers[typeName] ps := ss.ProviderSchema(typeName)
if !exists { if ps == nil {
return nil return nil
} }
return ps.Provider return ps.Provider
@ -37,12 +49,8 @@ func (ss *Schemas) ProviderConfig(typeName string) *configschema.Block {
// always pass the correct provider name, even though it many cases it feels // always pass the correct provider name, even though it many cases it feels
// redundant. // redundant.
func (ss *Schemas) ResourceTypeConfig(providerType string, resourceType string) *configschema.Block { func (ss *Schemas) ResourceTypeConfig(providerType string, resourceType string) *configschema.Block {
ps, exists := ss.providers[providerType] ps := ss.ProviderSchema(providerType)
if !exists { if ps == nil || ps.ResourceTypes == nil {
return nil
}
if ps.ResourceTypes == nil {
return nil return nil
} }
@ -59,12 +67,8 @@ func (ss *Schemas) ResourceTypeConfig(providerType string, resourceType string)
// always pass the correct provider name, even though it many cases it feels // always pass the correct provider name, even though it many cases it feels
// redundant. // redundant.
func (ss *Schemas) DataSourceConfig(providerType string, dataSource string) *configschema.Block { func (ss *Schemas) DataSourceConfig(providerType string, dataSource string) *configschema.Block {
ps, exists := ss.providers[providerType] ps := ss.ProviderSchema(providerType)
if !exists { if ps == nil || ps.DataSources == nil {
return nil
}
if ps.DataSources == nil {
return nil return nil
} }
@ -197,6 +201,7 @@ func loadProviderSchemas(schemas map[string]*ProviderSchema, config *configs.Con
// There's a check deeper in Terraform that makes this a // There's a check deeper in Terraform that makes this a
// failure when an empty/invalid provider string is present // failure when an empty/invalid provider string is present
// in practice. // in practice.
log.Printf("[WARN] LoadSchemas: Resource %s in %s has invalid provider address %q in its state", rsKey, moduleAddrStr, providerAddrStr)
diags = diags.Append( diags = diags.Append(
tfdiags.SimpleWarning(fmt.Sprintf("Resource %s in %s has invalid provider address %q in its state", rsKey, moduleAddrStr, providerAddrStr)), tfdiags.SimpleWarning(fmt.Sprintf("Resource %s in %s has invalid provider address %q in its state", rsKey, moduleAddrStr, providerAddrStr)),
) )