github.com/jrasell/terraform@v0.6.17-0.20160523115548-2652f5232949/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  		if rdiff.RequiresNew() && (rdiff.Destroy || rdiff.DestroyTainted) {
   219  			crud = "DESTROY/CREATE"
   220  		} else if rdiff.Destroy {
   221  			crud = "DESTROY"
   222  		} else if rdiff.RequiresNew() {
   223  			crud = "CREATE"
   224  		}
   225  
   226  		buf.WriteString(fmt.Sprintf(
   227  			"%s: %s\n",
   228  			crud,
   229  			name))
   230  
   231  		keyLen := 0
   232  		keys := make([]string, 0, len(rdiff.Attributes))
   233  		for key, _ := range rdiff.Attributes {
   234  			if key == "id" {
   235  				continue
   236  			}
   237  
   238  			keys = append(keys, key)
   239  			if len(key) > keyLen {
   240  				keyLen = len(key)
   241  			}
   242  		}
   243  		sort.Strings(keys)
   244  
   245  		for _, attrK := range keys {
   246  			attrDiff := rdiff.Attributes[attrK]
   247  
   248  			v := attrDiff.New
   249  			if attrDiff.NewComputed {
   250  				v = "<computed>"
   251  			}
   252  
   253  			newResource := ""
   254  			if attrDiff.RequiresNew {
   255  				newResource = " (forces new resource)"
   256  			}
   257  
   258  			buf.WriteString(fmt.Sprintf(
   259  				"  %s:%s %#v => %#v%s\n",
   260  				attrK,
   261  				strings.Repeat(" ", keyLen-len(attrK)),
   262  				attrDiff.Old,
   263  				v,
   264  				newResource))
   265  		}
   266  	}
   267  
   268  	return buf.String()
   269  }
   270  
   271  // InstanceDiff is the diff of a resource from some state to another.
   272  type InstanceDiff struct {
   273  	Attributes     map[string]*ResourceAttrDiff
   274  	Destroy        bool
   275  	DestroyTainted bool
   276  }
   277  
   278  // ResourceAttrDiff is the diff of a single attribute of a resource.
   279  type ResourceAttrDiff struct {
   280  	Old         string      // Old Value
   281  	New         string      // New Value
   282  	NewComputed bool        // True if new value is computed (unknown currently)
   283  	NewRemoved  bool        // True if this attribute is being removed
   284  	NewExtra    interface{} // Extra information for the provider
   285  	RequiresNew bool        // True if change requires new resource
   286  	Type        DiffAttrType
   287  }
   288  
   289  // Empty returns true if the diff for this attr is neutral
   290  func (d *ResourceAttrDiff) Empty() bool {
   291  	return d.Old == d.New && !d.NewComputed && !d.NewRemoved
   292  }
   293  
   294  func (d *ResourceAttrDiff) GoString() string {
   295  	return fmt.Sprintf("*%#v", *d)
   296  }
   297  
   298  // DiffAttrType is an enum type that says whether a resource attribute
   299  // diff is an input attribute (comes from the configuration) or an
   300  // output attribute (comes as a result of applying the configuration). An
   301  // example input would be "ami" for AWS and an example output would be
   302  // "private_ip".
   303  type DiffAttrType byte
   304  
   305  const (
   306  	DiffAttrUnknown DiffAttrType = iota
   307  	DiffAttrInput
   308  	DiffAttrOutput
   309  )
   310  
   311  func (d *InstanceDiff) init() {
   312  	if d.Attributes == nil {
   313  		d.Attributes = make(map[string]*ResourceAttrDiff)
   314  	}
   315  }
   316  
   317  // ChangeType returns the DiffChangeType represented by the diff
   318  // for this single instance.
   319  func (d *InstanceDiff) ChangeType() DiffChangeType {
   320  	if d.Empty() {
   321  		return DiffNone
   322  	}
   323  
   324  	if d.RequiresNew() && (d.Destroy || d.DestroyTainted) {
   325  		return DiffDestroyCreate
   326  	}
   327  
   328  	if d.Destroy {
   329  		return DiffDestroy
   330  	}
   331  
   332  	if d.RequiresNew() {
   333  		return DiffCreate
   334  	}
   335  
   336  	return DiffUpdate
   337  }
   338  
   339  // Empty returns true if this diff encapsulates no changes.
   340  func (d *InstanceDiff) Empty() bool {
   341  	if d == nil {
   342  		return true
   343  	}
   344  
   345  	return !d.Destroy && len(d.Attributes) == 0
   346  }
   347  
   348  func (d *InstanceDiff) GoString() string {
   349  	return fmt.Sprintf("*%#v", *d)
   350  }
   351  
   352  // RequiresNew returns true if the diff requires the creation of a new
   353  // resource (implying the destruction of the old).
   354  func (d *InstanceDiff) RequiresNew() bool {
   355  	if d == nil {
   356  		return false
   357  	}
   358  
   359  	for _, rd := range d.Attributes {
   360  		if rd != nil && rd.RequiresNew {
   361  			return true
   362  		}
   363  	}
   364  
   365  	return false
   366  }
   367  
   368  // Same checks whether or not two InstanceDiff's are the "same". When
   369  // we say "same", it is not necessarily exactly equal. Instead, it is
   370  // just checking that the same attributes are changing, a destroy
   371  // isn't suddenly happening, etc.
   372  func (d *InstanceDiff) Same(d2 *InstanceDiff) (bool, string) {
   373  	if d == nil && d2 == nil {
   374  		return true, ""
   375  	} else if d == nil || d2 == nil {
   376  		return false, "both nil"
   377  	}
   378  
   379  	if d.Destroy != d2.Destroy {
   380  		return false, fmt.Sprintf(
   381  			"diff: Destroy; old: %t, new: %t", d.Destroy, d2.Destroy)
   382  	}
   383  	if d.RequiresNew() != d2.RequiresNew() {
   384  		return false, fmt.Sprintf(
   385  			"diff RequiresNew; old: %t, new: %t", d.RequiresNew(), d2.RequiresNew())
   386  	}
   387  
   388  	// Go through the old diff and make sure the new diff has all the
   389  	// same attributes. To start, build up the check map to be all the keys.
   390  	checkOld := make(map[string]struct{})
   391  	checkNew := make(map[string]struct{})
   392  	for k, _ := range d.Attributes {
   393  		checkOld[k] = struct{}{}
   394  	}
   395  	for k, _ := range d2.Attributes {
   396  		checkNew[k] = struct{}{}
   397  	}
   398  
   399  	// Make an ordered list so we are sure the approximated hashes are left
   400  	// to process at the end of the loop
   401  	keys := make([]string, 0, len(d.Attributes))
   402  	for k, _ := range d.Attributes {
   403  		keys = append(keys, k)
   404  	}
   405  	sort.StringSlice(keys).Sort()
   406  
   407  	for _, k := range keys {
   408  		diffOld := d.Attributes[k]
   409  
   410  		if _, ok := checkOld[k]; !ok {
   411  			// We're not checking this key for whatever reason (see where
   412  			// check is modified).
   413  			continue
   414  		}
   415  
   416  		// Remove this key since we'll never hit it again
   417  		delete(checkOld, k)
   418  		delete(checkNew, k)
   419  
   420  		_, ok := d2.Attributes[k]
   421  		if !ok {
   422  			// If there's no new attribute, and the old diff expected the attribute
   423  			// to be removed, that's just fine.
   424  			if diffOld.NewRemoved {
   425  				continue
   426  			}
   427  
   428  			// No exact match, but maybe this is a set containing computed
   429  			// values. So check if there is an approximate hash in the key
   430  			// and if so, try to match the key.
   431  			if strings.Contains(k, "~") {
   432  				// TODO (SvH): There should be a better way to do this...
   433  				parts := strings.Split(k, ".")
   434  				parts2 := strings.Split(k, ".")
   435  				re := regexp.MustCompile(`^~\d+$`)
   436  				for i, part := range parts {
   437  					if re.MatchString(part) {
   438  						parts2[i] = `\d+`
   439  					}
   440  				}
   441  				re, err := regexp.Compile("^" + strings.Join(parts2, `\.`) + "$")
   442  				if err != nil {
   443  					return false, fmt.Sprintf("regexp failed to compile; err: %#v", err)
   444  				}
   445  				for k2, _ := range checkNew {
   446  					if re.MatchString(k2) {
   447  						delete(checkNew, k2)
   448  
   449  						if diffOld.NewComputed && strings.HasSuffix(k, ".#") {
   450  							// This is a computed list or set, so remove any keys with this
   451  							// prefix from the check list.
   452  							prefix := k2[:len(k2)-1]
   453  							for k2, _ := range checkNew {
   454  								if strings.HasPrefix(k2, prefix) {
   455  									delete(checkNew, k2)
   456  								}
   457  							}
   458  						}
   459  						ok = true
   460  						break
   461  					}
   462  				}
   463  			}
   464  
   465  			// This is a little tricky, but when a diff contains a computed list
   466  			// or set that can only be interpolated after the apply command has
   467  			// created the dependent resources, it could turn out that the result
   468  			// is actually the same as the existing state which would remove the
   469  			// key from the diff.
   470  			if diffOld.NewComputed && strings.HasSuffix(k, ".#") {
   471  				ok = true
   472  			}
   473  
   474  			// Similarly, in a RequiresNew scenario, a list that shows up in the plan
   475  			// diff can disappear from the apply diff, which is calculated from an
   476  			// empty state.
   477  			if d.RequiresNew() && strings.HasSuffix(k, ".#") {
   478  				ok = true
   479  			}
   480  
   481  			if !ok {
   482  				return false, fmt.Sprintf("attribute mismatch: %s", k)
   483  			}
   484  		}
   485  
   486  		if diffOld.NewComputed && strings.HasSuffix(k, ".#") {
   487  			// This is a computed list or set, so remove any keys with this
   488  			// prefix from the check list.
   489  			kprefix := k[:len(k)-1]
   490  			for k2, _ := range checkOld {
   491  				if strings.HasPrefix(k2, kprefix) {
   492  					delete(checkOld, k2)
   493  				}
   494  			}
   495  			for k2, _ := range checkNew {
   496  				if strings.HasPrefix(k2, kprefix) {
   497  					delete(checkNew, k2)
   498  				}
   499  			}
   500  		}
   501  
   502  		// TODO: check for the same value if not computed
   503  	}
   504  
   505  	// Check for leftover attributes
   506  	if len(checkNew) > 0 {
   507  		extras := make([]string, 0, len(checkNew))
   508  		for attr, _ := range checkNew {
   509  			extras = append(extras, attr)
   510  		}
   511  		return false,
   512  			fmt.Sprintf("extra attributes: %s", strings.Join(extras, ", "))
   513  	}
   514  
   515  	return true, ""
   516  }