diff: beginning work on higher level diff builder
This commit is contained in:
parent
f032ce6c1b
commit
602b7df97d
|
@ -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
|
||||||
|
}
|
|
@ -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>"
|
||||||
|
`
|
|
@ -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()
|
||||||
|
}
|
Loading…
Reference in New Issue