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