Skip to content

Commit

Permalink
Merge pull request #18 from hashicorp/paddy_more_lenient_schema_checks
Browse files Browse the repository at this point in the history
Be more permissive when checking schemas are identical.
  • Loading branch information
paddycarver authored Feb 10, 2021
2 parents 3017945 + 6c12ef6 commit 4b0c54e
Show file tree
Hide file tree
Showing 2 changed files with 334 additions and 4 deletions.
18 changes: 14 additions & 4 deletions schema_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,21 @@ import (
"strings"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
)

var _ tfprotov5.ProviderServer = SchemaServer{}

var cmpOptions = []cmp.Option{
cmpopts.SortSlices(func(i, j *tfprotov5.SchemaAttribute) bool {
return i.Name < j.Name
}),
cmpopts.SortSlices(func(i, j *tfprotov5.SchemaNestedBlock) bool {
return i.TypeName < j.TypeName
}),
}

// SchemaServerFactory is a generator for SchemaServers, which are Terraform
// gRPC servers that route requests to different gRPC provider implementations
// based on which gRPC provider implementation supports the resource the
Expand Down Expand Up @@ -79,14 +89,14 @@ func NewSchemaServerFactory(ctx context.Context, servers ...func() tfprotov5.Pro
}
return factory, fmt.Errorf("error retrieving schema for %T:\n\n\tAttribute: %s\n\tSummary: %s\n\tDetail: %s", s, diag.Attribute, diag.Summary, diag.Detail)
}
if resp.Provider != nil && factory.providerSchema != nil && !cmp.Equal(resp.Provider, factory.providerSchema) {
return factory, fmt.Errorf("got a different provider schema from two servers (%T, %T). Provider schemas must be identical across providers.", factory.servers[factory.providerSchemaFrom](), s)
if resp.Provider != nil && factory.providerSchema != nil && !cmp.Equal(resp.Provider, factory.providerSchema, cmpOptions...) {
return factory, fmt.Errorf("got a different provider schema from two servers (%T, %T). Provider schemas must be identical across providers. Diff: %s", factory.servers[factory.providerSchemaFrom](), s, cmp.Diff(resp.Provider, factory.providerSchema, cmpOptions...))
} else if resp.Provider != nil {
factory.providerSchemaFrom = pos
factory.providerSchema = resp.Provider
}
if resp.ProviderMeta != nil && factory.providerMetaSchema != nil && !cmp.Equal(resp.ProviderMeta, factory.providerMetaSchema) {
return factory, fmt.Errorf("got a different provider_meta schema from two servers (%T, %T). Provider metadata schemas must be identical across providers.", factory.servers[factory.providerMetaSchemaFrom](), s)
if resp.ProviderMeta != nil && factory.providerMetaSchema != nil && !cmp.Equal(resp.ProviderMeta, factory.providerMetaSchema, cmpOptions...) {
return factory, fmt.Errorf("got a different provider_meta schema from two servers (%T, %T). Provider metadata schemas must be identical across providers. Diff: %s", factory.servers[factory.providerMetaSchemaFrom](), s, cmp.Diff(resp.ProviderMeta, factory.providerMetaSchema, cmpOptions...))
} else if resp.ProviderMeta != nil {
factory.providerMetaSchemaFrom = pos
factory.providerMetaSchema = resp.ProviderMeta
Expand Down
320 changes: 320 additions & 0 deletions schema_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,326 @@ func TestSchemaServerGetProviderSchema_errorDuplicateDataSource(t *testing.T) {
}
}

func TestSchemaServerGetProviderSchema_providerOutOfOrder(t *testing.T) {
server1 := testFactory(&testServer{
providerSchema: &tfprotov5.Schema{
Version: 1,
Block: &tfprotov5.SchemaBlock{
Version: 1,
Attributes: []*tfprotov5.SchemaAttribute{
{
Name: "account_id",
Type: tftypes.String,
Required: true,
Description: "the account ID to make requests for",
DescriptionKind: tfprotov5.StringKindPlain,
},
{
Name: "secret",
Type: tftypes.String,
Required: true,
Description: "the secret to authenticate with",
DescriptionKind: tfprotov5.StringKindPlain,
},
},
BlockTypes: []*tfprotov5.SchemaNestedBlock{
{
TypeName: "other_feature",
Nesting: tfprotov5.SchemaNestedBlockNestingModeList,
Block: &tfprotov5.SchemaBlock{
Version: 1,
Description: "features to enable on the provider",
DescriptionKind: tfprotov5.StringKindPlain,
Attributes: []*tfprotov5.SchemaAttribute{
{
Name: "enabled",
Type: tftypes.Bool,
Required: true,
Description: "whether the feature is enabled",
DescriptionKind: tfprotov5.StringKindPlain,
},
{
Name: "feature_id",
Type: tftypes.Number,
Required: true,
Description: "The ID of the feature",
DescriptionKind: tfprotov5.StringKindPlain,
},
},
},
},
{
TypeName: "feature",
Nesting: tfprotov5.SchemaNestedBlockNestingModeList,
Block: &tfprotov5.SchemaBlock{
Version: 1,
Description: "features to enable on the provider",
DescriptionKind: tfprotov5.StringKindPlain,
Attributes: []*tfprotov5.SchemaAttribute{
{
Name: "feature_id",
Type: tftypes.Number,
Required: true,
Description: "The ID of the feature",
DescriptionKind: tfprotov5.StringKindPlain,
},
{
Name: "enabled",
Type: tftypes.Bool,
Required: true,
Description: "whether the feature is enabled",
DescriptionKind: tfprotov5.StringKindPlain,
},
},
},
},
},
},
},
})
server2 := testFactory(&testServer{
providerSchema: &tfprotov5.Schema{
Version: 1,
Block: &tfprotov5.SchemaBlock{
Version: 1,
Attributes: []*tfprotov5.SchemaAttribute{
{
Name: "secret",
Type: tftypes.String,
Required: true,
Description: "the secret to authenticate with",
DescriptionKind: tfprotov5.StringKindPlain,
},
{
Name: "account_id",
Type: tftypes.String,
Required: true,
Description: "the account ID to make requests for",
DescriptionKind: tfprotov5.StringKindPlain,
},
},
BlockTypes: []*tfprotov5.SchemaNestedBlock{
{
TypeName: "feature",
Nesting: tfprotov5.SchemaNestedBlockNestingModeList,
Block: &tfprotov5.SchemaBlock{
Version: 1,
Description: "features to enable on the provider",
DescriptionKind: tfprotov5.StringKindPlain,
Attributes: []*tfprotov5.SchemaAttribute{
{
Name: "enabled",
Type: tftypes.Bool,
Required: true,
Description: "whether the feature is enabled",
DescriptionKind: tfprotov5.StringKindPlain,
},
{
Name: "feature_id",
Type: tftypes.Number,
Required: true,
Description: "The ID of the feature",
DescriptionKind: tfprotov5.StringKindPlain,
},
},
},
},
{
TypeName: "other_feature",
Nesting: tfprotov5.SchemaNestedBlockNestingModeList,
Block: &tfprotov5.SchemaBlock{
Version: 1,
Description: "features to enable on the provider",
DescriptionKind: tfprotov5.StringKindPlain,
Attributes: []*tfprotov5.SchemaAttribute{
{
Name: "enabled",
Type: tftypes.Bool,
Required: true,
Description: "whether the feature is enabled",
DescriptionKind: tfprotov5.StringKindPlain,
},
{
Name: "feature_id",
Type: tftypes.Number,
Required: true,
Description: "The ID of the feature",
DescriptionKind: tfprotov5.StringKindPlain,
},
},
},
},
},
},
},
})

_, err := NewSchemaServerFactory(context.Background(), server1, server2)
if err != nil {
t.Error(err)
}
}

func TestSchemaServerGetProviderSchema_providerMetaOutOfOrder(t *testing.T) {
server1 := testFactory(&testServer{
providerMetaSchema: &tfprotov5.Schema{
Version: 1,
Block: &tfprotov5.SchemaBlock{
Version: 1,
Attributes: []*tfprotov5.SchemaAttribute{
{
Name: "account_id",
Type: tftypes.String,
Required: true,
Description: "the account ID to make requests for",
DescriptionKind: tfprotov5.StringKindPlain,
},
{
Name: "secret",
Type: tftypes.String,
Required: true,
Description: "the secret to authenticate with",
DescriptionKind: tfprotov5.StringKindPlain,
},
},
BlockTypes: []*tfprotov5.SchemaNestedBlock{
{
TypeName: "feature",
Nesting: tfprotov5.SchemaNestedBlockNestingModeList,
Block: &tfprotov5.SchemaBlock{
Version: 1,
Description: "features to enable on the provider",
DescriptionKind: tfprotov5.StringKindPlain,
Attributes: []*tfprotov5.SchemaAttribute{
{
Name: "feature_id",
Type: tftypes.Number,
Required: true,
Description: "The ID of the feature",
DescriptionKind: tfprotov5.StringKindPlain,
},
{
Name: "enabled",
Type: tftypes.Bool,
Required: true,
Description: "whether the feature is enabled",
DescriptionKind: tfprotov5.StringKindPlain,
},
},
},
},
{
TypeName: "other_feature",
Nesting: tfprotov5.SchemaNestedBlockNestingModeList,
Block: &tfprotov5.SchemaBlock{
Version: 1,
Description: "features to enable on the provider",
DescriptionKind: tfprotov5.StringKindPlain,
Attributes: []*tfprotov5.SchemaAttribute{
{
Name: "enabled",
Type: tftypes.Bool,
Required: true,
Description: "whether the feature is enabled",
DescriptionKind: tfprotov5.StringKindPlain,
},
{
Name: "feature_id",
Type: tftypes.Number,
Required: true,
Description: "The ID of the feature",
DescriptionKind: tfprotov5.StringKindPlain,
},
},
},
},
},
},
},
})
server2 := testFactory(&testServer{
providerMetaSchema: &tfprotov5.Schema{
Version: 1,
Block: &tfprotov5.SchemaBlock{
Version: 1,
Attributes: []*tfprotov5.SchemaAttribute{
{
Name: "secret",
Type: tftypes.String,
Required: true,
Description: "the secret to authenticate with",
DescriptionKind: tfprotov5.StringKindPlain,
},
{
Name: "account_id",
Type: tftypes.String,
Required: true,
Description: "the account ID to make requests for",
DescriptionKind: tfprotov5.StringKindPlain,
},
},
BlockTypes: []*tfprotov5.SchemaNestedBlock{
{
TypeName: "other_feature",
Nesting: tfprotov5.SchemaNestedBlockNestingModeList,
Block: &tfprotov5.SchemaBlock{
Version: 1,
Description: "features to enable on the provider",
DescriptionKind: tfprotov5.StringKindPlain,
Attributes: []*tfprotov5.SchemaAttribute{
{
Name: "feature_id",
Type: tftypes.Number,
Required: true,
Description: "The ID of the feature",
DescriptionKind: tfprotov5.StringKindPlain,
},
{
Name: "enabled",
Type: tftypes.Bool,
Required: true,
Description: "whether the feature is enabled",
DescriptionKind: tfprotov5.StringKindPlain,
},
},
},
},
{
TypeName: "feature",
Nesting: tfprotov5.SchemaNestedBlockNestingModeList,
Block: &tfprotov5.SchemaBlock{
Version: 1,
Description: "features to enable on the provider",
DescriptionKind: tfprotov5.StringKindPlain,
Attributes: []*tfprotov5.SchemaAttribute{
{
Name: "enabled",
Type: tftypes.Bool,
Required: true,
Description: "whether the feature is enabled",
DescriptionKind: tfprotov5.StringKindPlain,
},
{
Name: "feature_id",
Type: tftypes.Number,
Required: true,
Description: "The ID of the feature",
DescriptionKind: tfprotov5.StringKindPlain,
},
},
},
},
},
},
},
})

_, err := NewSchemaServerFactory(context.Background(), server1, server2)
if err != nil {
t.Error(err)
}
}

func TestSchemaServerGetProviderSchema_errorProviderMismatch(t *testing.T) {
server1 := testFactory(&testServer{
providerSchema: &tfprotov5.Schema{
Expand Down

0 comments on commit 4b0c54e

Please sign in to comment.