helper/diff: work with complex data types
This commit is contained in:
parent
d2d6ef64aa
commit
37995e7ff8
|
@ -1,14 +1,43 @@
|
||||||
package diff
|
package diff
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/flatmap"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// AttrType is an enum that tells the ResourceBuilder what type of attribute
|
||||||
|
// an attribute is, affecting the overall diff output.
|
||||||
|
//
|
||||||
|
// The valid values are:
|
||||||
|
//
|
||||||
|
// * AttrTypeCreate - This attribute can only be set or updated on create.
|
||||||
|
// This means that if this attribute is changed, it will require a new
|
||||||
|
// resource to be created if it is already created.
|
||||||
|
//
|
||||||
|
// * AttrTypeUpdate - This attribute can be set at create time or updated
|
||||||
|
// in-place. Changing this attribute does not require a new resource.
|
||||||
|
//
|
||||||
|
type AttrType byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
AttrTypeUnknown AttrType = iota
|
||||||
|
AttrTypeCreate
|
||||||
|
AttrTypeUpdate
|
||||||
|
)
|
||||||
|
|
||||||
// ResourceBuilder is a helper that knows about how a single resource
|
// ResourceBuilder is a helper that knows about how a single resource
|
||||||
// changes and how those changes affect the diff.
|
// changes and how those changes affect the diff.
|
||||||
type ResourceBuilder struct {
|
type ResourceBuilder struct {
|
||||||
CreateComputedAttrs []string
|
// Attrs are the mapping of attributes that can be set from the
|
||||||
RequiresNewAttrs []string
|
// configuration, and the affect they have. See the documentation for
|
||||||
|
// AttrType for more info.
|
||||||
|
Attrs map[string]AttrType
|
||||||
|
|
||||||
|
// ComputedAttrs are the attributes that are computed at
|
||||||
|
// resource creation time.
|
||||||
|
ComputedAttrs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Diff returns the ResourceDiff for a resource given its state and
|
// Diff returns the ResourceDiff for a resource given its state and
|
||||||
|
@ -18,45 +47,52 @@ func (b *ResourceBuilder) Diff(
|
||||||
c *terraform.ResourceConfig) (*terraform.ResourceDiff, error) {
|
c *terraform.ResourceConfig) (*terraform.ResourceDiff, error) {
|
||||||
attrs := make(map[string]*terraform.ResourceAttrDiff)
|
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
|
// 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.
|
// this to true if any configuration changed that triggers a new resource.
|
||||||
requiresNew := s.ID == ""
|
requiresNew := s.ID == ""
|
||||||
|
|
||||||
// Go through the configuration and find the changed attributes
|
// Flatten the raw and processed configuration
|
||||||
for k, v := range c.Raw {
|
flatRaw := flatmap.Flatten(c.Raw)
|
||||||
newV := v.(string)
|
flatConfig := flatmap.Flatten(c.Config)
|
||||||
|
|
||||||
|
for k, v := range flatRaw {
|
||||||
|
// Make sure this is an attribute that actually affects
|
||||||
|
// the diff in some way.
|
||||||
|
var attr AttrType
|
||||||
|
for ak, at := range b.Attrs {
|
||||||
|
if strings.HasPrefix(k, ak) {
|
||||||
|
attr = at
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if attr == AttrTypeUnknown {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// If this key is in the cleaned config, then use that value
|
// If this key is in the cleaned config, then use that value
|
||||||
// because it'll have its variables properly interpolated
|
// because it'll have its variables properly interpolated
|
||||||
if cleanV, ok := c.Config[k]; ok {
|
if cleanV, ok := flatConfig[k]; ok {
|
||||||
newV = cleanV.(string)
|
v = cleanV
|
||||||
}
|
}
|
||||||
|
|
||||||
var oldV string
|
oldV, ok := s.Attributes[k]
|
||||||
var ok bool
|
|
||||||
if oldV, ok = s.Attributes[k]; ok {
|
// If there is an old value and they're the same, no change
|
||||||
// Old value exists! We check to see if there is a change
|
if ok && oldV == v {
|
||||||
if oldV == newV {
|
continue
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// There has been a change. Record it
|
// Record the change
|
||||||
attrs[k] = &terraform.ResourceAttrDiff{
|
attrs[k] = &terraform.ResourceAttrDiff{
|
||||||
Old: oldV,
|
Old: oldV,
|
||||||
New: newV,
|
New: v,
|
||||||
|
Type: terraform.DiffAttrInput,
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this requires a new resource, record that and flag our
|
// If this requires a new resource, record that and flag our
|
||||||
// boolean.
|
// boolean.
|
||||||
if _, ok := requiresNewSet[k]; ok {
|
if attr == AttrTypeCreate {
|
||||||
attrs[k].RequiresNew = true
|
attrs[k].RequiresNew = true
|
||||||
attrs[k].Type = terraform.DiffAttrInput
|
|
||||||
requiresNew = true
|
requiresNew = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,7 +100,7 @@ func (b *ResourceBuilder) Diff(
|
||||||
// If we require a new resource, then process all the attributes
|
// If we require a new resource, then process all the attributes
|
||||||
// that will be changing due to the creation of the resource.
|
// that will be changing due to the creation of the resource.
|
||||||
if requiresNew {
|
if requiresNew {
|
||||||
for _, k := range b.CreateComputedAttrs {
|
for _, k := range b.ComputedAttrs {
|
||||||
old := s.Attributes[k]
|
old := s.Attributes[k]
|
||||||
attrs[k] = &terraform.ResourceAttrDiff{
|
attrs[k] = &terraform.ResourceAttrDiff{
|
||||||
Old: old,
|
Old: old,
|
||||||
|
|
|
@ -7,9 +7,51 @@ import (
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestResourceBuilder_complex(t *testing.T) {
|
||||||
|
rb := &ResourceBuilder{
|
||||||
|
Attrs: map[string]AttrType{
|
||||||
|
"listener": AttrTypeUpdate,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
state := &terraform.ResourceState{
|
||||||
|
ID: "foo",
|
||||||
|
Attributes: map[string]string{
|
||||||
|
"ignore": "1",
|
||||||
|
"listener.#": "1",
|
||||||
|
"listener.0.port": "80",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
c := testConfig(t, map[string]interface{}{
|
||||||
|
"listener": []interface{}{
|
||||||
|
map[interface{}]interface{}{
|
||||||
|
"port": 3000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, 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 := testRBComplexDiff
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("bad: %s", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestResourceBuilder_new(t *testing.T) {
|
func TestResourceBuilder_new(t *testing.T) {
|
||||||
rb := &ResourceBuilder{
|
rb := &ResourceBuilder{
|
||||||
CreateComputedAttrs: []string{"private_ip"},
|
Attrs: map[string]AttrType{
|
||||||
|
"foo": AttrTypeUpdate,
|
||||||
|
},
|
||||||
|
ComputedAttrs: []string{"private_ip"},
|
||||||
}
|
}
|
||||||
|
|
||||||
state := &terraform.ResourceState{}
|
state := &terraform.ResourceState{}
|
||||||
|
@ -35,8 +77,10 @@ func TestResourceBuilder_new(t *testing.T) {
|
||||||
|
|
||||||
func TestResourceBuilder_requiresNew(t *testing.T) {
|
func TestResourceBuilder_requiresNew(t *testing.T) {
|
||||||
rb := &ResourceBuilder{
|
rb := &ResourceBuilder{
|
||||||
CreateComputedAttrs: []string{"private_ip"},
|
ComputedAttrs: []string{"private_ip"},
|
||||||
RequiresNewAttrs: []string{"ami"},
|
Attrs: map[string]AttrType{
|
||||||
|
"ami": AttrTypeCreate,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
state := &terraform.ResourceState{
|
state := &terraform.ResourceState{
|
||||||
|
@ -68,7 +112,7 @@ func TestResourceBuilder_requiresNew(t *testing.T) {
|
||||||
|
|
||||||
func TestResourceBuilder_same(t *testing.T) {
|
func TestResourceBuilder_same(t *testing.T) {
|
||||||
rb := &ResourceBuilder{
|
rb := &ResourceBuilder{
|
||||||
CreateComputedAttrs: []string{"private_ip"},
|
ComputedAttrs: []string{"private_ip"},
|
||||||
}
|
}
|
||||||
|
|
||||||
state := &terraform.ResourceState{
|
state := &terraform.ResourceState{
|
||||||
|
@ -92,7 +136,11 @@ func TestResourceBuilder_same(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResourceBuilder_unknown(t *testing.T) {
|
func TestResourceBuilder_unknown(t *testing.T) {
|
||||||
rb := &ResourceBuilder{}
|
rb := &ResourceBuilder{
|
||||||
|
Attrs: map[string]AttrType{
|
||||||
|
"foo": AttrTypeUpdate,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
state := &terraform.ResourceState{}
|
state := &terraform.ResourceState{}
|
||||||
|
|
||||||
|
@ -119,7 +167,11 @@ func TestResourceBuilder_unknown(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResourceBuilder_vars(t *testing.T) {
|
func TestResourceBuilder_vars(t *testing.T) {
|
||||||
rb := &ResourceBuilder{}
|
rb := &ResourceBuilder{
|
||||||
|
Attrs: map[string]AttrType{
|
||||||
|
"foo": AttrTypeUpdate,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
state := &terraform.ResourceState{}
|
state := &terraform.ResourceState{}
|
||||||
|
|
||||||
|
@ -144,6 +196,10 @@ func TestResourceBuilder_vars(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const testRBComplexDiff = `UPDATE
|
||||||
|
IN listener.0.port: "80" => "3000"
|
||||||
|
`
|
||||||
|
|
||||||
const testRBNewDiff = `UPDATE
|
const testRBNewDiff = `UPDATE
|
||||||
IN foo: "" => "bar"
|
IN foo: "" => "bar"
|
||||||
OUT private_ip: "" => "<computed>"
|
OUT private_ip: "" => "<computed>"
|
||||||
|
|
Loading…
Reference in New Issue