core: load a provider's schema at initialization

This is currently not very ergonomic due to the API exposed by providers.
We'll smooth this out in a later change to improve the provider API, since
we know we always want the entire schema.
This commit is contained in:
Martin Atkins 2018-04-19 14:47:36 -07:00
parent 3822650e15
commit a09498a8a3
4 changed files with 128 additions and 0 deletions

View File

@ -32,6 +32,10 @@ type EvalContext interface {
// initialized) or returns nil if the provider isn't initialized.
Provider(string) ResourceProvider
// ProviderSchema retrieves the schema for a particular provider, which
// must have already be initialized with InitProvider.
ProviderSchema(string) *ProviderSchema
// CloseProvider closes provider connections that aren't needed anymore.
CloseProvider(string) error

View File

@ -33,6 +33,7 @@ type BuiltinEvalContext struct {
Hooks []Hook
InputValue UIInput
ProviderCache map[string]ResourceProvider
ProviderSchemas map[string]*ProviderSchema
ProviderInputConfig map[string]map[string]interface{}
ProviderLock *sync.Mutex
ProvisionerCache map[string]ResourceProvisioner
@ -97,6 +98,35 @@ func (ctx *BuiltinEvalContext) InitProvider(typeName, name string) (ResourceProv
}
ctx.ProviderCache[name] = 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", name, err)
}
if ctx.ProviderSchemas == nil {
ctx.ProviderSchemas = make(map[string]*ProviderSchema)
}
ctx.ProviderSchemas[name] = schema
return p, nil
}
@ -109,6 +139,15 @@ func (ctx *BuiltinEvalContext) Provider(n string) ResourceProvider {
return ctx.ProviderCache[n]
}
func (ctx *BuiltinEvalContext) ProviderSchema(n string) *ProviderSchema {
ctx.once.Do(ctx.init)
ctx.ProviderLock.Lock()
defer ctx.ProviderLock.Unlock()
return ctx.ProviderSchemas[n]
}
func (ctx *BuiltinEvalContext) CloseProvider(n string) error {
ctx.once.Do(ctx.init)

View File

@ -4,6 +4,9 @@ import (
"reflect"
"sync"
"testing"
"github.com/davecgh/go-spew/spew"
"github.com/hashicorp/terraform/config/configschema"
)
func TestBuiltinEvalContextProviderInput(t *testing.T) {
@ -37,6 +40,78 @@ func TestBuiltinEvalContextProviderInput(t *testing.T) {
}
}
func TestBuildingEvalContextInitProvider(t *testing.T) {
var lock sync.Mutex
testP := &MockResourceProvider{
ResourcesReturn: []ResourceType{
{
Name: "test_thing",
SchemaAvailable: true,
},
},
DataSourcesReturn: []DataSource{
{
Name: "test_thing",
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.ProviderLock = &lock
ctx.ProviderCache = make(map[string]ResourceProvider)
ctx.Components = &basicComponentFactory{
providers: map[string]ResourceProviderFactory{
"test": ResourceProviderFactoryFixed(testP),
},
}
_, err := ctx.InitProvider("test", "test")
if err != nil {
t.Fatalf("error initializing provider test: %s", err)
}
_, err = ctx.InitProvider("test", "test.foo")
if err != nil {
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("test")
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("test.foo")
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 {
return &BuiltinEvalContext{}
}

View File

@ -28,6 +28,10 @@ type MockEvalContext struct {
ProviderName string
ProviderProvider ResourceProvider
ProviderSchemaCalled bool
ProviderSchemaName string
ProviderSchemaSchema *ProviderSchema
CloseProviderCalled bool
CloseProviderName string
CloseProviderProvider ResourceProvider
@ -119,6 +123,12 @@ func (c *MockEvalContext) Provider(n string) ResourceProvider {
return c.ProviderProvider
}
func (c *MockEvalContext) ProviderSchema(n string) *ProviderSchema {
c.ProviderSchemaCalled = true
c.ProviderSchemaName = n
return c.ProviderSchemaSchema
}
func (c *MockEvalContext) CloseProvider(n string) error {
c.CloseProviderCalled = true
c.CloseProviderName = n