Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core: ResourceProvider.GetSchema method #16352

Merged
merged 1 commit into from
Oct 17, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion helper/schema/core_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ import (
// panic or produce an invalid result if given an invalid schemaMap.
func (m schemaMap) CoreConfigSchema() *configschema.Block {
if len(m) == 0 {
return nil
// We return an actual (empty) object here, rather than a nil,
// because a nil result would mean that we don't have a schema at
// all, rather than that we have an empty one.
return &configschema.Block{}
}

ret := &configschema.Block{
Expand Down
2 changes: 1 addition & 1 deletion helper/schema/core_schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func TestSchemaMapCoreConfigSchema(t *testing.T) {
}{
"empty": {
map[string]*Schema{},
nil,
&configschema.Block{},
},
"primitives": {
map[string]*Schema{
Expand Down
32 changes: 32 additions & 0 deletions helper/schema/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/config/configschema"
"github.com/hashicorp/terraform/terraform"
)

Expand Down Expand Up @@ -185,6 +186,29 @@ func (p *Provider) TestReset() error {
return nil
}

// GetSchema implementation of terraform.ResourceProvider interface
func (p *Provider) GetSchema(req *terraform.ProviderSchemaRequest) (*terraform.ProviderSchema, error) {
resourceTypes := map[string]*configschema.Block{}
dataSources := map[string]*configschema.Block{}

for _, name := range req.ResourceTypes {
if r, exists := p.ResourcesMap[name]; exists {
resourceTypes[name] = r.CoreConfigSchema()
}
}
for _, name := range req.DataSources {
if r, exists := p.DataSourcesMap[name]; exists {
dataSources[name] = r.CoreConfigSchema()
}
}

return &terraform.ProviderSchema{
Provider: schemaMap(p.Schema).CoreConfigSchema(),
ResourceTypes: resourceTypes,
DataSources: dataSources,
}, nil
}

// Input implementation of terraform.ResourceProvider interface.
func (p *Provider) Input(
input terraform.UIInput,
Expand Down Expand Up @@ -305,6 +329,10 @@ func (p *Provider) Resources() []terraform.ResourceType {
result = append(result, terraform.ResourceType{
Name: k,
Importable: resource.Importer != nil,

// Indicates that a provider is compiled against a new enough
// version of core to support the GetSchema method.
SchemaAvailable: true,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the protocol version indicate if we support the correct methods?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This field is being used to avoid incrementing the protocol version so that we don't create a provider plugin compatibility wall yet. There will be some bigger changes to the provider protocol coming later, so the desire is to get it all out in a single increment so we can avoid putting users through that upgrade pain multiple times.

(If we increment the plugin protocol version then new provider releases become incompatible with older core versions, which returns us to the sad old world where you need to upgrade core in order to get the latest provider features. Tolerable to do once or twice, but not something I want to make a habit of...)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gotcha, just making sure I understood why the workaround was here.

})
}

Expand Down Expand Up @@ -410,6 +438,10 @@ func (p *Provider) DataSources() []terraform.DataSource {
for _, k := range keys {
result = append(result, terraform.DataSource{
Name: k,

// Indicates that a provider is compiled against a new enough
// version of core to support the GetSchema method.
SchemaAvailable: true,
})
}

Expand Down
96 changes: 89 additions & 7 deletions helper/schema/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,96 @@ import (
"testing"
"time"

"github.com/davecgh/go-spew/spew"
"github.com/zclconf/go-cty/cty"

"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/config/configschema"
"github.com/hashicorp/terraform/terraform"
)

func TestProvider_impl(t *testing.T) {
var _ terraform.ResourceProvider = new(Provider)
}

func TestProviderGetSchema(t *testing.T) {
// This functionality is already broadly tested in core_schema_test.go,
// so this is just to ensure that the call passes through correctly.
p := &Provider{
Schema: map[string]*Schema{
"bar": {
Type: TypeString,
Required: true,
},
},
ResourcesMap: map[string]*Resource{
"foo": &Resource{
Schema: map[string]*Schema{
"bar": {
Type: TypeString,
Required: true,
},
},
},
},
DataSourcesMap: map[string]*Resource{
"baz": &Resource{
Schema: map[string]*Schema{
"bur": {
Type: TypeString,
Required: true,
},
},
},
},
}

want := &terraform.ProviderSchema{
Provider: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"bar": &configschema.Attribute{
Type: cty.String,
Required: true,
},
},
BlockTypes: map[string]*configschema.NestedBlock{},
},
ResourceTypes: map[string]*configschema.Block{
"foo": &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"bar": &configschema.Attribute{
Type: cty.String,
Required: true,
},
},
BlockTypes: map[string]*configschema.NestedBlock{},
},
},
DataSources: map[string]*configschema.Block{
"baz": &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"bur": &configschema.Attribute{
Type: cty.String,
Required: true,
},
},
BlockTypes: map[string]*configschema.NestedBlock{},
},
},
}
got, err := p.GetSchema(&terraform.ProviderSchemaRequest{
ResourceTypes: []string{"foo", "bar"},
DataSources: []string{"baz", "bar"},
})
if err != nil {
t.Fatalf("unexpected error %s", err)
}

if !reflect.DeepEqual(got, want) {
t.Errorf("wrong result\ngot: %swant: %s", spew.Sdump(got), spew.Sdump(want))
}
}

func TestProviderConfigure(t *testing.T) {
cases := []struct {
P *Provider
Expand Down Expand Up @@ -104,8 +186,8 @@ func TestProviderResources(t *testing.T) {
},
},
Result: []terraform.ResourceType{
terraform.ResourceType{Name: "bar"},
terraform.ResourceType{Name: "foo"},
terraform.ResourceType{Name: "bar", SchemaAvailable: true},
terraform.ResourceType{Name: "foo", SchemaAvailable: true},
},
},

Expand All @@ -118,9 +200,9 @@ func TestProviderResources(t *testing.T) {
},
},
Result: []terraform.ResourceType{
terraform.ResourceType{Name: "bar", Importable: true},
terraform.ResourceType{Name: "baz"},
terraform.ResourceType{Name: "foo"},
terraform.ResourceType{Name: "bar", Importable: true, SchemaAvailable: true},
terraform.ResourceType{Name: "baz", SchemaAvailable: true},
terraform.ResourceType{Name: "foo", SchemaAvailable: true},
},
},
}
Expand Down Expand Up @@ -151,8 +233,8 @@ func TestProviderDataSources(t *testing.T) {
},
},
Result: []terraform.DataSource{
terraform.DataSource{Name: "bar"},
terraform.DataSource{Name: "foo"},
terraform.DataSource{Name: "bar", SchemaAvailable: true},
terraform.DataSource{Name: "foo", SchemaAvailable: true},
},
},
}
Expand Down
39 changes: 39 additions & 0 deletions plugin/resource_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,24 @@ func (p *ResourceProvider) Stop() error {
return err
}

func (p *ResourceProvider) GetSchema(req *terraform.ProviderSchemaRequest) (*terraform.ProviderSchema, error) {
var result ResourceProviderGetSchemaResponse
args := &ResourceProviderGetSchemaArgs{
Req: req,
}

err := p.Client.Call("Plugin.GetSchema", args, &result)
if err != nil {
return nil, err
}

if result.Error != nil {
err = result.Error
}

return result.Schema, err
}

func (p *ResourceProvider) Input(
input terraform.UIInput,
c *terraform.ResourceConfig) (*terraform.ResourceConfig, error) {
Expand Down Expand Up @@ -312,6 +330,15 @@ type ResourceProviderStopResponse struct {
Error *plugin.BasicError
}

type ResourceProviderGetSchemaArgs struct {
Req *terraform.ProviderSchemaRequest
}

type ResourceProviderGetSchemaResponse struct {
Schema *terraform.ProviderSchema
Error *plugin.BasicError
}

type ResourceProviderConfigureResponse struct {
Error *plugin.BasicError
}
Expand Down Expand Up @@ -418,6 +445,18 @@ func (s *ResourceProviderServer) Stop(
return nil
}

func (s *ResourceProviderServer) GetSchema(
args *ResourceProviderGetSchemaArgs,
result *ResourceProviderGetSchemaResponse,
) error {
schema, err := s.Provider.GetSchema(args.Req)
result.Schema = schema
if err != nil {
result.Error = plugin.NewBasicError(err)
}
return nil
}

func (s *ResourceProviderServer) Input(
args *ResourceProviderInputArgs,
reply *ResourceProviderInputResponse) error {
Expand Down
23 changes: 23 additions & 0 deletions terraform/resource_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ type ResourceProvider interface {
* Functions related to the provider
*********************************************************************/

// ProviderSchema returns the config schema for the main provider
// configuration, as would appear in a "provider" block in the
// configuration files.
//
// Currently not all providers support schema. Callers must therefore
// first call Resources and DataSources and ensure that at least one
// resource or data source has the SchemaAvailable flag set.
GetSchema(*ProviderSchemaRequest) (*ProviderSchema, error)

// Input is called to ask the provider to ask the user for input
// for completing the configuration if necesarry.
//
Expand Down Expand Up @@ -183,11 +192,25 @@ type ResourceProviderCloser interface {
type ResourceType struct {
Name string // Name of the resource, example "instance" (no provider prefix)
Importable bool // Whether this resource supports importing

// SchemaAvailable is set if the provider supports the ProviderSchema,
// ResourceTypeSchema and DataSourceSchema methods. Although it is
// included on each resource type, it's actually a provider-wide setting
// that's smuggled here only because that avoids a breaking change to
// the plugin protocol.
SchemaAvailable bool
}

// DataSource is a data source that a resource provider implements.
type DataSource struct {
Name string

// SchemaAvailable is set if the provider supports the ProviderSchema,
// ResourceTypeSchema and DataSourceSchema methods. Although it is
// included on each resource type, it's actually a provider-wide setting
// that's smuggled here only because that avoids a breaking change to
// the plugin protocol.
SchemaAvailable bool
}

// ResourceProviderResolver is an interface implemented by objects that are
Expand Down
17 changes: 16 additions & 1 deletion terraform/resource_provider_mock.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package terraform

import "sync"
import (
"sync"
)

// MockResourceProvider implements ResourceProvider but mocks out all the
// calls for testing purposes.
Expand All @@ -12,6 +14,10 @@ type MockResourceProvider struct {

CloseCalled bool
CloseError error
GetSchemaCalled bool
GetSchemaRequest *ProviderSchemaRequest
GetSchemaReturn *ProviderSchema
GetSchemaReturnError error
InputCalled bool
InputInput UIInput
InputConfig *ResourceConfig
Expand Down Expand Up @@ -92,6 +98,15 @@ func (p *MockResourceProvider) Close() error {
return p.CloseError
}

func (p *MockResourceProvider) GetSchema(req *ProviderSchemaRequest) (*ProviderSchema, error) {
p.Lock()
defer p.Unlock()

p.GetSchemaCalled = true
p.GetSchemaRequest = req
return p.GetSchemaReturn, p.GetSchemaReturnError
}

func (p *MockResourceProvider) Input(
input UIInput, c *ResourceConfig) (*ResourceConfig, error) {
p.Lock()
Expand Down
34 changes: 34 additions & 0 deletions terraform/schemas.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package terraform

import (
"github.com/hashicorp/terraform/config/configschema"
)

type Schemas struct {
Providers ProviderSchemas
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(this seemingly-unnecessary container will grow to include provisioner schemas in a later commit)

}

// ProviderSchemas is a map from provider names to provider schemas.
//
// The names in this map are the direct plugin name (e.g. "aws") rather than
// any alias name (e.g. "aws.foo"), since.
type ProviderSchemas map[string]*ProviderSchema

// ProviderSchema represents the schema for a provider's own configuration
// and the configuration for some or all of its resources and data sources.
//
// The completeness of this structure depends on how it was constructed.
// When constructed for a configuration, it will generally include only
// resource types and data sources used by that configuration.
type ProviderSchema struct {
Provider *configschema.Block
ResourceTypes map[string]*configschema.Block
DataSources map[string]*configschema.Block
}

// ProviderSchemaRequest is used to describe to a ResourceProvider which
// aspects of schema are required, when calling the GetSchema method.
type ProviderSchemaRequest struct {
ResourceTypes []string
DataSources []string
}