github.com/jrperritt/terraform@v0.1.1-0.20170525065507-96f391dafc38/helper/diff/resource_builder.go (about)

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