helper/schema: Resource can be writable or not

In the "schema" layer a Resource is just any "thing" that has a schema
and supports some or all of the CRUD operations. Data sources introduce
a new use of Resource to represent read-only resources, which require
some different InternalValidate logic.
This commit is contained in:
Martin Atkins 2016-04-16 17:27:00 -07:00
parent 0e0e3d73af
commit 6a468dcd83
4 changed files with 53 additions and 14 deletions

View File

@ -75,21 +75,15 @@ func (p *Provider) InternalValidate() error {
} }
for k, r := range p.ResourcesMap { for k, r := range p.ResourcesMap {
if err := r.InternalValidate(nil); err != nil { if err := r.InternalValidate(nil, true); err != nil {
return fmt.Errorf("resource %s: %s", k, err) return fmt.Errorf("resource %s: %s", k, err)
} }
} }
for k, r := range p.DataSourcesMap { for k, r := range p.DataSourcesMap {
if err := r.InternalValidate(nil); err != nil { if err := r.InternalValidate(nil, false); err != nil {
return fmt.Errorf("data source %s: %s", k, err) return fmt.Errorf("data source %s: %s", k, err)
} }
if r.Create != nil || r.Update != nil || r.Delete != nil {
return fmt.Errorf(
"data source %s: must not have Create, Update or Delete", k,
)
}
} }
return nil return nil

View File

@ -14,6 +14,10 @@ import (
// The Resource schema is an abstraction that allows provider writers to // The Resource schema is an abstraction that allows provider writers to
// worry only about CRUD operations while off-loading validation, diff // worry only about CRUD operations while off-loading validation, diff
// generation, etc. to this higher level library. // generation, etc. to this higher level library.
//
// In spite of the name, this struct is not used only for terraform resources,
// but also for data sources. In the case of data sources, the Create,
// Update and Delete functions must not be provided.
type Resource struct { type Resource struct {
// Schema is the schema for the configuration of this resource. // Schema is the schema for the configuration of this resource.
// //
@ -260,13 +264,20 @@ func (r *Resource) Refresh(
// Provider.InternalValidate() will automatically call this for all of // Provider.InternalValidate() will automatically call this for all of
// the resources it manages, so you don't need to call this manually if it // the resources it manages, so you don't need to call this manually if it
// is part of a Provider. // is part of a Provider.
func (r *Resource) InternalValidate(topSchemaMap schemaMap) error { func (r *Resource) InternalValidate(topSchemaMap schemaMap, writable bool) error {
if r == nil { if r == nil {
return errors.New("resource is nil") return errors.New("resource is nil")
} }
if !writable {
if r.Create != nil || r.Update != nil || r.Delete != nil {
return fmt.Errorf("must not implement Create, Update or Delete")
}
}
tsm := topSchemaMap tsm := topSchemaMap
if r.isTopLevel() { if r.isTopLevel() && writable {
// All non-Computed attributes must be ForceNew if Update is not defined // All non-Computed attributes must be ForceNew if Update is not defined
if r.Update == nil { if r.Update == nil {
nonForceNewAttrs := make([]string, 0) nonForceNewAttrs := make([]string, 0)

View File

@ -395,12 +395,14 @@ func TestResourceApply_isNewResource(t *testing.T) {
func TestResourceInternalValidate(t *testing.T) { func TestResourceInternalValidate(t *testing.T) {
cases := []struct { cases := []struct {
In *Resource In *Resource
Err bool Writable bool
Err bool
}{ }{
{ {
nil, nil,
true, true,
true,
}, },
// No optional and no required // No optional and no required
@ -415,6 +417,7 @@ func TestResourceInternalValidate(t *testing.T) {
}, },
}, },
true, true,
true,
}, },
// Update undefined for non-ForceNew field // Update undefined for non-ForceNew field
@ -429,6 +432,7 @@ func TestResourceInternalValidate(t *testing.T) {
}, },
}, },
true, true,
true,
}, },
// Update defined for ForceNew field // Update defined for ForceNew field
@ -445,11 +449,41 @@ func TestResourceInternalValidate(t *testing.T) {
}, },
}, },
true, true,
true,
},
// non-writable doesn't need Update, Create or Delete
{
&Resource{
Schema: map[string]*Schema{
"goo": &Schema{
Type: TypeInt,
Optional: true,
},
},
},
false,
false,
},
// non-writable *must not* have Create
{
&Resource{
Create: func(d *ResourceData, meta interface{}) error { return nil },
Schema: map[string]*Schema{
"goo": &Schema{
Type: TypeInt,
Optional: true,
},
},
},
false,
true,
}, },
} }
for i, tc := range cases { for i, tc := range cases {
err := tc.In.InternalValidate(schemaMap{}) err := tc.In.InternalValidate(schemaMap{}, tc.Writable)
if err != nil != tc.Err { if err != nil != tc.Err {
t.Fatalf("%d: bad: %s", i, err) t.Fatalf("%d: bad: %s", i, err)
} }

View File

@ -553,7 +553,7 @@ func (m schemaMap) InternalValidate(topSchemaMap schemaMap) error {
switch t := v.Elem.(type) { switch t := v.Elem.(type) {
case *Resource: case *Resource:
if err := t.InternalValidate(topSchemaMap); err != nil { if err := t.InternalValidate(topSchemaMap, true); err != nil {
return err return err
} }
case *Schema: case *Schema: