helper/schema: create/update/delete should work for Resource

This commit is contained in:
Mitchell Hashimoto 2014-08-17 20:20:11 -07:00
parent 894187ec18
commit c418681cc3
3 changed files with 246 additions and 0 deletions

View File

@ -31,6 +31,64 @@ type Resource struct {
Delete DeleteFunc Delete DeleteFunc
} }
// Apply creates, updates, and/or deletes a resource.
func (r *Resource) Apply(
s *terraform.ResourceState,
d *terraform.ResourceDiff,
meta interface{}) (*terraform.ResourceState, error) {
data, err := schemaMap(r.Schema).Data(s, d)
if err != nil {
return s, err
}
if s == nil {
// The Terraform API dictates that this should never happen, but
// it doesn't hurt to be safe in this case.
s = new(terraform.ResourceState)
}
if d.Destroy || d.RequiresNew() {
if s.ID != "" {
// Destroy the resource since it is created
if err := r.Delete(data, meta); err != nil {
return data.State(), err
}
// Reset the data to be empty
data, err = schemaMap(r.Schema).Data(nil, d)
if err != nil {
return nil, err
}
}
// If we're only destroying, and not creating, then return
// now since we're done!
if !d.RequiresNew() {
return nil, nil
}
}
err = nil
if s.ID == "" {
// We're creating, it is a new resource.
err = r.Create(data, meta)
} else {
err = r.Update(data, meta)
}
// Always set the ID attribute if it is set. We also always collapse
// the state since even partial states need to be returned.
state := data.State()
if state.ID != "" {
if state.Attributes == nil {
state.Attributes = make(map[string]string)
}
state.Attributes["id"] = state.ID
}
return state, err
}
// Diff returns a diff of this resource and is API compatible with the // Diff returns a diff of this resource and is API compatible with the
// ResourceProvider interface. // ResourceProvider interface.
func (r *Resource) Diff( func (r *Resource) Diff(

View File

@ -46,6 +46,16 @@ func (d *ResourceData) Set(key string, value interface{}) error {
return d.setObject("", parts, d.schema, value) return d.setObject("", parts, d.schema, value)
} }
// SetId sets the ID of the resource. If the value is blank, then the
// resource is destroyed.
func (d *ResourceData) SetId(v string) {
if d.newState == nil {
d.newState = new(terraform.ResourceState)
}
d.newState.ID = v
}
// State returns the new ResourceState after the diff and any Set // State returns the new ResourceState after the diff and any Set
// calls. // calls.
func (d *ResourceData) State() *terraform.ResourceState { func (d *ResourceData) State() *terraform.ResourceState {

View File

@ -8,6 +8,184 @@ import (
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
func TestResourceApply_create(t *testing.T) {
r := &Resource{
Schema: map[string]*Schema{
"foo": &Schema{
Type: TypeInt,
Optional: true,
},
},
}
called := false
r.Create = func(d *ResourceData, m interface{}) error {
called = true
d.SetId("foo")
return nil
}
var s *terraform.ResourceState = nil
d := &terraform.ResourceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"foo": &terraform.ResourceAttrDiff{
New: "42",
},
},
}
actual, err := r.Apply(s, d, nil)
if err != nil {
t.Fatalf("err: %s", err)
}
if !called {
t.Fatal("not called")
}
expected := &terraform.ResourceState{
ID: "foo",
Attributes: map[string]string{
"id": "foo",
"foo": "42",
},
}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
}
func TestResourceApply_destroy(t *testing.T) {
r := &Resource{
Schema: map[string]*Schema{
"foo": &Schema{
Type: TypeInt,
Optional: true,
},
},
}
called := false
r.Delete = func(d *ResourceData, m interface{}) error {
called = true
return nil
}
s := &terraform.ResourceState{
ID: "bar",
}
d := &terraform.ResourceDiff{
Destroy: true,
}
actual, err := r.Apply(s, d, nil)
if err != nil {
t.Fatalf("err: %s", err)
}
if !called {
t.Fatal("delete not called")
}
if actual != nil {
t.Fatalf("bad: %#v", actual)
}
}
func TestResourceApply_destroyPartial(t *testing.T) {
r := &Resource{
Schema: map[string]*Schema{
"foo": &Schema{
Type: TypeInt,
Optional: true,
},
},
}
r.Delete = func(d *ResourceData, m interface{}) error {
d.Set("foo", 42)
return fmt.Errorf("some error")
}
s := &terraform.ResourceState{
ID: "bar",
Attributes: map[string]string{
"foo": "12",
},
}
d := &terraform.ResourceDiff{
Destroy: true,
}
actual, err := r.Apply(s, d, nil)
if err == nil {
t.Fatal("should error")
}
expected := &terraform.ResourceState{
ID: "bar",
Attributes: map[string]string{
"foo": "42",
},
}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
}
func TestResourceApply_update(t *testing.T) {
r := &Resource{
Schema: map[string]*Schema{
"foo": &Schema{
Type: TypeInt,
Optional: true,
},
},
}
r.Update = func(d *ResourceData, m interface{}) error {
d.Set("foo", 42)
return nil
}
s := &terraform.ResourceState{
ID: "foo",
Attributes: map[string]string{
"foo": "12",
},
}
d := &terraform.ResourceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"foo": &terraform.ResourceAttrDiff{
New: "13",
},
},
}
actual, err := r.Apply(s, d, nil)
if err != nil {
t.Fatalf("err: %s", err)
}
expected := &terraform.ResourceState{
ID: "foo",
Attributes: map[string]string{
"id": "foo",
"foo": "42",
},
}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
}
func TestResourceInternalValidate(t *testing.T) { func TestResourceInternalValidate(t *testing.T) {
cases := []struct { cases := []struct {
In *Resource In *Resource