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