github.com/turtlemonvh/terraform@v0.6.9-0.20151204001754-8e40b6b855e8/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  func (d *ResourceAttrDiff) GoString() string {
   290  	return fmt.Sprintf("*%#v", *d)
   291  }
   292  
   293  // DiffAttrType is an enum type that says whether a resource attribute
   294  // diff is an input attribute (comes from the configuration) or an
   295  // output attribute (comes as a result of applying the configuration). An
   296  // example input would be "ami" for AWS and an example output would be
   297  // "private_ip".
   298  type DiffAttrType byte
   299  
   300  const (
   301  	DiffAttrUnknown DiffAttrType = iota
   302  	DiffAttrInput
   303  	DiffAttrOutput
   304  )
   305  
   306  func (d *InstanceDiff) init() {
   307  	if d.Attributes == nil {
   308  		d.Attributes = make(map[string]*ResourceAttrDiff)
   309  	}
   310  }
   311  
   312  // ChangeType returns the DiffChangeType represented by the diff
   313  // for this single instance.
   314  func (d *InstanceDiff) ChangeType() DiffChangeType {
   315  	if d.Empty() {
   316  		return DiffNone
   317  	}
   318  
   319  	if d.RequiresNew() && (d.Destroy || d.DestroyTainted) {
   320  		return DiffDestroyCreate
   321  	}
   322  
   323  	if d.Destroy {
   324  		return DiffDestroy
   325  	}
   326  
   327  	if d.RequiresNew() {
   328  		return DiffCreate
   329  	}
   330  
   331  	return DiffUpdate
   332  }
   333  
   334  // Empty returns true if this diff encapsulates no changes.
   335  func (d *InstanceDiff) Empty() bool {
   336  	if d == nil {
   337  		return true
   338  	}
   339  
   340  	return !d.Destroy && len(d.Attributes) == 0
   341  }
   342  
   343  func (d *InstanceDiff) GoString() string {
   344  	return fmt.Sprintf("*%#v", *d)
   345  }
   346  
   347  // RequiresNew returns true if the diff requires the creation of a new
   348  // resource (implying the destruction of the old).
   349  func (d *InstanceDiff) RequiresNew() bool {
   350  	if d == nil {
   351  		return false
   352  	}
   353  
   354  	for _, rd := range d.Attributes {
   355  		if rd != nil && rd.RequiresNew {
   356  			return true
   357  		}
   358  	}
   359  
   360  	return false
   361  }
   362  
   363  // Same checks whether or not two InstanceDiff's are the "same". When
   364  // we say "same", it is not necessarily exactly equal. Instead, it is
   365  // just checking that the same attributes are changing, a destroy
   366  // isn't suddenly happening, etc.
   367  func (d *InstanceDiff) Same(d2 *InstanceDiff) (bool, string) {
   368  	if d == nil && d2 == nil {
   369  		return true, ""
   370  	} else if d == nil || d2 == nil {
   371  		return false, "both nil"
   372  	}
   373  
   374  	if d.Destroy != d2.Destroy {
   375  		return false, fmt.Sprintf(
   376  			"diff: Destroy; old: %t, new: %t", d.Destroy, d2.Destroy)
   377  	}
   378  	if d.RequiresNew() != d2.RequiresNew() {
   379  		return false, fmt.Sprintf(
   380  			"diff RequiresNew; old: %t, new: %t", d.RequiresNew(), d2.RequiresNew())
   381  	}
   382  
   383  	// Go through the old diff and make sure the new diff has all the
   384  	// same attributes. To start, build up the check map to be all the keys.
   385  	checkOld := make(map[string]struct{})
   386  	checkNew := make(map[string]struct{})
   387  	for k, _ := range d.Attributes {
   388  		checkOld[k] = struct{}{}
   389  	}
   390  	for k, _ := range d2.Attributes {
   391  		checkNew[k] = struct{}{}
   392  	}
   393  
   394  	// Make an ordered list so we are sure the approximated hashes are left
   395  	// to process at the end of the loop
   396  	keys := make([]string, 0, len(d.Attributes))
   397  	for k, _ := range d.Attributes {
   398  		keys = append(keys, k)
   399  	}
   400  	sort.StringSlice(keys).Sort()
   401  
   402  	for _, k := range keys {
   403  		diffOld := d.Attributes[k]
   404  
   405  		if _, ok := checkOld[k]; !ok {
   406  			// We're not checking this key for whatever reason (see where
   407  			// check is modified).
   408  			continue
   409  		}
   410  
   411  		// Remove this key since we'll never hit it again
   412  		delete(checkOld, k)
   413  		delete(checkNew, k)
   414  
   415  		_, ok := d2.Attributes[k]
   416  		if !ok {
   417  			// If there's no new attribute, and the old diff expected the attribute
   418  			// to be removed, that's just fine.
   419  			if diffOld.NewRemoved {
   420  				continue
   421  			}
   422  
   423  			// No exact match, but maybe this is a set containing computed
   424  			// values. So check if there is an approximate hash in the key
   425  			// and if so, try to match the key.
   426  			if strings.Contains(k, "~") {
   427  				// TODO (SvH): There should be a better way to do this...
   428  				parts := strings.Split(k, ".")
   429  				parts2 := strings.Split(k, ".")
   430  				re := regexp.MustCompile(`^~\d+$`)
   431  				for i, part := range parts {
   432  					if re.MatchString(part) {
   433  						parts2[i] = `\d+`
   434  					}
   435  				}
   436  				re, err := regexp.Compile("^" + strings.Join(parts2, `\.`) + "$")
   437  				if err != nil {
   438  					return false, fmt.Sprintf("regexp failed to compile; err: %#v", err)
   439  				}
   440  				for k2, _ := range checkNew {
   441  					if re.MatchString(k2) {
   442  						delete(checkNew, k2)
   443  
   444  						if diffOld.NewComputed && strings.HasSuffix(k, ".#") {
   445  							// This is a computed list or set, so remove any keys with this
   446  							// prefix from the check list.
   447  							prefix := k2[:len(k2)-1]
   448  							for k2, _ := range checkNew {
   449  								if strings.HasPrefix(k2, prefix) {
   450  									delete(checkNew, k2)
   451  								}
   452  							}
   453  						}
   454  						ok = true
   455  						break
   456  					}
   457  				}
   458  			}
   459  
   460  			// This is a little tricky, but when a diff contains a computed list
   461  			// or set that can only be interpolated after the apply command has
   462  			// created the dependent resources, it could turn out that the result
   463  			// is actually the same as the existing state which would remove the
   464  			// key from the diff.
   465  			if diffOld.NewComputed && strings.HasSuffix(k, ".#") {
   466  				ok = true
   467  			}
   468  
   469  			if !ok {
   470  				return false, fmt.Sprintf("attribute mismatch: %s", k)
   471  			}
   472  		}
   473  
   474  		if diffOld.NewComputed && strings.HasSuffix(k, ".#") {
   475  			// This is a computed list or set, so remove any keys with this
   476  			// prefix from the check list.
   477  			kprefix := k[:len(k)-1]
   478  			for k2, _ := range checkOld {
   479  				if strings.HasPrefix(k2, kprefix) {
   480  					delete(checkOld, k2)
   481  				}
   482  			}
   483  			for k2, _ := range checkNew {
   484  				if strings.HasPrefix(k2, kprefix) {
   485  					delete(checkNew, k2)
   486  				}
   487  			}
   488  		}
   489  
   490  		// TODO: check for the same value if not computed
   491  	}
   492  
   493  	// Check for leftover attributes
   494  	if len(checkNew) > 0 {
   495  		extras := make([]string, 0, len(checkNew))
   496  		for attr, _ := range checkNew {
   497  			extras = append(extras, attr)
   498  		}
   499  		return false,
   500  			fmt.Sprintf("extra attributes: %s", strings.Join(extras, ", "))
   501  	}
   502  
   503  	return true, ""
   504  }