github.com/chalford/terraform@v0.3.7-0.20150113080010-a78c69a8c81f/helper/diff/resource_builder.go (about)

     1  package diff
     2  
     3  import (
     4  	"strings"
     5  
     6  	"github.com/hashicorp/terraform/flatmap"
     7  	"github.com/hashicorp/terraform/terraform"
     8  )
     9  
    10  // AttrType is an enum that tells the ResourceBuilder what type of attribute
    11  // an attribute is, affecting the overall diff output.
    12  //
    13  // The valid values are:
    14  //
    15  //   * AttrTypeCreate - This attribute can only be set or updated on create.
    16  //       This means that if this attribute is changed, it will require a new
    17  //       resource to be created if it is already created.
    18  //
    19  //   * AttrTypeUpdate - This attribute can be set at create time or updated
    20  //       in-place. Changing this attribute does not require a new resource.
    21  //
    22  type AttrType byte
    23  
    24  const (
    25  	AttrTypeUnknown AttrType = iota
    26  	AttrTypeCreate
    27  	AttrTypeUpdate
    28  )
    29  
    30  // ResourceBuilder is a helper that knows about how a single resource
    31  // changes and how those changes affect the diff.
    32  type ResourceBuilder struct {
    33  	// Attrs are the mapping of attributes that can be set from the
    34  	// configuration, and the affect they have. See the documentation for
    35  	// AttrType for more info.
    36  	//
    37  	// Sometimes attributes in here are also computed. For example, an
    38  	// "availability_zone" might be optional, but will be chosen for you
    39  	// by AWS. In that case, specify it both here and in ComputedAttrs.
    40  	// This will make sure that the absence of the configuration won't
    41  	// cause a diff by setting it to the empty string.
    42  	Attrs map[string]AttrType
    43  
    44  	// ComputedAttrs are the attributes that are computed at
    45  	// resource creation time.
    46  	ComputedAttrs []string
    47  
    48  	// ComputedAttrsUpdate are the attributes that are computed
    49  	// at resource update time (this includes creation).
    50  	ComputedAttrsUpdate []string
    51  
    52  	// PreProcess is a mapping of exact keys that are sent through
    53  	// a pre-processor before comparing values. The original value will
    54  	// be put in the "NewExtra" field of the diff.
    55  	PreProcess map[string]PreProcessFunc
    56  }
    57  
    58  // PreProcessFunc is used with the PreProcess field in a ResourceBuilder
    59  type PreProcessFunc func(string) string
    60  
    61  // Diff returns the ResourceDiff for a resource given its state and
    62  // configuration.
    63  func (b *ResourceBuilder) Diff(
    64  	s *terraform.InstanceState,
    65  	c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) {
    66  	attrs := make(map[string]*terraform.ResourceAttrDiff)
    67  
    68  	// We require a new resource if the ID is empty. Or, later, we set
    69  	// this to true if any configuration changed that triggers a new resource.
    70  	requiresNew := s.ID == ""
    71  
    72  	// Flatten the raw and processed configuration
    73  	flatRaw := flatmap.Flatten(c.Raw)
    74  	flatConfig := flatmap.Flatten(c.Config)
    75  
    76  	for ak, at := range b.Attrs {
    77  		// Keep track of all the keys we saw in the raw structure
    78  		// so that we can prune our attributes later.
    79  		seenKeys := make([]string, 0)
    80  
    81  		// Go through and find the added/changed keys in flatRaw
    82  		for k, v := range flatRaw {
    83  			// Find only the attributes that match our prefix
    84  			if !strings.HasPrefix(k, ak) {
    85  				continue
    86  			}
    87  
    88  			// Track that we saw this key
    89  			seenKeys = append(seenKeys, k)
    90  
    91  			// We keep track of this in case we have a pre-processor
    92  			// so that we can store the original value still.
    93  			originalV := v
    94  
    95  			// If this key is in the cleaned config, then use that value
    96  			// because it'll have its variables properly interpolated
    97  			if cleanV, ok := flatConfig[k]; ok {
    98  				v = cleanV
    99  				originalV = v
   100  
   101  				// If we have a pre-processor for this, run it.
   102  				if pp, ok := b.PreProcess[k]; ok {
   103  					v = pp(v)
   104  				}
   105  			}
   106  
   107  			oldV, ok := s.Attributes[k]
   108  
   109  			// If there is an old value and they're the same, no change
   110  			if ok && oldV == v {
   111  				continue
   112  			}
   113  
   114  			// Record the change
   115  			attrs[k] = &terraform.ResourceAttrDiff{
   116  				Old:      oldV,
   117  				New:      v,
   118  				NewExtra: originalV,
   119  				Type:     terraform.DiffAttrInput,
   120  			}
   121  
   122  			// If this requires a new resource, record that and flag our
   123  			// boolean.
   124  			if at == AttrTypeCreate {
   125  				attrs[k].RequiresNew = true
   126  				requiresNew = true
   127  			}
   128  		}
   129  
   130  		// Find all the keys that are in our attributes right now that
   131  		// we also care about.
   132  		matchingKeys := make(map[string]struct{})
   133  		for k, _ := range s.Attributes {
   134  			// Find only the attributes that match our prefix
   135  			if !strings.HasPrefix(k, ak) {
   136  				continue
   137  			}
   138  
   139  			// If this key is computed, then we don't ever delete it
   140  			comp := false
   141  			for _, ck := range b.ComputedAttrs {
   142  				if ck == k {
   143  					comp = true
   144  					break
   145  				}
   146  
   147  				// If the key is prefixed with the computed key, don't
   148  				// mark it for delete, ever.
   149  				if strings.HasPrefix(k, ck+".") {
   150  					comp = true
   151  					break
   152  				}
   153  			}
   154  			if comp {
   155  				continue
   156  			}
   157  
   158  			matchingKeys[k] = struct{}{}
   159  		}
   160  
   161  		// Delete the keys we saw in the configuration from the keys
   162  		// that are currently set.
   163  		for _, k := range seenKeys {
   164  			delete(matchingKeys, k)
   165  		}
   166  		for k, _ := range matchingKeys {
   167  			attrs[k] = &terraform.ResourceAttrDiff{
   168  				Old:        s.Attributes[k],
   169  				NewRemoved: true,
   170  				Type:       terraform.DiffAttrInput,
   171  			}
   172  		}
   173  	}
   174  
   175  	// If we require a new resource, then process all the attributes
   176  	// that will be changing due to the creation of the resource.
   177  	if requiresNew {
   178  		for _, k := range b.ComputedAttrs {
   179  			if _, ok := attrs[k]; ok {
   180  				continue
   181  			}
   182  
   183  			old := s.Attributes[k]
   184  			attrs[k] = &terraform.ResourceAttrDiff{
   185  				Old:         old,
   186  				NewComputed: true,
   187  				Type:        terraform.DiffAttrOutput,
   188  			}
   189  		}
   190  	}
   191  
   192  	// If we're changing anything, then mark the updated
   193  	// attributes.
   194  	if len(attrs) > 0 {
   195  		for _, k := range b.ComputedAttrsUpdate {
   196  			if _, ok := attrs[k]; ok {
   197  				continue
   198  			}
   199  
   200  			old := s.Attributes[k]
   201  			attrs[k] = &terraform.ResourceAttrDiff{
   202  				Old:         old,
   203  				NewComputed: true,
   204  				Type:        terraform.DiffAttrOutput,
   205  			}
   206  		}
   207  	}
   208  
   209  	// Build our resulting diff if we had attributes change
   210  	var result *terraform.InstanceDiff
   211  	if len(attrs) > 0 {
   212  		result = &terraform.InstanceDiff{
   213  			Attributes: attrs,
   214  		}
   215  	}
   216  
   217  	return result, nil
   218  }