add implicit "id" to resource attribute schemas

When converting a legacy schemaMap to a configschema, we need to add
"id" as a required attribute to top-level resources if it's not
declared.

The "id" field will be required to interoperate with the legacy helper
schema, since the presence of an id was used to indicate the existence
of a resource.
This commit is contained in:
James Bardin 2018-07-11 20:59:32 -04:00 committed by Martin Atkins
parent e3f706c97d
commit befa81c74f
3 changed files with 71 additions and 27 deletions

View File

@ -153,9 +153,29 @@ func (s *Schema) coreConfigSchemaType() cty.Type {
} }
} }
// CoreConfigSchema is a convenient shortcut for calling CoreConfigSchema // CoreConfigSchema is a convenient shortcut for calling CoreConfigSchema on
// on the resource's schema. // the resource's schema. CoreConfigSchema adds the implicitly required "id"
// attribute for top level resources if it doesn't exist.
func (r *Resource) CoreConfigSchema() *configschema.Block { func (r *Resource) CoreConfigSchema() *configschema.Block {
block := r.coreConfigSchema()
if block.Attributes == nil {
block.Attributes = map[string]*configschema.Attribute{}
}
// Add the implicitly required "id" field if it doesn't exist
if block.Attributes["id"] == nil {
block.Attributes["id"] = &configschema.Attribute{
Type: cty.String,
Optional: true,
Computed: true,
}
}
return block
}
func (r *Resource) coreConfigSchema() *configschema.Block {
return schemaMap(r.Schema).CoreConfigSchema() return schemaMap(r.Schema).CoreConfigSchema()
} }

View File

@ -1,16 +1,40 @@
package schema package schema
import ( import (
"reflect"
"testing" "testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty"
"github.com/davecgh/go-spew/spew"
"github.com/hashicorp/terraform/configs/configschema" "github.com/hashicorp/terraform/configs/configschema"
) )
var (
ignoreUnexported = cmpopts.IgnoreUnexported(cty.Type{})
equateEmpty = cmpopts.EquateEmpty()
)
// add the implicit "id" attribute for test resources
func testResource(block *configschema.Block) *configschema.Block {
if block.Attributes == nil {
block.Attributes = make(map[string]*configschema.Attribute)
}
if block.BlockTypes == nil {
block.BlockTypes = make(map[string]*configschema.NestedBlock)
}
if block.Attributes["id"] == nil {
block.Attributes["id"] = &configschema.Attribute{
Type: cty.String,
Optional: true,
Computed: true,
}
}
return block
}
func TestSchemaMapCoreConfigSchema(t *testing.T) { func TestSchemaMapCoreConfigSchema(t *testing.T) {
tests := map[string]struct { tests := map[string]struct {
Schema map[string]*Schema Schema map[string]*Schema
@ -18,7 +42,7 @@ func TestSchemaMapCoreConfigSchema(t *testing.T) {
}{ }{
"empty": { "empty": {
map[string]*Schema{}, map[string]*Schema{},
&configschema.Block{}, testResource(&configschema.Block{}),
}, },
"primitives": { "primitives": {
map[string]*Schema{ map[string]*Schema{
@ -41,7 +65,7 @@ func TestSchemaMapCoreConfigSchema(t *testing.T) {
Computed: true, Computed: true,
}, },
}, },
&configschema.Block{ testResource(&configschema.Block{
Attributes: map[string]*configschema.Attribute{ Attributes: map[string]*configschema.Attribute{
"int": { "int": {
Type: cty.Number, Type: cty.Number,
@ -63,7 +87,7 @@ func TestSchemaMapCoreConfigSchema(t *testing.T) {
}, },
}, },
BlockTypes: map[string]*configschema.NestedBlock{}, BlockTypes: map[string]*configschema.NestedBlock{},
}, }),
}, },
"simple collections": { "simple collections": {
map[string]*Schema{ map[string]*Schema{
@ -96,7 +120,7 @@ func TestSchemaMapCoreConfigSchema(t *testing.T) {
// for pre-existing schemas. // for pre-existing schemas.
}, },
}, },
&configschema.Block{ testResource(&configschema.Block{
Attributes: map[string]*configschema.Attribute{ Attributes: map[string]*configschema.Attribute{
"list": { "list": {
Type: cty.List(cty.Number), Type: cty.List(cty.Number),
@ -116,7 +140,7 @@ func TestSchemaMapCoreConfigSchema(t *testing.T) {
}, },
}, },
BlockTypes: map[string]*configschema.NestedBlock{}, BlockTypes: map[string]*configschema.NestedBlock{},
}, }),
}, },
"incorrectly-specified collections": { "incorrectly-specified collections": {
// Historically we tolerated setting a type directly as the Elem // Historically we tolerated setting a type directly as the Elem
@ -140,7 +164,7 @@ func TestSchemaMapCoreConfigSchema(t *testing.T) {
Elem: TypeBool, Elem: TypeBool,
}, },
}, },
&configschema.Block{ testResource(&configschema.Block{
Attributes: map[string]*configschema.Attribute{ Attributes: map[string]*configschema.Attribute{
"list": { "list": {
Type: cty.List(cty.Number), Type: cty.List(cty.Number),
@ -156,7 +180,7 @@ func TestSchemaMapCoreConfigSchema(t *testing.T) {
}, },
}, },
BlockTypes: map[string]*configschema.NestedBlock{}, BlockTypes: map[string]*configschema.NestedBlock{},
}, }),
}, },
"sub-resource collections": { "sub-resource collections": {
map[string]*Schema{ map[string]*Schema{
@ -184,7 +208,7 @@ func TestSchemaMapCoreConfigSchema(t *testing.T) {
}, },
}, },
}, },
&configschema.Block{ testResource(&configschema.Block{
Attributes: map[string]*configschema.Attribute{}, Attributes: map[string]*configschema.Attribute{},
BlockTypes: map[string]*configschema.NestedBlock{ BlockTypes: map[string]*configschema.NestedBlock{
"list": { "list": {
@ -203,7 +227,7 @@ func TestSchemaMapCoreConfigSchema(t *testing.T) {
Block: configschema.Block{}, Block: configschema.Block{},
}, },
}, },
}, }),
}, },
"nested attributes and blocks": { "nested attributes and blocks": {
map[string]*Schema{ map[string]*Schema{
@ -233,7 +257,7 @@ func TestSchemaMapCoreConfigSchema(t *testing.T) {
}, },
}, },
}, },
&configschema.Block{ testResource(&configschema.Block{
Attributes: map[string]*configschema.Attribute{}, Attributes: map[string]*configschema.Attribute{},
BlockTypes: map[string]*configschema.NestedBlock{ BlockTypes: map[string]*configschema.NestedBlock{
"foo": &configschema.NestedBlock{ "foo": &configschema.NestedBlock{
@ -255,7 +279,7 @@ func TestSchemaMapCoreConfigSchema(t *testing.T) {
MinItems: 1, // because schema is Required MinItems: 1, // because schema is Required
}, },
}, },
}, }),
}, },
"sensitive": { "sensitive": {
map[string]*Schema{ map[string]*Schema{
@ -265,7 +289,7 @@ func TestSchemaMapCoreConfigSchema(t *testing.T) {
Sensitive: true, Sensitive: true,
}, },
}, },
&configschema.Block{ testResource(&configschema.Block{
Attributes: map[string]*configschema.Attribute{ Attributes: map[string]*configschema.Attribute{
"string": { "string": {
Type: cty.String, Type: cty.String,
@ -274,15 +298,15 @@ func TestSchemaMapCoreConfigSchema(t *testing.T) {
}, },
}, },
BlockTypes: map[string]*configschema.NestedBlock{}, BlockTypes: map[string]*configschema.NestedBlock{},
}, }),
}, },
} }
for name, test := range tests { for name, test := range tests {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
got := schemaMap(test.Schema).CoreConfigSchema() got := schemaMap(test.Schema).CoreConfigSchema()
if !reflect.DeepEqual(got, test.Want) { if !cmp.Equal(got, test.Want, equateEmpty, ignoreUnexported) {
t.Errorf("wrong result\ngot: %swant: %s", spew.Sdump(got), spew.Sdump(test.Want)) cmp.Diff(got, test.Want, equateEmpty, ignoreUnexported)
} }
}) })
} }

View File

@ -6,7 +6,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/davecgh/go-spew/spew" "github.com/google/go-cmp/cmp"
"github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config"
@ -61,7 +61,7 @@ func TestProviderGetSchema(t *testing.T) {
BlockTypes: map[string]*configschema.NestedBlock{}, BlockTypes: map[string]*configschema.NestedBlock{},
}, },
ResourceTypes: map[string]*configschema.Block{ ResourceTypes: map[string]*configschema.Block{
"foo": &configschema.Block{ "foo": testResource(&configschema.Block{
Attributes: map[string]*configschema.Attribute{ Attributes: map[string]*configschema.Attribute{
"bar": &configschema.Attribute{ "bar": &configschema.Attribute{
Type: cty.String, Type: cty.String,
@ -69,10 +69,10 @@ func TestProviderGetSchema(t *testing.T) {
}, },
}, },
BlockTypes: map[string]*configschema.NestedBlock{}, BlockTypes: map[string]*configschema.NestedBlock{},
}, }),
}, },
DataSources: map[string]*configschema.Block{ DataSources: map[string]*configschema.Block{
"baz": &configschema.Block{ "baz": testResource(&configschema.Block{
Attributes: map[string]*configschema.Attribute{ Attributes: map[string]*configschema.Attribute{
"bur": &configschema.Attribute{ "bur": &configschema.Attribute{
Type: cty.String, Type: cty.String,
@ -80,7 +80,7 @@ func TestProviderGetSchema(t *testing.T) {
}, },
}, },
BlockTypes: map[string]*configschema.NestedBlock{}, BlockTypes: map[string]*configschema.NestedBlock{},
}, }),
}, },
} }
got, err := p.GetSchema(&terraform.ProviderSchemaRequest{ got, err := p.GetSchema(&terraform.ProviderSchemaRequest{
@ -91,8 +91,8 @@ func TestProviderGetSchema(t *testing.T) {
t.Fatalf("unexpected error %s", err) t.Fatalf("unexpected error %s", err)
} }
if !reflect.DeepEqual(got, want) { if !cmp.Equal(got, want, equateEmpty, ignoreUnexported) {
t.Errorf("wrong result\ngot: %swant: %s", spew.Sdump(got), spew.Sdump(want)) t.Error("wrong result:\n", cmp.Diff(got, want, equateEmpty, ignoreUnexported))
} }
} }