diff: beginning work on higher level diff builder

This commit is contained in:
Mitchell Hashimoto 2014-06-17 18:40:32 -07:00
parent f032ce6c1b
commit 602b7df97d
3 changed files with 266 additions and 0 deletions

86
diff/builder.go Normal file
View File

@ -0,0 +1,86 @@
package diff
import (
"github.com/hashicorp/terraform/terraform"
)
type Builder struct {
Resources map[string]*ResourceBuilder
}
type ResourceBuilder struct {
CreateComputedAttrs []string
RequiresNewAttrs []string
}
type ResourceBuilderFactory func() *ResourceBuilder
func (b *ResourceBuilder) Diff(
s *terraform.ResourceState,
c *terraform.ResourceConfig) (*terraform.ResourceDiff, error) {
attrs := make(map[string]*terraform.ResourceAttrDiff)
requiresNewSet := make(map[string]struct{})
for _, k := range b.RequiresNewAttrs {
requiresNewSet[k] = struct{}{}
}
// We require a new resource if the ID is empty. Or, later, we set
// this to true if any configuration changed that triggers a new resource.
requiresNew := s.ID == ""
// Go through the configuration and find the changed attributes
for k, v := range c.Raw {
newV := v.(string)
var oldV string
var ok bool
if oldV, ok = s.Attributes[k]; ok {
// Old value exists! We check to see if there is a change
if oldV == newV {
continue
}
}
// There has been a change. Record it
attrs[k] = &terraform.ResourceAttrDiff{
Old: oldV,
New: newV,
}
// If this requires a new resource, record that and flag our
// boolean.
if _, ok := requiresNewSet[k]; ok {
attrs[k].RequiresNew = true
requiresNew = true
}
}
// If we require a new resource, then process all the attributes
// that will be changing due to the creation of the resource.
if requiresNew {
attrs["id"] = &terraform.ResourceAttrDiff{
Old: s.ID,
NewComputed: true,
RequiresNew: true,
}
for _, k := range b.CreateComputedAttrs {
old := s.Attributes[k]
attrs[k] = &terraform.ResourceAttrDiff{
Old: old,
NewComputed: true,
}
}
}
// Build our resulting diff if we had attributes change
var result *terraform.ResourceDiff
if len(attrs) > 0 {
result = &terraform.ResourceDiff{
Attributes: attrs,
}
}
return result, nil
}

103
diff/builder_test.go Normal file
View File

@ -0,0 +1,103 @@
package diff
import (
"testing"
"github.com/hashicorp/terraform/terraform"
)
func TestResourceBuilder_new(t *testing.T) {
rb := &ResourceBuilder{
CreateComputedAttrs: []string{"private_ip"},
}
state := &terraform.ResourceState{}
c := testConfig(t, map[string]interface{}{
"foo": "bar",
}, nil)
diff, err := rb.Diff(state, c)
if err != nil {
t.Fatalf("err: %s", err)
}
if diff == nil {
t.Fatal("should not be nil")
}
actual := testResourceDiffStr(diff)
expected := testRBNewDiff
if actual != expected {
t.Fatalf("bad: %s", actual)
}
}
func TestResourceBuilder_requiresNew(t *testing.T) {
rb := &ResourceBuilder{
CreateComputedAttrs: []string{"private_ip"},
RequiresNewAttrs: []string{"ami"},
}
state := &terraform.ResourceState{
ID: "1",
Attributes: map[string]string{
"ami": "foo",
"private_ip": "127.0.0.1",
},
}
c := testConfig(t, map[string]interface{}{
"ami": "bar",
}, nil)
diff, err := rb.Diff(state, c)
if err != nil {
t.Fatalf("err: %s", err)
}
if diff == nil {
t.Fatal("should not be nil")
}
actual := testResourceDiffStr(diff)
expected := testRBRequiresNewDiff
if actual != expected {
t.Fatalf("bad: %s", actual)
}
}
func TestResourceBuilder_same(t *testing.T) {
rb := &ResourceBuilder{
CreateComputedAttrs: []string{"private_ip"},
}
state := &terraform.ResourceState{
ID: "1",
Attributes: map[string]string{
"foo": "bar",
},
}
c := testConfig(t, map[string]interface{}{
"foo": "bar",
}, nil)
diff, err := rb.Diff(state, c)
if err != nil {
t.Fatalf("err: %s", err)
}
if diff != nil {
t.Fatal("should not diff: %s", diff)
}
}
const testRBNewDiff = `CREATE
foo: "" => "bar"
id: "" => "<computed>" (forces new resource)
private_ip: "" => "<computed>"
`
const testRBRequiresNewDiff = `CREATE
ami: "foo" => "bar" (forces new resource)
id: "1" => "<computed>" (forces new resource)
private_ip: "127.0.0.1" => "<computed>"
`

77
diff/diff_test.go Normal file
View File

@ -0,0 +1,77 @@
package diff
import (
"bytes"
"fmt"
"sort"
"strings"
"testing"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/terraform"
)
func testConfig(
t *testing.T,
c map[string]interface{},
vs map[string]string) *terraform.ResourceConfig {
rc, err := config.NewRawConfig(c)
if err != nil {
t.Fatalf("err: %s", err)
}
if len(vs) > 0 {
if err := rc.Interpolate(vs); err != nil {
t.Fatalf("err: %s", err)
}
}
return terraform.NewResourceConfig(rc)
}
func testResourceDiffStr(rd *terraform.ResourceDiff) string {
var buf bytes.Buffer
crud := "UPDATE"
if rd.RequiresNew() {
crud = "CREATE"
}
buf.WriteString(fmt.Sprintf(
"%s\n",
crud))
keyLen := 0
keys := make([]string, 0, len(rd.Attributes))
for key, _ := range rd.Attributes {
keys = append(keys, key)
if len(key) > keyLen {
keyLen = len(key)
}
}
sort.Strings(keys)
for _, attrK := range keys {
attrDiff := rd.Attributes[attrK]
v := attrDiff.New
if attrDiff.NewComputed {
v = "<computed>"
}
newResource := ""
if attrDiff.RequiresNew {
newResource = " (forces new resource)"
}
buf.WriteString(fmt.Sprintf(
" %s:%s %#v => %#v%s\n",
attrK,
strings.Repeat(" ", keyLen-len(attrK)),
attrDiff.Old,
v,
newResource))
}
return buf.String()
}