From a09498a8a392ef3b3e09283094e7eaf9a7a41338 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Thu, 19 Apr 2018 14:47:36 -0700 Subject: [PATCH] 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. --- terraform/eval_context.go | 4 ++ terraform/eval_context_builtin.go | 39 ++++++++++++++ terraform/eval_context_builtin_test.go | 75 ++++++++++++++++++++++++++ terraform/eval_context_mock.go | 10 ++++ 4 files changed, 128 insertions(+) diff --git a/terraform/eval_context.go b/terraform/eval_context.go index 86481dedb..5d0aef8de 100644 --- a/terraform/eval_context.go +++ b/terraform/eval_context.go @@ -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 diff --git a/terraform/eval_context_builtin.go b/terraform/eval_context_builtin.go index 1b6ee5a62..f0ee1f021 100644 --- a/terraform/eval_context_builtin.go +++ b/terraform/eval_context_builtin.go @@ -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) diff --git a/terraform/eval_context_builtin_test.go b/terraform/eval_context_builtin_test.go index 4d07e75e0..e1ab95268 100644 --- a/terraform/eval_context_builtin_test.go +++ b/terraform/eval_context_builtin_test.go @@ -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{} } diff --git a/terraform/eval_context_mock.go b/terraform/eval_context_mock.go index 646451795..066463041 100644 --- a/terraform/eval_context_mock.go +++ b/terraform/eval_context_mock.go @@ -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