github.com/tomaszheflik/terraform@v0.7.3-0.20160827060421-32f990b41594/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 && !d.DestroyTainted && len(d.Attributes) == 0
   365  }
   366  
   367  func (d *InstanceDiff) GoString() string {
   368  	return fmt.Sprintf("*%#v", InstanceDiff{
   369  		Attributes:     d.Attributes,
   370  		Destroy:        d.Destroy,
   371  		DestroyTainted: d.DestroyTainted,
   372  	})
   373  }
   374  
   375  // RequiresNew returns true if the diff requires the creation of a new
   376  // resource (implying the destruction of the old).
   377  func (d *InstanceDiff) RequiresNew() bool {
   378  	if d == nil {
   379  		return false
   380  	}
   381  
   382  	d.mu.Lock()
   383  	defer d.mu.Unlock()
   384  
   385  	return d.requiresNew()
   386  }
   387  
   388  func (d *InstanceDiff) requiresNew() bool {
   389  	if d == nil {
   390  		return false
   391  	}
   392  
   393  	if d.DestroyTainted {
   394  		return true
   395  	}
   396  
   397  	for _, rd := range d.Attributes {
   398  		if rd != nil && rd.RequiresNew {
   399  			return true
   400  		}
   401  	}
   402  
   403  	return false
   404  }
   405  
   406  // These methods are properly locked, for use outside other InstanceDiff
   407  // methods but everywhere else within in the terraform package.
   408  // TODO refactor the locking scheme
   409  func (d *InstanceDiff) SetTainted(b bool) {
   410  	d.mu.Lock()
   411  	defer d.mu.Unlock()
   412  
   413  	d.DestroyTainted = b
   414  }
   415  
   416  func (d *InstanceDiff) GetDestroyTainted() bool {
   417  	d.mu.Lock()
   418  	defer d.mu.Unlock()
   419  
   420  	return d.DestroyTainted
   421  }
   422  
   423  func (d *InstanceDiff) SetDestroy(b bool) {
   424  	d.mu.Lock()
   425  	defer d.mu.Unlock()
   426  
   427  	d.Destroy = b
   428  }
   429  
   430  func (d *InstanceDiff) GetDestroy() bool {
   431  	d.mu.Lock()
   432  	defer d.mu.Unlock()
   433  
   434  	return d.Destroy
   435  }
   436  
   437  func (d *InstanceDiff) SetAttribute(key string, attr *ResourceAttrDiff) {
   438  	d.mu.Lock()
   439  	defer d.mu.Unlock()
   440  
   441  	d.Attributes[key] = attr
   442  }
   443  
   444  func (d *InstanceDiff) DelAttribute(key string) {
   445  	d.mu.Lock()
   446  	defer d.mu.Unlock()
   447  
   448  	delete(d.Attributes, key)
   449  }
   450  
   451  func (d *InstanceDiff) GetAttribute(key string) (*ResourceAttrDiff, bool) {
   452  	d.mu.Lock()
   453  	defer d.mu.Unlock()
   454  
   455  	attr, ok := d.Attributes[key]
   456  	return attr, ok
   457  }
   458  func (d *InstanceDiff) GetAttributesLen() int {
   459  	d.mu.Lock()
   460  	defer d.mu.Unlock()
   461  
   462  	return len(d.Attributes)
   463  }
   464  
   465  // Safely copies the Attributes map
   466  func (d *InstanceDiff) CopyAttributes() map[string]*ResourceAttrDiff {
   467  	d.mu.Lock()
   468  	defer d.mu.Unlock()
   469  
   470  	attrs := make(map[string]*ResourceAttrDiff)
   471  	for k, v := range d.Attributes {
   472  		attrs[k] = v
   473  	}
   474  
   475  	return attrs
   476  }
   477  
   478  // Same checks whether or not two InstanceDiff's are the "same". When
   479  // we say "same", it is not necessarily exactly equal. Instead, it is
   480  // just checking that the same attributes are changing, a destroy
   481  // isn't suddenly happening, etc.
   482  func (d *InstanceDiff) Same(d2 *InstanceDiff) (bool, string) {
   483  	// we can safely compare the pointers without a lock
   484  	switch {
   485  	case d == nil && d2 == nil:
   486  		return true, ""
   487  	case d == nil || d2 == nil:
   488  		return false, "one nil"
   489  	case d == d2:
   490  		return true, ""
   491  	}
   492  
   493  	d.mu.Lock()
   494  	defer d.mu.Unlock()
   495  
   496  	if d.Destroy != d2.GetDestroy() {
   497  		return false, fmt.Sprintf(
   498  			"diff: Destroy; old: %t, new: %t", d.Destroy, d2.GetDestroy())
   499  	}
   500  	if d.requiresNew() != d2.RequiresNew() {
   501  		return false, fmt.Sprintf(
   502  			"diff RequiresNew; old: %t, new: %t", d.requiresNew(), d2.RequiresNew())
   503  	}
   504  
   505  	// Go through the old diff and make sure the new diff has all the
   506  	// same attributes. To start, build up the check map to be all the keys.
   507  	checkOld := make(map[string]struct{})
   508  	checkNew := make(map[string]struct{})
   509  	for k, _ := range d.Attributes {
   510  		checkOld[k] = struct{}{}
   511  	}
   512  	for k, _ := range d2.CopyAttributes() {
   513  		checkNew[k] = struct{}{}
   514  	}
   515  
   516  	// Make an ordered list so we are sure the approximated hashes are left
   517  	// to process at the end of the loop
   518  	keys := make([]string, 0, len(d.Attributes))
   519  	for k, _ := range d.Attributes {
   520  		keys = append(keys, k)
   521  	}
   522  	sort.StringSlice(keys).Sort()
   523  
   524  	for _, k := range keys {
   525  		diffOld := d.Attributes[k]
   526  
   527  		if _, ok := checkOld[k]; !ok {
   528  			// We're not checking this key for whatever reason (see where
   529  			// check is modified).
   530  			continue
   531  		}
   532  
   533  		// Remove this key since we'll never hit it again
   534  		delete(checkOld, k)
   535  		delete(checkNew, k)
   536  
   537  		_, ok := d2.GetAttribute(k)
   538  		if !ok {
   539  			// If there's no new attribute, and the old diff expected the attribute
   540  			// to be removed, that's just fine.
   541  			if diffOld.NewRemoved {
   542  				continue
   543  			}
   544  
   545  			// No exact match, but maybe this is a set containing computed
   546  			// values. So check if there is an approximate hash in the key
   547  			// and if so, try to match the key.
   548  			if strings.Contains(k, "~") {
   549  				parts := strings.Split(k, ".")
   550  				parts2 := append([]string(nil), parts...)
   551  
   552  				re := regexp.MustCompile(`^~\d+$`)
   553  				for i, part := range parts {
   554  					if re.MatchString(part) {
   555  						// we're going to consider this the base of a
   556  						// computed hash, and remove all longer matching fields
   557  						ok = true
   558  
   559  						parts2[i] = `\d+`
   560  						parts2 = parts2[:i+1]
   561  						break
   562  					}
   563  				}
   564  
   565  				re, err := regexp.Compile("^" + strings.Join(parts2, `\.`))
   566  				if err != nil {
   567  					return false, fmt.Sprintf("regexp failed to compile; err: %#v", err)
   568  				}
   569  
   570  				for k2, _ := range checkNew {
   571  					if re.MatchString(k2) {
   572  						delete(checkNew, k2)
   573  					}
   574  				}
   575  			}
   576  
   577  			// This is a little tricky, but when a diff contains a computed
   578  			// list, set, or map that can only be interpolated after the apply
   579  			// command has created the dependent resources, it could turn out
   580  			// that the result is actually the same as the existing state which
   581  			// would remove the key from the diff.
   582  			if diffOld.NewComputed && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) {
   583  				ok = true
   584  			}
   585  
   586  			// Similarly, in a RequiresNew scenario, a list that shows up in the plan
   587  			// diff can disappear from the apply diff, which is calculated from an
   588  			// empty state.
   589  			if d.requiresNew() && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) {
   590  				ok = true
   591  			}
   592  
   593  			if !ok {
   594  				return false, fmt.Sprintf("attribute mismatch: %s", k)
   595  			}
   596  		}
   597  
   598  		// search for the suffix of the base of a [computed] map, list or set.
   599  		multiVal := regexp.MustCompile(`\.(#|~#|%)$`)
   600  		match := multiVal.FindStringSubmatch(k)
   601  
   602  		if diffOld.NewComputed && len(match) == 2 {
   603  			matchLen := len(match[1])
   604  
   605  			// This is a computed list, set, or map, so remove any keys with
   606  			// this prefix from the check list.
   607  			kprefix := k[:len(k)-matchLen]
   608  			for k2, _ := range checkOld {
   609  				if strings.HasPrefix(k2, kprefix) {
   610  					delete(checkOld, k2)
   611  				}
   612  			}
   613  			for k2, _ := range checkNew {
   614  				if strings.HasPrefix(k2, kprefix) {
   615  					delete(checkNew, k2)
   616  				}
   617  			}
   618  		}
   619  
   620  		// TODO: check for the same value if not computed
   621  	}
   622  
   623  	// Check for leftover attributes
   624  	if len(checkNew) > 0 {
   625  		extras := make([]string, 0, len(checkNew))
   626  		for attr, _ := range checkNew {
   627  			extras = append(extras, attr)
   628  		}
   629  		return false,
   630  			fmt.Sprintf("extra attributes: %s", strings.Join(extras, ", "))
   631  	}
   632  
   633  	return true, ""
   634  }