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