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