terraform/terraform/diff.go

300 lines
6.4 KiB
Go
Raw Normal View History

package terraform
2014-06-05 11:32:10 +02:00
import (
"bufio"
2014-06-10 20:22:32 +02:00
"bytes"
"fmt"
"reflect"
2014-06-10 20:22:32 +02:00
"sort"
"strings"
2014-06-05 11:32:10 +02:00
)
// Diff trackes the changes that are necessary to apply a configuration
// to an existing infrastructure.
type Diff struct {
// Modules contains all the modules that have a diff
Modules []*ModuleDiff
2014-06-05 11:32:10 +02:00
}
2014-09-23 23:15:40 +02:00
// AddModule adds the module with the given path to the diff.
//
// This should be the preferred method to add module diffs since it
// allows us to optimize lookups later as well as control sorting.
func (d *Diff) AddModule(path []string) *ModuleDiff {
m := &ModuleDiff{Path: path}
m.init()
d.Modules = append(d.Modules, m)
return m
}
// ModuleByPath is used to lookup the module diff for the given path.
// This should be the prefered lookup mechanism as it allows for future
// lookup optimizations.
func (d *Diff) ModuleByPath(path []string) *ModuleDiff {
if d == nil {
return nil
}
for _, mod := range d.Modules {
if mod.Path == nil {
panic("missing module path")
}
if reflect.DeepEqual(mod.Path, path) {
return mod
}
}
return nil
}
// RootModule returns the ModuleState for the root module
func (d *Diff) RootModule() *ModuleDiff {
root := d.ModuleByPath(rootModulePath)
if root == nil {
panic("missing root module")
}
return root
}
// Empty returns true if the diff has no changes.
func (d *Diff) Empty() bool {
for _, m := range d.Modules {
if !m.Empty() {
return false
}
}
return true
}
func (d *Diff) String() string {
var buf bytes.Buffer
for _, m := range d.Modules {
mStr := m.String()
// If we're the root module, we just write the output directly.
if reflect.DeepEqual(m.Path, rootModulePath) {
buf.WriteString(mStr + "\n")
continue
}
buf.WriteString(fmt.Sprintf("module.%s:\n", strings.Join(m.Path[1:], ".")))
s := bufio.NewScanner(strings.NewReader(mStr))
for s.Scan() {
buf.WriteString(fmt.Sprintf(" %s\n", s.Text()))
2014-06-10 20:22:32 +02:00
}
}
return strings.TrimSpace(buf.String())
}
func (d *Diff) init() {
if d.Modules == nil {
rootDiff := &ModuleDiff{Path: rootModulePath}
d.Modules = []*ModuleDiff{rootDiff}
}
for _, m := range d.Modules {
m.init()
}
}
// ModuleDiff tracks the differences between resources to apply within
// a single module.
type ModuleDiff struct {
Path []string
Resources map[string]*InstanceDiff
}
func (d *ModuleDiff) init() {
if d.Resources == nil {
d.Resources = make(map[string]*InstanceDiff)
}
for _, r := range d.Resources {
r.init()
}
}
// Empty returns true if the diff has no changes within this module.
func (d *ModuleDiff) Empty() bool {
2014-06-19 23:57:36 +02:00
if len(d.Resources) == 0 {
return true
}
for _, rd := range d.Resources {
if !rd.Empty() {
2014-06-19 23:57:36 +02:00
return false
}
}
return true
}
2014-06-10 20:22:32 +02:00
// String outputs the diff in a long but command-line friendly output
// format that users can read to quickly inspect a diff.
func (d *ModuleDiff) String() string {
2014-06-10 20:22:32 +02:00
var buf bytes.Buffer
names := make([]string, 0, len(d.Resources))
for name, _ := range d.Resources {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
rdiff := d.Resources[name]
crud := "UPDATE"
2014-09-20 06:47:53 +02:00
if rdiff.RequiresNew() && (rdiff.Destroy || rdiff.DestroyTainted) {
2014-06-26 06:58:33 +02:00
crud = "DESTROY/CREATE"
} else if rdiff.Destroy {
crud = "DESTROY"
} else if rdiff.RequiresNew() {
crud = "CREATE"
}
buf.WriteString(fmt.Sprintf(
"%s: %s\n",
crud,
name))
2014-06-10 20:22:32 +02:00
keyLen := 0
keys := make([]string, 0, len(rdiff.Attributes))
for key, _ := range rdiff.Attributes {
2014-07-09 01:58:31 +02:00
if key == "id" {
continue
}
keys = append(keys, key)
if len(key) > keyLen {
keyLen = len(key)
}
}
sort.Strings(keys)
for _, attrK := range keys {
attrDiff := rdiff.Attributes[attrK]
2014-06-10 20:22:32 +02:00
v := attrDiff.New
if attrDiff.NewComputed {
v = "<computed>"
}
newResource := ""
if attrDiff.RequiresNew {
newResource = " (forces new resource)"
}
2014-06-10 20:22:32 +02:00
buf.WriteString(fmt.Sprintf(
" %s:%s %#v => %#v%s\n",
2014-06-10 20:22:32 +02:00
attrK,
strings.Repeat(" ", keyLen-len(attrK)),
2014-06-10 20:22:32 +02:00
attrDiff.Old,
v,
newResource))
2014-06-10 20:22:32 +02:00
}
}
return buf.String()
}
2014-09-18 01:33:24 +02:00
// InstanceDiff is the diff of a resource from some state to another.
type InstanceDiff struct {
2014-09-20 06:47:53 +02:00
Attributes map[string]*ResourceAttrDiff
Destroy bool
DestroyTainted bool
}
// 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)
NewRemoved bool // True if this attribute is being removed
NewExtra interface{} // Extra information for the provider
RequiresNew bool // True if change requires new resource
Type DiffAttrType
}
func (d *ResourceAttrDiff) GoString() string {
return fmt.Sprintf("*%#v", *d)
}
// DiffAttrType is an enum type that says whether a resource attribute
// diff is an input attribute (comes from the configuration) or an
// output attribute (comes as a result of applying the configuration). An
// example input would be "ami" for AWS and an example output would be
// "private_ip".
type DiffAttrType byte
const (
DiffAttrUnknown DiffAttrType = iota
DiffAttrInput
DiffAttrOutput
)
2014-09-18 01:33:24 +02:00
func (d *InstanceDiff) init() {
if d.Attributes == nil {
d.Attributes = make(map[string]*ResourceAttrDiff)
}
2014-07-09 01:58:31 +02:00
}
2014-06-20 20:03:33 +02:00
// Empty returns true if this diff encapsulates no changes.
2014-09-18 01:33:24 +02:00
func (d *InstanceDiff) Empty() bool {
2014-06-20 20:03:33 +02:00
if d == nil {
return true
}
2014-06-26 06:58:33 +02:00
return !d.Destroy && len(d.Attributes) == 0
2014-06-20 20:03:33 +02:00
}
// RequiresNew returns true if the diff requires the creation of a new
// resource (implying the destruction of the old).
2014-09-18 01:33:24 +02:00
func (d *InstanceDiff) RequiresNew() bool {
2014-06-18 03:10:38 +02:00
if d == nil {
return false
}
for _, rd := range d.Attributes {
2014-07-23 04:32:46 +02:00
if rd != nil && rd.RequiresNew {
return true
}
}
return false
}
2014-07-23 04:32:46 +02:00
2014-09-18 01:33:24 +02:00
// Same checks whether or not to InstanceDiff are the "same." When
2014-07-23 04:32:46 +02:00
// we say "same", it is not necessarily exactly equal. Instead, it is
// just checking that the same attributes are changing, a destroy
// isn't suddenly happening, etc.
2014-09-18 01:33:24 +02:00
func (d *InstanceDiff) Same(d2 *InstanceDiff) bool {
if d == nil && d2 == nil {
return true
} else if d == nil || d2 == nil {
return false
}
2014-07-23 04:32:46 +02:00
if d.Destroy != d2.Destroy {
return false
}
if d.RequiresNew() != d2.RequiresNew() {
return false
}
if len(d.Attributes) != len(d2.Attributes) {
return false
}
ks := make(map[string]struct{})
for k, _ := range d.Attributes {
ks[k] = struct{}{}
}
for k, _ := range d2.Attributes {
delete(ks, k)
}
if len(ks) > 0 {
return false
}
return true
}