terraform: diff/state work better together, merge

This commit is contained in:
Mitchell Hashimoto 2014-06-05 06:57:06 -07:00
parent bd1f235b9b
commit 7c6920bba1
9 changed files with 163 additions and 43 deletions

View File

@ -91,19 +91,19 @@ func TestResourceReplaceVariables(t *testing.T) {
}
/*
TODO(mitchellh): Eventually, preserve original config...
TODO(mitchellh): Eventually, preserve original config...
expectedOriginal := &Resource{
Name: "foo",
Type: "bar",
Config: map[string]interface{}{
"foo": "${var.bar}",
},
}
expectedOriginal := &Resource{
Name: "foo",
Type: "bar",
Config: map[string]interface{}{
"foo": "${var.bar}",
},
}
if !reflect.DeepEqual(r, expectedOriginal) {
t.Fatalf("bad: %#v", r)
}
if !reflect.DeepEqual(r, expectedOriginal) {
t.Fatalf("bad: %#v", r)
}
*/
}

View File

@ -16,12 +16,15 @@ func (d *Diff) init() {
})
}
// ResourceAttrDiff is the diff of a single attribute of a resource.
//
// This tracks the old value, the new value, and whether the change of this
// value actually requires an entirely new resource.
type ResourceAttrDiff struct {
Old string
New string
RequiresNew bool
// ResourceDiff is the diff of a resource from some state to another.
type ResourceDiff struct {
Attributes map[string]*ResourceAttrDiff
}
// ResourceAttrDiff is the diff of a single attribute of a resource.
type ResourceAttrDiff struct {
Old string // Old Value
New string // New Value
NewComputed bool // True if new value is computed (unknown currently)
RequiresNew bool // True if change requires new resource
}

32
terraform/diff_test.go Normal file
View File

@ -0,0 +1,32 @@
package terraform
import (
"bytes"
"fmt"
"sort"
)
func testDiffStr(d *Diff) string {
var buf bytes.Buffer
names := make([]string, len(d.Resources))
for n, _ := range d.Resources {
names = append(names, n)
}
sort.Strings(names)
for _, n := range names {
r := d.Resources[n]
buf.WriteString(fmt.Sprintf("%s\n", n))
for attr, attrDiff := range r {
buf.WriteString(fmt.Sprintf(
" %s: %#v => %#v\n",
attr,
attrDiff.Old,
attrDiff.New,
))
}
}
return buf.String()
}

View File

@ -28,22 +28,6 @@ type ResourceProvider interface {
map[string]interface{}) (ResourceDiff, error)
}
// ResourceDiff is the diff of a resource from some state to another.
type ResourceDiff struct {
Attributes map[string]*ResourceAttrDiff
}
// ResourceState holds the state of a resource that is used so that
// a provider can find and manage an existing resource as well as for
// storing attributes that are uesd to populate variables of child
// resources.
type ResourceState struct {
Type string
ID string
Attributes map[string]string
Extra map[string]interface{}
}
// ResourceType is a type of resource that a resource provider can manage.
type ResourceType struct {
Name string

View File

@ -13,6 +13,7 @@ type MockResourceProvider struct {
DiffCalled bool
DiffState ResourceState
DiffDesired map[string]interface{}
DiffFn func(ResourceState, map[string]interface{}) (ResourceDiff, error)
DiffReturn ResourceDiff
DiffReturnError error
ResourcesCalled bool
@ -31,6 +32,10 @@ func (p *MockResourceProvider) Diff(
p.DiffCalled = true
p.DiffState = state
p.DiffDesired = desired
if p.DiffFn != nil {
return p.DiffFn(state, desired)
}
return p.DiffReturn, p.DiffReturnError
}

View File

@ -6,3 +6,37 @@ package terraform
type State struct {
resources map[string]ResourceState
}
// ResourceState holds the state of a resource that is used so that
// a provider can find and manage an existing resource as well as for
// storing attributes that are uesd to populate variables of child
// resources.
//
// Attributes has attributes about the created resource that are
// queryable in interpolation: "${type.id.attr}"
//
// Extra is just extra data that a provider can return that we store
// for later, but is not exposed in any way to the user.
type ResourceState struct {
ID string
Attributes map[string]string
Extra map[string]interface{}
}
// MergeDiff takes a ResourceDiff and merges the attributes into
// this resource state in order to generate a new state. This new
// state can be used to provide updated attribute lookups for
// variable interpolation.
func (s *ResourceState) MergeDiff(
d map[string]*ResourceAttrDiff) ResourceState {
result := *s
result.Attributes = make(map[string]string)
for k, v := range s.Attributes {
result.Attributes[k] = v
}
for k, diff := range d {
result.Attributes[k] = diff.New
}
return result
}

37
terraform/state_test.go Normal file
View File

@ -0,0 +1,37 @@
package terraform
import (
"reflect"
"testing"
)
func TestResourceState_MergeDiff(t *testing.T) {
rs := ResourceState{
ID: "foo",
Attributes: map[string]string{
"foo": "bar",
},
}
diff := map[string]*ResourceAttrDiff{
"foo": &ResourceAttrDiff{
Old: "bar",
New: "baz",
},
"bar": &ResourceAttrDiff{
Old: "",
New: "foo",
},
}
rs2 := rs.MergeDiff(diff)
expected := map[string]string{
"foo": "baz",
"bar": "foo",
}
if !reflect.DeepEqual(expected, rs2.Attributes) {
t.Fatalf("bad: %#v", rs2.Attributes)
}
}

View File

@ -2,6 +2,7 @@ package terraform
import (
"path/filepath"
"strings"
"testing"
"github.com/hashicorp/terraform/config"
@ -113,6 +114,12 @@ func TestTerraformDiff(t *testing.T) {
if len(diff.Resources) < 2 {
t.Fatalf("bad: %#v", diff.Resources)
}
actual := strings.TrimSpace(testDiffStr(diff))
expected := strings.TrimSpace(testTerraformDiffStr)
if actual != expected {
t.Fatalf("bad:\n%s", actual)
}
}
func testConfig(t *testing.T, name string) *config.Config {
@ -133,17 +140,28 @@ func testProviderFunc(n string, rs []string) ResourceProviderFactory {
}
return func() (ResourceProvider, error) {
var diff ResourceDiff
diff.Attributes = map[string]*ResourceAttrDiff{
n: &ResourceAttrDiff{
Old: "foo",
New: "bar",
},
diffFn := func(
_ ResourceState,
c map[string]interface{}) (ResourceDiff, error) {
var diff ResourceDiff
diff.Attributes = make(map[string]*ResourceAttrDiff)
for k, v := range c {
if _, ok := v.(string); !ok {
continue
}
diff.Attributes[k] = &ResourceAttrDiff{
Old: "",
New: v.(string),
}
}
return diff, nil
}
result := &MockResourceProvider{
Meta: n,
DiffReturn: diff,
DiffFn: diffFn,
ResourcesReturn: resources,
}
@ -184,3 +202,10 @@ func testTerraform(t *testing.T, name string) *Terraform {
return tf
}
const testTerraformDiffStr = `
aws_instance.bar
foo: "" => "${aws_instance.foo.num}"
aws_instance.foo
num: "" => "2"
`

View File

@ -1,5 +1,5 @@
resource "aws_instance" "foo" {
num = 2
num = "2"
}
resource "aws_instance" "bar" {