github.com/adamar/terraform@v0.2.2-0.20141016210445-2e703afdad0e/terraform/diff.go (about)

     1  package terraform
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"fmt"
     7  	"reflect"
     8  	"sort"
     9  	"strings"
    10  )
    11  
    12  // DiffChangeType is an enum with the kind of changes a diff has planned.
    13  type DiffChangeType byte
    14  
    15  const (
    16  	DiffInvalid DiffChangeType = iota
    17  	DiffNone
    18  	DiffCreate
    19  	DiffUpdate
    20  	DiffDestroy
    21  	DiffDestroyCreate
    22  )
    23  
    24  // Diff trackes the changes that are necessary to apply a configuration
    25  // to an existing infrastructure.
    26  type Diff struct {
    27  	// Modules contains all the modules that have a diff
    28  	Modules []*ModuleDiff
    29  }
    30  
    31  // AddModule adds the module with the given path to the diff.
    32  //
    33  // This should be the preferred method to add module diffs since it
    34  // allows us to optimize lookups later as well as control sorting.
    35  func (d *Diff) AddModule(path []string) *ModuleDiff {
    36  	m := &ModuleDiff{Path: path}
    37  	m.init()
    38  	d.Modules = append(d.Modules, m)
    39  	return m
    40  }
    41  
    42  // ModuleByPath is used to lookup the module diff for the given path.
    43  // This should be the prefered lookup mechanism as it allows for future
    44  // lookup optimizations.
    45  func (d *Diff) ModuleByPath(path []string) *ModuleDiff {
    46  	if d == nil {
    47  		return nil
    48  	}
    49  	for _, mod := range d.Modules {
    50  		if mod.Path == nil {
    51  			panic("missing module path")
    52  		}
    53  		if reflect.DeepEqual(mod.Path, path) {
    54  			return mod
    55  		}
    56  	}
    57  	return nil
    58  }
    59  
    60  // RootModule returns the ModuleState for the root module
    61  func (d *Diff) RootModule() *ModuleDiff {
    62  	root := d.ModuleByPath(rootModulePath)
    63  	if root == nil {
    64  		panic("missing root module")
    65  	}
    66  	return root
    67  }
    68  
    69  // Empty returns true if the diff has no changes.
    70  func (d *Diff) Empty() bool {
    71  	for _, m := range d.Modules {
    72  		if !m.Empty() {
    73  			return false
    74  		}
    75  	}
    76  
    77  	return true
    78  }
    79  
    80  func (d *Diff) String() string {
    81  	var buf bytes.Buffer
    82  	for _, m := range d.Modules {
    83  		mStr := m.String()
    84  
    85  		// If we're the root module, we just write the output directly.
    86  		if reflect.DeepEqual(m.Path, rootModulePath) {
    87  			buf.WriteString(mStr + "\n")
    88  			continue
    89  		}
    90  
    91  		buf.WriteString(fmt.Sprintf("module.%s:\n", strings.Join(m.Path[1:], ".")))
    92  
    93  		s := bufio.NewScanner(strings.NewReader(mStr))
    94  		for s.Scan() {
    95  			buf.WriteString(fmt.Sprintf("  %s\n", s.Text()))
    96  		}
    97  	}
    98  
    99  	return strings.TrimSpace(buf.String())
   100  }
   101  
   102  func (d *Diff) init() {
   103  	if d.Modules == nil {
   104  		rootDiff := &ModuleDiff{Path: rootModulePath}
   105  		d.Modules = []*ModuleDiff{rootDiff}
   106  	}
   107  	for _, m := range d.Modules {
   108  		m.init()
   109  	}
   110  }
   111  
   112  // ModuleDiff tracks the differences between resources to apply within
   113  // a single module.
   114  type ModuleDiff struct {
   115  	Path      []string
   116  	Resources map[string]*InstanceDiff
   117  }
   118  
   119  func (d *ModuleDiff) init() {
   120  	if d.Resources == nil {
   121  		d.Resources = make(map[string]*InstanceDiff)
   122  	}
   123  	for _, r := range d.Resources {
   124  		r.init()
   125  	}
   126  }
   127  
   128  // ChangeType returns the type of changes that the diff for this
   129  // module includes.
   130  //
   131  // At a module level, this will only be DiffNone, DiffUpdate, DiffDestroy, or
   132  // DiffCreate. If an instance within the module has a DiffDestroyCreate
   133  // then this will register as a DiffCreate for a module.
   134  func (d *ModuleDiff) ChangeType() DiffChangeType {
   135  	result := DiffNone
   136  	for _, r := range d.Resources {
   137  		change := r.ChangeType()
   138  		switch change {
   139  		case DiffCreate:
   140  			fallthrough
   141  		case DiffDestroy:
   142  			if result == DiffNone {
   143  				result = change
   144  			}
   145  		case DiffDestroyCreate:
   146  			fallthrough
   147  		case DiffUpdate:
   148  			result = DiffUpdate
   149  		}
   150  	}
   151  
   152  	return result
   153  }
   154  
   155  // Empty returns true if the diff has no changes within this module.
   156  func (d *ModuleDiff) Empty() bool {
   157  	if len(d.Resources) == 0 {
   158  		return true
   159  	}
   160  
   161  	for _, rd := range d.Resources {
   162  		if !rd.Empty() {
   163  			return false
   164  		}
   165  	}
   166  
   167  	return true
   168  }
   169  
   170  // Instances returns the instance diffs for the id given. This can return
   171  // multiple instance diffs if there are counts within the resource.
   172  func (d *ModuleDiff) Instances(id string) []*InstanceDiff {
   173  	var result []*InstanceDiff
   174  	for k, diff := range d.Resources {
   175  		if k == id || strings.HasPrefix(k, id+".") {
   176  			if !diff.Empty() {
   177  				result = append(result, diff)
   178  			}
   179  		}
   180  	}
   181  
   182  	return result
   183  }
   184  
   185  // IsRoot says whether or not this module diff is for the root module.
   186  func (d *ModuleDiff) IsRoot() bool {
   187  	return reflect.DeepEqual(d.Path, rootModulePath)
   188  }
   189  
   190  // String outputs the diff in a long but command-line friendly output
   191  // format that users can read to quickly inspect a diff.
   192  func (d *ModuleDiff) String() string {
   193  	var buf bytes.Buffer
   194  
   195  	names := make([]string, 0, len(d.Resources))
   196  	for name, _ := range d.Resources {
   197  		names = append(names, name)
   198  	}
   199  	sort.Strings(names)
   200  
   201  	for _, name := range names {
   202  		rdiff := d.Resources[name]
   203  
   204  		crud := "UPDATE"
   205  		if rdiff.RequiresNew() && (rdiff.Destroy || rdiff.DestroyTainted) {
   206  			crud = "DESTROY/CREATE"
   207  		} else if rdiff.Destroy {
   208  			crud = "DESTROY"
   209  		} else if rdiff.RequiresNew() {
   210  			crud = "CREATE"
   211  		}
   212  
   213  		buf.WriteString(fmt.Sprintf(
   214  			"%s: %s\n",
   215  			crud,
   216  			name))
   217  
   218  		keyLen := 0
   219  		keys := make([]string, 0, len(rdiff.Attributes))
   220  		for key, _ := range rdiff.Attributes {
   221  			if key == "id" {
   222  				continue
   223  			}
   224  
   225  			keys = append(keys, key)
   226  			if len(key) > keyLen {
   227  				keyLen = len(key)
   228  			}
   229  		}
   230  		sort.Strings(keys)
   231  
   232  		for _, attrK := range keys {
   233  			attrDiff := rdiff.Attributes[attrK]
   234  
   235  			v := attrDiff.New
   236  			if attrDiff.NewComputed {
   237  				v = "<computed>"
   238  			}
   239  
   240  			newResource := ""
   241  			if attrDiff.RequiresNew {
   242  				newResource = " (forces new resource)"
   243  			}
   244  
   245  			buf.WriteString(fmt.Sprintf(
   246  				"  %s:%s %#v => %#v%s\n",
   247  				attrK,
   248  				strings.Repeat(" ", keyLen-len(attrK)),
   249  				attrDiff.Old,
   250  				v,
   251  				newResource))
   252  		}
   253  	}
   254  
   255  	return buf.String()
   256  }
   257  
   258  // InstanceDiff is the diff of a resource from some state to another.
   259  type InstanceDiff struct {
   260  	Attributes     map[string]*ResourceAttrDiff
   261  	Destroy        bool
   262  	DestroyTainted bool
   263  }
   264  
   265  // ResourceAttrDiff is the diff of a single attribute of a resource.
   266  type ResourceAttrDiff struct {
   267  	Old         string      // Old Value
   268  	New         string      // New Value
   269  	NewComputed bool        // True if new value is computed (unknown currently)
   270  	NewRemoved  bool        // True if this attribute is being removed
   271  	NewExtra    interface{} // Extra information for the provider
   272  	RequiresNew bool        // True if change requires new resource
   273  	Type        DiffAttrType
   274  }
   275  
   276  func (d *ResourceAttrDiff) GoString() string {
   277  	return fmt.Sprintf("*%#v", *d)
   278  }
   279  
   280  // DiffAttrType is an enum type that says whether a resource attribute
   281  // diff is an input attribute (comes from the configuration) or an
   282  // output attribute (comes as a result of applying the configuration). An
   283  // example input would be "ami" for AWS and an example output would be
   284  // "private_ip".
   285  type DiffAttrType byte
   286  
   287  const (
   288  	DiffAttrUnknown DiffAttrType = iota
   289  	DiffAttrInput
   290  	DiffAttrOutput
   291  )
   292  
   293  func (d *InstanceDiff) init() {
   294  	if d.Attributes == nil {
   295  		d.Attributes = make(map[string]*ResourceAttrDiff)
   296  	}
   297  }
   298  
   299  // ChangeType returns the DiffChangeType represented by the diff
   300  // for this single instance.
   301  func (d *InstanceDiff) ChangeType() DiffChangeType {
   302  	if d.Empty() {
   303  		return DiffNone
   304  	}
   305  
   306  	if d.RequiresNew() && (d.Destroy || d.DestroyTainted) {
   307  		return DiffDestroyCreate
   308  	}
   309  
   310  	if d.Destroy {
   311  		return DiffDestroy
   312  	}
   313  
   314  	if d.RequiresNew() {
   315  		return DiffCreate
   316  	}
   317  
   318  	return DiffUpdate
   319  }
   320  
   321  // Empty returns true if this diff encapsulates no changes.
   322  func (d *InstanceDiff) Empty() bool {
   323  	if d == nil {
   324  		return true
   325  	}
   326  
   327  	return !d.Destroy && len(d.Attributes) == 0
   328  }
   329  
   330  // RequiresNew returns true if the diff requires the creation of a new
   331  // resource (implying the destruction of the old).
   332  func (d *InstanceDiff) RequiresNew() bool {
   333  	if d == nil {
   334  		return false
   335  	}
   336  
   337  	for _, rd := range d.Attributes {
   338  		if rd != nil && rd.RequiresNew {
   339  			return true
   340  		}
   341  	}
   342  
   343  	return false
   344  }
   345  
   346  // Same checks whether or not to InstanceDiff are the "same." When
   347  // we say "same", it is not necessarily exactly equal. Instead, it is
   348  // just checking that the same attributes are changing, a destroy
   349  // isn't suddenly happening, etc.
   350  func (d *InstanceDiff) Same(d2 *InstanceDiff) bool {
   351  	if d == nil && d2 == nil {
   352  		return true
   353  	} else if d == nil || d2 == nil {
   354  		return false
   355  	}
   356  
   357  	if d.Destroy != d2.Destroy {
   358  		return false
   359  	}
   360  	if d.RequiresNew() != d2.RequiresNew() {
   361  		return false
   362  	}
   363  
   364  	// Go through the old diff and make sure the new diff has all the
   365  	// same attributes. To start, build up the check map to be all the keys.
   366  	checkOld := make(map[string]struct{})
   367  	checkNew := make(map[string]struct{})
   368  	for k, _ := range d.Attributes {
   369  		checkOld[k] = struct{}{}
   370  	}
   371  	for k, _ := range d2.Attributes {
   372  		checkNew[k] = struct{}{}
   373  	}
   374  	for k, diffOld := range d.Attributes {
   375  		if _, ok := checkOld[k]; !ok {
   376  			// We're not checking this key for whatever reason (see where
   377  			// check is modified).
   378  			continue
   379  		}
   380  
   381  		// Remove this key since we'll never hit it again
   382  		delete(checkOld, k)
   383  		delete(checkNew, k)
   384  
   385  		_, ok := d2.Attributes[k]
   386  		if !ok {
   387  			// The matching attribute was not found, we're different
   388  			return false
   389  		}
   390  
   391  		if diffOld.NewComputed && strings.HasSuffix(k, ".#") {
   392  			// This is a computed list, so remove any keys with this
   393  			// prefix from the check list.
   394  			kprefix := k[0:len(k)-2] + "."
   395  			for k2, _ := range checkOld {
   396  				if strings.HasPrefix(k2, kprefix) {
   397  					delete(checkOld, k2)
   398  				}
   399  			}
   400  			for k2, _ := range checkNew {
   401  				if strings.HasPrefix(k2, kprefix) {
   402  					delete(checkNew, k2)
   403  				}
   404  			}
   405  		}
   406  
   407  		// TODO: check for the same value if not computed
   408  	}
   409  
   410  	// Check for leftover attributes
   411  	if len(checkNew) > 0 {
   412  		return false
   413  	}
   414  
   415  	return true
   416  }