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 }