github.com/renier/terraform@v0.7.8-0.20161024133817-eb8a9ef5471a/terraform/diff.go (about)

     1  package terraform
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"fmt"
     7  	"reflect"
     8  	"regexp"
     9  	"sort"
    10  	"strings"
    11  	"sync"
    12  
    13  	"github.com/mitchellh/copystructure"
    14  )
    15  
    16  // DiffChangeType is an enum with the kind of changes a diff has planned.
    17  type DiffChangeType byte
    18  
    19  const (
    20  	DiffInvalid DiffChangeType = iota
    21  	DiffNone
    22  	DiffCreate
    23  	DiffUpdate
    24  	DiffDestroy
    25  	DiffDestroyCreate
    26  )
    27  
    28  // Diff trackes the changes that are necessary to apply a configuration
    29  // to an existing infrastructure.
    30  type Diff struct {
    31  	// Modules contains all the modules that have a diff
    32  	Modules []*ModuleDiff
    33  }
    34  
    35  // AddModule adds the module with the given path to the diff.
    36  //
    37  // This should be the preferred method to add module diffs since it
    38  // allows us to optimize lookups later as well as control sorting.
    39  func (d *Diff) AddModule(path []string) *ModuleDiff {
    40  	m := &ModuleDiff{Path: path}
    41  	m.init()
    42  	d.Modules = append(d.Modules, m)
    43  	return m
    44  }
    45  
    46  // ModuleByPath is used to lookup the module diff for the given path.
    47  // This should be the preferred lookup mechanism as it allows for future
    48  // lookup optimizations.
    49  func (d *Diff) ModuleByPath(path []string) *ModuleDiff {
    50  	if d == nil {
    51  		return nil
    52  	}
    53  	for _, mod := range d.Modules {
    54  		if mod.Path == nil {
    55  			panic("missing module path")
    56  		}
    57  		if reflect.DeepEqual(mod.Path, path) {
    58  			return mod
    59  		}
    60  	}
    61  	return nil
    62  }
    63  
    64  // RootModule returns the ModuleState for the root module
    65  func (d *Diff) RootModule() *ModuleDiff {
    66  	root := d.ModuleByPath(rootModulePath)
    67  	if root == nil {
    68  		panic("missing root module")
    69  	}
    70  	return root
    71  }
    72  
    73  // Empty returns true if the diff has no changes.
    74  func (d *Diff) Empty() bool {
    75  	if d == nil {
    76  		return true
    77  	}
    78  
    79  	for _, m := range d.Modules {
    80  		if !m.Empty() {
    81  			return false
    82  		}
    83  	}
    84  
    85  	return true
    86  }
    87  
    88  // Equal compares two diffs for exact equality.
    89  //
    90  // This is different from the Same comparison that is supported which
    91  // checks for operation equality taking into account computed values. Equal
    92  // instead checks for exact equality.
    93  func (d *Diff) Equal(d2 *Diff) bool {
    94  	// If one is nil, they must both be nil
    95  	if d == nil || d2 == nil {
    96  		return d == d2
    97  	}
    98  
    99  	// Sort the modules
   100  	sort.Sort(moduleDiffSort(d.Modules))
   101  	sort.Sort(moduleDiffSort(d2.Modules))
   102  
   103  	// Use DeepEqual
   104  	return reflect.DeepEqual(d, d2)
   105  }
   106  
   107  // DeepCopy performs a deep copy of all parts of the Diff, making the
   108  // resulting Diff safe to use without modifying this one.
   109  func (d *Diff) DeepCopy() *Diff {
   110  	copy, err := copystructure.Config{Lock: true}.Copy(d)
   111  	if err != nil {
   112  		panic(err)
   113  	}
   114  
   115  	return copy.(*Diff)
   116  }
   117  
   118  func (d *Diff) String() string {
   119  	var buf bytes.Buffer
   120  
   121  	keys := make([]string, 0, len(d.Modules))
   122  	lookup := make(map[string]*ModuleDiff)
   123  	for _, m := range d.Modules {
   124  		key := fmt.Sprintf("module.%s", strings.Join(m.Path[1:], "."))
   125  		keys = append(keys, key)
   126  		lookup[key] = m
   127  	}
   128  	sort.Strings(keys)
   129  
   130  	for _, key := range keys {
   131  		m := lookup[key]
   132  		mStr := m.String()
   133  
   134  		// If we're the root module, we just write the output directly.
   135  		if reflect.DeepEqual(m.Path, rootModulePath) {
   136  			buf.WriteString(mStr + "\n")
   137  			continue
   138  		}
   139  
   140  		buf.WriteString(fmt.Sprintf("%s:\n", key))
   141  
   142  		s := bufio.NewScanner(strings.NewReader(mStr))
   143  		for s.Scan() {
   144  			buf.WriteString(fmt.Sprintf("  %s\n", s.Text()))
   145  		}
   146  	}
   147  
   148  	return strings.TrimSpace(buf.String())
   149  }
   150  
   151  func (d *Diff) init() {
   152  	if d.Modules == nil {
   153  		rootDiff := &ModuleDiff{Path: rootModulePath}
   154  		d.Modules = []*ModuleDiff{rootDiff}
   155  	}
   156  	for _, m := range d.Modules {
   157  		m.init()
   158  	}
   159  }
   160  
   161  // ModuleDiff tracks the differences between resources to apply within
   162  // a single module.
   163  type ModuleDiff struct {
   164  	Path      []string
   165  	Resources map[string]*InstanceDiff
   166  	Destroy   bool // Set only by the destroy plan
   167  }
   168  
   169  func (d *ModuleDiff) init() {
   170  	if d.Resources == nil {
   171  		d.Resources = make(map[string]*InstanceDiff)
   172  	}
   173  	for _, r := range d.Resources {
   174  		r.init()
   175  	}
   176  }
   177  
   178  // ChangeType returns the type of changes that the diff for this
   179  // module includes.
   180  //
   181  // At a module level, this will only be DiffNone, DiffUpdate, DiffDestroy, or
   182  // DiffCreate. If an instance within the module has a DiffDestroyCreate
   183  // then this will register as a DiffCreate for a module.
   184  func (d *ModuleDiff) ChangeType() DiffChangeType {
   185  	result := DiffNone
   186  	for _, r := range d.Resources {
   187  		change := r.ChangeType()
   188  		switch change {
   189  		case DiffCreate, DiffDestroy:
   190  			if result == DiffNone {
   191  				result = change
   192  			}
   193  		case DiffDestroyCreate, DiffUpdate:
   194  			result = DiffUpdate
   195  		}
   196  	}
   197  
   198  	return result
   199  }
   200  
   201  // Empty returns true if the diff has no changes within this module.
   202  func (d *ModuleDiff) Empty() bool {
   203  	if len(d.Resources) == 0 {
   204  		return true
   205  	}
   206  
   207  	for _, rd := range d.Resources {
   208  		if !rd.Empty() {
   209  			return false
   210  		}
   211  	}
   212  
   213  	return true
   214  }
   215  
   216  // Instances returns the instance diffs for the id given. This can return
   217  // multiple instance diffs if there are counts within the resource.
   218  func (d *ModuleDiff) Instances(id string) []*InstanceDiff {
   219  	var result []*InstanceDiff
   220  	for k, diff := range d.Resources {
   221  		if k == id || strings.HasPrefix(k, id+".") {
   222  			if !diff.Empty() {
   223  				result = append(result, diff)
   224  			}
   225  		}
   226  	}
   227  
   228  	return result
   229  }
   230  
   231  // IsRoot says whether or not this module diff is for the root module.
   232  func (d *ModuleDiff) IsRoot() bool {
   233  	return reflect.DeepEqual(d.Path, rootModulePath)
   234  }
   235  
   236  // String outputs the diff in a long but command-line friendly output
   237  // format that users can read to quickly inspect a diff.
   238  func (d *ModuleDiff) String() string {
   239  	var buf bytes.Buffer
   240  
   241  	if d.Destroy {
   242  		buf.WriteString("DESTROY MODULE\n")
   243  	}
   244  
   245  	names := make([]string, 0, len(d.Resources))
   246  	for name, _ := range d.Resources {
   247  		names = append(names, name)
   248  	}
   249  	sort.Strings(names)
   250  
   251  	for _, name := range names {
   252  		rdiff := d.Resources[name]
   253  
   254  		crud := "UPDATE"
   255  		switch {
   256  		case rdiff.RequiresNew() && (rdiff.GetDestroy() || rdiff.GetDestroyTainted()):
   257  			crud = "DESTROY/CREATE"
   258  		case rdiff.GetDestroy():
   259  			crud = "DESTROY"
   260  		case rdiff.RequiresNew():
   261  			crud = "CREATE"
   262  		}
   263  
   264  		buf.WriteString(fmt.Sprintf(
   265  			"%s: %s\n",
   266  			crud,
   267  			name))
   268  
   269  		keyLen := 0
   270  		rdiffAttrs := rdiff.CopyAttributes()
   271  		keys := make([]string, 0, len(rdiffAttrs))
   272  		for key, _ := range rdiffAttrs {
   273  			if key == "id" {
   274  				continue
   275  			}
   276  
   277  			keys = append(keys, key)
   278  			if len(key) > keyLen {
   279  				keyLen = len(key)
   280  			}
   281  		}
   282  		sort.Strings(keys)
   283  
   284  		for _, attrK := range keys {
   285  			attrDiff, _ := rdiff.GetAttribute(attrK)
   286  
   287  			v := attrDiff.New
   288  			u := attrDiff.Old
   289  			if attrDiff.NewComputed {
   290  				v = "<computed>"
   291  			}
   292  
   293  			if attrDiff.Sensitive {
   294  				u = "<sensitive>"
   295  				v = "<sensitive>"
   296  			}
   297  
   298  			updateMsg := ""
   299  			if attrDiff.RequiresNew {
   300  				updateMsg = " (forces new resource)"
   301  			} else if attrDiff.Sensitive {
   302  				updateMsg = " (attribute changed)"
   303  			}
   304  
   305  			buf.WriteString(fmt.Sprintf(
   306  				"  %s:%s %#v => %#v%s\n",
   307  				attrK,
   308  				strings.Repeat(" ", keyLen-len(attrK)),
   309  				u,
   310  				v,
   311  				updateMsg))
   312  		}
   313  	}
   314  
   315  	return buf.String()
   316  }
   317  
   318  // InstanceDiff is the diff of a resource from some state to another.
   319  type InstanceDiff struct {
   320  	mu             sync.Mutex
   321  	Attributes     map[string]*ResourceAttrDiff
   322  	Destroy        bool
   323  	DestroyTainted bool
   324  }
   325  
   326  // ResourceAttrDiff is the diff of a single attribute of a resource.
   327  type ResourceAttrDiff struct {
   328  	Old         string      // Old Value
   329  	New         string      // New Value
   330  	NewComputed bool        // True if new value is computed (unknown currently)
   331  	NewRemoved  bool        // True if this attribute is being removed
   332  	NewExtra    interface{} // Extra information for the provider
   333  	RequiresNew bool        // True if change requires new resource
   334  	Sensitive   bool        // True if the data should not be displayed in UI output
   335  	Type        DiffAttrType
   336  }
   337  
   338  // Empty returns true if the diff for this attr is neutral
   339  func (d *ResourceAttrDiff) Empty() bool {
   340  	return d.Old == d.New && !d.NewComputed && !d.NewRemoved
   341  }
   342  
   343  func (d *ResourceAttrDiff) GoString() string {
   344  	return fmt.Sprintf("*%#v", *d)
   345  }
   346  
   347  // DiffAttrType is an enum type that says whether a resource attribute
   348  // diff is an input attribute (comes from the configuration) or an
   349  // output attribute (comes as a result of applying the configuration). An
   350  // example input would be "ami" for AWS and an example output would be
   351  // "private_ip".
   352  type DiffAttrType byte
   353  
   354  const (
   355  	DiffAttrUnknown DiffAttrType = iota
   356  	DiffAttrInput
   357  	DiffAttrOutput
   358  )
   359  
   360  func (d *InstanceDiff) init() {
   361  	if d.Attributes == nil {
   362  		d.Attributes = make(map[string]*ResourceAttrDiff)
   363  	}
   364  }
   365  
   366  func NewInstanceDiff() *InstanceDiff {
   367  	return &InstanceDiff{Attributes: make(map[string]*ResourceAttrDiff)}
   368  }
   369  
   370  // ChangeType returns the DiffChangeType represented by the diff
   371  // for this single instance.
   372  func (d *InstanceDiff) ChangeType() DiffChangeType {
   373  	if d.Empty() {
   374  		return DiffNone
   375  	}
   376  
   377  	if d.RequiresNew() && (d.GetDestroy() || d.GetDestroyTainted()) {
   378  		return DiffDestroyCreate
   379  	}
   380  
   381  	if d.GetDestroy() {
   382  		return DiffDestroy
   383  	}
   384  
   385  	if d.RequiresNew() {
   386  		return DiffCreate
   387  	}
   388  
   389  	return DiffUpdate
   390  }
   391  
   392  // Empty returns true if this diff encapsulates no changes.
   393  func (d *InstanceDiff) Empty() bool {
   394  	if d == nil {
   395  		return true
   396  	}
   397  
   398  	d.mu.Lock()
   399  	defer d.mu.Unlock()
   400  	return !d.Destroy && !d.DestroyTainted && len(d.Attributes) == 0
   401  }
   402  
   403  // Equal compares two diffs for exact equality.
   404  //
   405  // This is different from the Same comparison that is supported which
   406  // checks for operation equality taking into account computed values. Equal
   407  // instead checks for exact equality.
   408  func (d *InstanceDiff) Equal(d2 *InstanceDiff) bool {
   409  	// If one is nil, they must both be nil
   410  	if d == nil || d2 == nil {
   411  		return d == d2
   412  	}
   413  
   414  	// Use DeepEqual
   415  	return reflect.DeepEqual(d, d2)
   416  }
   417  
   418  // DeepCopy performs a deep copy of all parts of the InstanceDiff
   419  func (d *InstanceDiff) DeepCopy() *InstanceDiff {
   420  	copy, err := copystructure.Config{Lock: true}.Copy(d)
   421  	if err != nil {
   422  		panic(err)
   423  	}
   424  
   425  	return copy.(*InstanceDiff)
   426  }
   427  
   428  func (d *InstanceDiff) GoString() string {
   429  	return fmt.Sprintf("*%#v", InstanceDiff{
   430  		Attributes:     d.Attributes,
   431  		Destroy:        d.Destroy,
   432  		DestroyTainted: d.DestroyTainted,
   433  	})
   434  }
   435  
   436  // RequiresNew returns true if the diff requires the creation of a new
   437  // resource (implying the destruction of the old).
   438  func (d *InstanceDiff) RequiresNew() bool {
   439  	if d == nil {
   440  		return false
   441  	}
   442  
   443  	d.mu.Lock()
   444  	defer d.mu.Unlock()
   445  
   446  	return d.requiresNew()
   447  }
   448  
   449  func (d *InstanceDiff) requiresNew() bool {
   450  	if d == nil {
   451  		return false
   452  	}
   453  
   454  	if d.DestroyTainted {
   455  		return true
   456  	}
   457  
   458  	for _, rd := range d.Attributes {
   459  		if rd != nil && rd.RequiresNew {
   460  			return true
   461  		}
   462  	}
   463  
   464  	return false
   465  }
   466  
   467  // These methods are properly locked, for use outside other InstanceDiff
   468  // methods but everywhere else within in the terraform package.
   469  // TODO refactor the locking scheme
   470  func (d *InstanceDiff) SetTainted(b bool) {
   471  	d.mu.Lock()
   472  	defer d.mu.Unlock()
   473  
   474  	d.DestroyTainted = b
   475  }
   476  
   477  func (d *InstanceDiff) GetDestroyTainted() bool {
   478  	d.mu.Lock()
   479  	defer d.mu.Unlock()
   480  
   481  	return d.DestroyTainted
   482  }
   483  
   484  func (d *InstanceDiff) SetDestroy(b bool) {
   485  	d.mu.Lock()
   486  	defer d.mu.Unlock()
   487  
   488  	d.Destroy = b
   489  }
   490  
   491  func (d *InstanceDiff) GetDestroy() bool {
   492  	d.mu.Lock()
   493  	defer d.mu.Unlock()
   494  
   495  	return d.Destroy
   496  }
   497  
   498  func (d *InstanceDiff) SetAttribute(key string, attr *ResourceAttrDiff) {
   499  	d.mu.Lock()
   500  	defer d.mu.Unlock()
   501  
   502  	d.Attributes[key] = attr
   503  }
   504  
   505  func (d *InstanceDiff) DelAttribute(key string) {
   506  	d.mu.Lock()
   507  	defer d.mu.Unlock()
   508  
   509  	delete(d.Attributes, key)
   510  }
   511  
   512  func (d *InstanceDiff) GetAttribute(key string) (*ResourceAttrDiff, bool) {
   513  	d.mu.Lock()
   514  	defer d.mu.Unlock()
   515  
   516  	attr, ok := d.Attributes[key]
   517  	return attr, ok
   518  }
   519  func (d *InstanceDiff) GetAttributesLen() int {
   520  	d.mu.Lock()
   521  	defer d.mu.Unlock()
   522  
   523  	return len(d.Attributes)
   524  }
   525  
   526  // Safely copies the Attributes map
   527  func (d *InstanceDiff) CopyAttributes() map[string]*ResourceAttrDiff {
   528  	d.mu.Lock()
   529  	defer d.mu.Unlock()
   530  
   531  	attrs := make(map[string]*ResourceAttrDiff)
   532  	for k, v := range d.Attributes {
   533  		attrs[k] = v
   534  	}
   535  
   536  	return attrs
   537  }
   538  
   539  // Same checks whether or not two InstanceDiff's are the "same". When
   540  // we say "same", it is not necessarily exactly equal. Instead, it is
   541  // just checking that the same attributes are changing, a destroy
   542  // isn't suddenly happening, etc.
   543  func (d *InstanceDiff) Same(d2 *InstanceDiff) (bool, string) {
   544  	// we can safely compare the pointers without a lock
   545  	switch {
   546  	case d == nil && d2 == nil:
   547  		return true, ""
   548  	case d == nil || d2 == nil:
   549  		return false, "one nil"
   550  	case d == d2:
   551  		return true, ""
   552  	}
   553  
   554  	d.mu.Lock()
   555  	defer d.mu.Unlock()
   556  
   557  	if d.Destroy != d2.GetDestroy() {
   558  		return false, fmt.Sprintf(
   559  			"diff: Destroy; old: %t, new: %t", d.Destroy, d2.GetDestroy())
   560  	}
   561  	if d.requiresNew() != d2.RequiresNew() {
   562  		return false, fmt.Sprintf(
   563  			"diff RequiresNew; old: %t, new: %t", d.requiresNew(), d2.RequiresNew())
   564  	}
   565  
   566  	// Go through the old diff and make sure the new diff has all the
   567  	// same attributes. To start, build up the check map to be all the keys.
   568  	checkOld := make(map[string]struct{})
   569  	checkNew := make(map[string]struct{})
   570  	for k, _ := range d.Attributes {
   571  		checkOld[k] = struct{}{}
   572  	}
   573  	for k, _ := range d2.CopyAttributes() {
   574  		checkNew[k] = struct{}{}
   575  	}
   576  
   577  	// Make an ordered list so we are sure the approximated hashes are left
   578  	// to process at the end of the loop
   579  	keys := make([]string, 0, len(d.Attributes))
   580  	for k, _ := range d.Attributes {
   581  		keys = append(keys, k)
   582  	}
   583  	sort.StringSlice(keys).Sort()
   584  
   585  	for _, k := range keys {
   586  		diffOld := d.Attributes[k]
   587  
   588  		if _, ok := checkOld[k]; !ok {
   589  			// We're not checking this key for whatever reason (see where
   590  			// check is modified).
   591  			continue
   592  		}
   593  
   594  		// Remove this key since we'll never hit it again
   595  		delete(checkOld, k)
   596  		delete(checkNew, k)
   597  
   598  		_, ok := d2.GetAttribute(k)
   599  		if !ok {
   600  			// If there's no new attribute, and the old diff expected the attribute
   601  			// to be removed, that's just fine.
   602  			if diffOld.NewRemoved {
   603  				continue
   604  			}
   605  
   606  			// No exact match, but maybe this is a set containing computed
   607  			// values. So check if there is an approximate hash in the key
   608  			// and if so, try to match the key.
   609  			if strings.Contains(k, "~") {
   610  				parts := strings.Split(k, ".")
   611  				parts2 := append([]string(nil), parts...)
   612  
   613  				re := regexp.MustCompile(`^~\d+$`)
   614  				for i, part := range parts {
   615  					if re.MatchString(part) {
   616  						// we're going to consider this the base of a
   617  						// computed hash, and remove all longer matching fields
   618  						ok = true
   619  
   620  						parts2[i] = `\d+`
   621  						parts2 = parts2[:i+1]
   622  						break
   623  					}
   624  				}
   625  
   626  				re, err := regexp.Compile("^" + strings.Join(parts2, `\.`))
   627  				if err != nil {
   628  					return false, fmt.Sprintf("regexp failed to compile; err: %#v", err)
   629  				}
   630  
   631  				for k2, _ := range checkNew {
   632  					if re.MatchString(k2) {
   633  						delete(checkNew, k2)
   634  					}
   635  				}
   636  			}
   637  
   638  			// This is a little tricky, but when a diff contains a computed
   639  			// list, set, or map that can only be interpolated after the apply
   640  			// command has created the dependent resources, it could turn out
   641  			// that the result is actually the same as the existing state which
   642  			// would remove the key from the diff.
   643  			if diffOld.NewComputed && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) {
   644  				ok = true
   645  			}
   646  
   647  			// Similarly, in a RequiresNew scenario, a list that shows up in the plan
   648  			// diff can disappear from the apply diff, which is calculated from an
   649  			// empty state.
   650  			if d.requiresNew() && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) {
   651  				ok = true
   652  			}
   653  
   654  			if !ok {
   655  				return false, fmt.Sprintf("attribute mismatch: %s", k)
   656  			}
   657  		}
   658  
   659  		// search for the suffix of the base of a [computed] map, list or set.
   660  		multiVal := regexp.MustCompile(`\.(#|~#|%)$`)
   661  		match := multiVal.FindStringSubmatch(k)
   662  
   663  		if diffOld.NewComputed && len(match) == 2 {
   664  			matchLen := len(match[1])
   665  
   666  			// This is a computed list, set, or map, so remove any keys with
   667  			// this prefix from the check list.
   668  			kprefix := k[:len(k)-matchLen]
   669  			for k2, _ := range checkOld {
   670  				if strings.HasPrefix(k2, kprefix) {
   671  					delete(checkOld, k2)
   672  				}
   673  			}
   674  			for k2, _ := range checkNew {
   675  				if strings.HasPrefix(k2, kprefix) {
   676  					delete(checkNew, k2)
   677  				}
   678  			}
   679  		}
   680  
   681  		// TODO: check for the same value if not computed
   682  	}
   683  
   684  	// Check for leftover attributes
   685  	if len(checkNew) > 0 {
   686  		extras := make([]string, 0, len(checkNew))
   687  		for attr, _ := range checkNew {
   688  			extras = append(extras, attr)
   689  		}
   690  		return false,
   691  			fmt.Sprintf("extra attributes: %s", strings.Join(extras, ", "))
   692  	}
   693  
   694  	return true, ""
   695  }
   696  
   697  // moduleDiffSort implements sort.Interface to sort module diffs by path.
   698  type moduleDiffSort []*ModuleDiff
   699  
   700  func (s moduleDiffSort) Len() int      { return len(s) }
   701  func (s moduleDiffSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
   702  func (s moduleDiffSort) Less(i, j int) bool {
   703  	a := s[i]
   704  	b := s[j]
   705  
   706  	// If the lengths are different, then the shorter one always wins
   707  	if len(a.Path) != len(b.Path) {
   708  		return len(a.Path) < len(b.Path)
   709  	}
   710  
   711  	// Otherwise, compare lexically
   712  	return strings.Join(a.Path, ".") < strings.Join(b.Path, ".")
   713  }