github.com/cmalfait/terraform@v0.11.12-beta1/helper/schema/resource_diff.go (about) 1 package schema 2 3 import ( 4 "errors" 5 "fmt" 6 "reflect" 7 "strings" 8 "sync" 9 10 "github.com/hashicorp/terraform/terraform" 11 ) 12 13 // newValueWriter is a minor re-implementation of MapFieldWriter to include 14 // keys that should be marked as computed, to represent the new part of a 15 // pseudo-diff. 16 type newValueWriter struct { 17 *MapFieldWriter 18 19 // A list of keys that should be marked as computed. 20 computedKeys map[string]bool 21 22 // A lock to prevent races on writes. The underlying writer will have one as 23 // well - this is for computed keys. 24 lock sync.Mutex 25 26 // To be used with init. 27 once sync.Once 28 } 29 30 // init performs any initialization tasks for the newValueWriter. 31 func (w *newValueWriter) init() { 32 if w.computedKeys == nil { 33 w.computedKeys = make(map[string]bool) 34 } 35 } 36 37 // WriteField overrides MapValueWriter's WriteField, adding the ability to flag 38 // the address as computed. 39 func (w *newValueWriter) WriteField(address []string, value interface{}, computed bool) error { 40 // Fail the write if we have a non-nil value and computed is true. 41 // NewComputed values should not have a value when written. 42 if value != nil && computed { 43 return errors.New("Non-nil value with computed set") 44 } 45 46 if err := w.MapFieldWriter.WriteField(address, value); err != nil { 47 return err 48 } 49 50 w.once.Do(w.init) 51 52 w.lock.Lock() 53 defer w.lock.Unlock() 54 if computed { 55 w.computedKeys[strings.Join(address, ".")] = true 56 } 57 return nil 58 } 59 60 // ComputedKeysMap returns the underlying computed keys map. 61 func (w *newValueWriter) ComputedKeysMap() map[string]bool { 62 w.once.Do(w.init) 63 return w.computedKeys 64 } 65 66 // newValueReader is a minor re-implementation of MapFieldReader and is the 67 // read counterpart to MapValueWriter, allowing the read of keys flagged as 68 // computed to accommodate the diff override logic in ResourceDiff. 69 type newValueReader struct { 70 *MapFieldReader 71 72 // The list of computed keys from a newValueWriter. 73 computedKeys map[string]bool 74 } 75 76 // ReadField reads the values from the underlying writer, returning the 77 // computed value if it is found as well. 78 func (r *newValueReader) ReadField(address []string) (FieldReadResult, error) { 79 addrKey := strings.Join(address, ".") 80 v, err := r.MapFieldReader.ReadField(address) 81 if err != nil { 82 return FieldReadResult{}, err 83 } 84 for computedKey := range r.computedKeys { 85 if childAddrOf(addrKey, computedKey) { 86 if strings.HasSuffix(addrKey, ".#") { 87 // This is a count value for a list or set that has been marked as 88 // computed, or a sub-list/sub-set of a complex resource that has 89 // been marked as computed. We need to pass through to other readers 90 // so that an accurate previous count can be fetched for the diff. 91 v.Exists = false 92 } 93 v.Computed = true 94 } 95 } 96 97 return v, nil 98 } 99 100 // ResourceDiff is used to query and make custom changes to an in-flight diff. 101 // It can be used to veto particular changes in the diff, customize the diff 102 // that has been created, or diff values not controlled by config. 103 // 104 // The object functions similar to ResourceData, however most notably lacks 105 // Set, SetPartial, and Partial, as it should be used to change diff values 106 // only. Most other first-class ResourceData functions exist, namely Get, 107 // GetOk, HasChange, and GetChange exist. 108 // 109 // All functions in ResourceDiff, save for ForceNew, can only be used on 110 // computed fields. 111 type ResourceDiff struct { 112 // The schema for the resource being worked on. 113 schema map[string]*Schema 114 115 // The current config for this resource. 116 config *terraform.ResourceConfig 117 118 // The state for this resource as it exists post-refresh, after the initial 119 // diff. 120 state *terraform.InstanceState 121 122 // The diff created by Terraform. This diff is used, along with state, 123 // config, and custom-set diff data, to provide a multi-level reader 124 // experience similar to ResourceData. 125 diff *terraform.InstanceDiff 126 127 // The internal reader structure that contains the state, config, the default 128 // diff, and the new diff. 129 multiReader *MultiLevelFieldReader 130 131 // A writer that writes overridden new fields. 132 newWriter *newValueWriter 133 134 // Tracks which keys have been updated by ResourceDiff to ensure that the 135 // diff does not get re-run on keys that were not touched, or diffs that were 136 // just removed (re-running on the latter would just roll back the removal). 137 updatedKeys map[string]bool 138 139 // Tracks which keys were flagged as forceNew. These keys are not saved in 140 // newWriter, but we need to track them so that they can be re-diffed later. 141 forcedNewKeys map[string]bool 142 } 143 144 // newResourceDiff creates a new ResourceDiff instance. 145 func newResourceDiff(schema map[string]*Schema, config *terraform.ResourceConfig, state *terraform.InstanceState, diff *terraform.InstanceDiff) *ResourceDiff { 146 d := &ResourceDiff{ 147 config: config, 148 state: state, 149 diff: diff, 150 schema: schema, 151 } 152 153 d.newWriter = &newValueWriter{ 154 MapFieldWriter: &MapFieldWriter{Schema: d.schema}, 155 } 156 readers := make(map[string]FieldReader) 157 var stateAttributes map[string]string 158 if d.state != nil { 159 stateAttributes = d.state.Attributes 160 readers["state"] = &MapFieldReader{ 161 Schema: d.schema, 162 Map: BasicMapReader(stateAttributes), 163 } 164 } 165 if d.config != nil { 166 readers["config"] = &ConfigFieldReader{ 167 Schema: d.schema, 168 Config: d.config, 169 } 170 } 171 if d.diff != nil { 172 readers["diff"] = &DiffFieldReader{ 173 Schema: d.schema, 174 Diff: d.diff, 175 Source: &MultiLevelFieldReader{ 176 Levels: []string{"state", "config"}, 177 Readers: readers, 178 }, 179 } 180 } 181 readers["newDiff"] = &newValueReader{ 182 MapFieldReader: &MapFieldReader{ 183 Schema: d.schema, 184 Map: BasicMapReader(d.newWriter.Map()), 185 }, 186 computedKeys: d.newWriter.ComputedKeysMap(), 187 } 188 d.multiReader = &MultiLevelFieldReader{ 189 Levels: []string{ 190 "state", 191 "config", 192 "diff", 193 "newDiff", 194 }, 195 196 Readers: readers, 197 } 198 199 d.updatedKeys = make(map[string]bool) 200 d.forcedNewKeys = make(map[string]bool) 201 202 return d 203 } 204 205 // UpdatedKeys returns the keys that were updated by this ResourceDiff run. 206 // These are the only keys that a diff should be re-calculated for. 207 // 208 // This is the combined result of both keys for which diff values were updated 209 // for or cleared, and also keys that were flagged to be re-diffed as a result 210 // of ForceNew. 211 func (d *ResourceDiff) UpdatedKeys() []string { 212 var s []string 213 for k := range d.updatedKeys { 214 s = append(s, k) 215 } 216 for k := range d.forcedNewKeys { 217 for _, l := range s { 218 if k == l { 219 break 220 } 221 } 222 s = append(s, k) 223 } 224 return s 225 } 226 227 // Clear wipes the diff for a particular key. It is called by ResourceDiff's 228 // functionality to remove any possibility of conflicts, but can be called on 229 // its own to just remove a specific key from the diff completely. 230 // 231 // Note that this does not wipe an override. This function is only allowed on 232 // computed keys. 233 func (d *ResourceDiff) Clear(key string) error { 234 if err := d.checkKey(key, "Clear", true); err != nil { 235 return err 236 } 237 238 return d.clear(key) 239 } 240 241 func (d *ResourceDiff) clear(key string) error { 242 // Check the schema to make sure that this key exists first. 243 schemaL := addrToSchema(strings.Split(key, "."), d.schema) 244 if len(schemaL) == 0 { 245 return fmt.Errorf("%s is not a valid key", key) 246 } 247 248 for k := range d.diff.Attributes { 249 if strings.HasPrefix(k, key) { 250 delete(d.diff.Attributes, k) 251 } 252 } 253 return nil 254 } 255 256 // GetChangedKeysPrefix helps to implement Resource.CustomizeDiff 257 // where we need to act on all nested fields 258 // without calling out each one separately 259 func (d *ResourceDiff) GetChangedKeysPrefix(prefix string) []string { 260 keys := make([]string, 0) 261 for k := range d.diff.Attributes { 262 if strings.HasPrefix(k, prefix) { 263 keys = append(keys, k) 264 } 265 } 266 return keys 267 } 268 269 // diffChange helps to implement resourceDiffer and derives its change values 270 // from ResourceDiff's own change data, in addition to existing diff, config, and state. 271 func (d *ResourceDiff) diffChange(key string) (interface{}, interface{}, bool, bool, bool) { 272 old, new, customized := d.getChange(key) 273 274 if !old.Exists { 275 old.Value = nil 276 } 277 if !new.Exists || d.removed(key) { 278 new.Value = nil 279 } 280 281 return old.Value, new.Value, !reflect.DeepEqual(old.Value, new.Value), new.Computed, customized 282 } 283 284 // SetNew is used to set a new diff value for the mentioned key. The value must 285 // be correct for the attribute's schema (mostly relevant for maps, lists, and 286 // sets). The original value from the state is used as the old value. 287 // 288 // This function is only allowed on computed attributes. 289 func (d *ResourceDiff) SetNew(key string, value interface{}) error { 290 if err := d.checkKey(key, "SetNew", false); err != nil { 291 return err 292 } 293 294 return d.setDiff(key, value, false) 295 } 296 297 // SetNewComputed functions like SetNew, except that it blanks out a new value 298 // and marks it as computed. 299 // 300 // This function is only allowed on computed attributes. 301 func (d *ResourceDiff) SetNewComputed(key string) error { 302 if err := d.checkKey(key, "SetNewComputed", false); err != nil { 303 return err 304 } 305 306 return d.setDiff(key, nil, true) 307 } 308 309 // setDiff performs common diff setting behaviour. 310 func (d *ResourceDiff) setDiff(key string, new interface{}, computed bool) error { 311 if err := d.clear(key); err != nil { 312 return err 313 } 314 315 if err := d.newWriter.WriteField(strings.Split(key, "."), new, computed); err != nil { 316 return fmt.Errorf("Cannot set new diff value for key %s: %s", key, err) 317 } 318 319 d.updatedKeys[key] = true 320 321 return nil 322 } 323 324 // ForceNew force-flags ForceNew in the schema for a specific key, and 325 // re-calculates its diff, effectively causing this attribute to force a new 326 // resource. 327 // 328 // Keep in mind that forcing a new resource will force a second run of the 329 // resource's CustomizeDiff function (with a new ResourceDiff) once the current 330 // one has completed. This second run is performed without state. This behavior 331 // will be the same as if a new resource is being created and is performed to 332 // ensure that the diff looks like the diff for a new resource as much as 333 // possible. CustomizeDiff should expect such a scenario and act correctly. 334 // 335 // This function is a no-op/error if there is no diff. 336 // 337 // Note that the change to schema is permanent for the lifecycle of this 338 // specific ResourceDiff instance. 339 func (d *ResourceDiff) ForceNew(key string) error { 340 if !d.HasChange(key) { 341 return fmt.Errorf("ForceNew: No changes for %s", key) 342 } 343 344 keyParts := strings.Split(key, ".") 345 var schema *Schema 346 schemaL := addrToSchema(keyParts, d.schema) 347 if len(schemaL) > 0 { 348 schema = schemaL[len(schemaL)-1] 349 } else { 350 return fmt.Errorf("ForceNew: %s is not a valid key", key) 351 } 352 353 schema.ForceNew = true 354 355 // Flag this for a re-diff. Don't save any values to guarantee that existing 356 // diffs aren't messed with, as this gets messy when dealing with complex 357 // structures, zero values, etc. 358 d.forcedNewKeys[keyParts[0]] = true 359 360 return nil 361 } 362 363 // Get hands off to ResourceData.Get. 364 func (d *ResourceDiff) Get(key string) interface{} { 365 r, _ := d.GetOk(key) 366 return r 367 } 368 369 // GetChange gets the change between the state and diff, checking first to see 370 // if a overridden diff exists. 371 // 372 // This implementation differs from ResourceData's in the way that we first get 373 // results from the exact levels for the new diff, then from state and diff as 374 // per normal. 375 func (d *ResourceDiff) GetChange(key string) (interface{}, interface{}) { 376 old, new, _ := d.getChange(key) 377 return old.Value, new.Value 378 } 379 380 // GetOk functions the same way as ResourceData.GetOk, but it also checks the 381 // new diff levels to provide data consistent with the current state of the 382 // customized diff. 383 func (d *ResourceDiff) GetOk(key string) (interface{}, bool) { 384 r := d.get(strings.Split(key, "."), "newDiff") 385 exists := r.Exists && !r.Computed 386 if exists { 387 // If it exists, we also want to verify it is not the zero-value. 388 value := r.Value 389 zero := r.Schema.Type.Zero() 390 391 if eq, ok := value.(Equal); ok { 392 exists = !eq.Equal(zero) 393 } else { 394 exists = !reflect.DeepEqual(value, zero) 395 } 396 } 397 398 return r.Value, exists 399 } 400 401 // GetOkExists functions the same way as GetOkExists within ResourceData, but 402 // it also checks the new diff levels to provide data consistent with the 403 // current state of the customized diff. 404 // 405 // This is nearly the same function as GetOk, yet it does not check 406 // for the zero value of the attribute's type. This allows for attributes 407 // without a default, to fully check for a literal assignment, regardless 408 // of the zero-value for that type. 409 func (d *ResourceDiff) GetOkExists(key string) (interface{}, bool) { 410 r := d.get(strings.Split(key, "."), "newDiff") 411 exists := r.Exists && !r.Computed 412 return r.Value, exists 413 } 414 415 // NewValueKnown returns true if the new value for the given key is available 416 // as its final value at diff time. If the return value is false, this means 417 // either the value is based of interpolation that was unavailable at diff 418 // time, or that the value was explicitly marked as computed by SetNewComputed. 419 func (d *ResourceDiff) NewValueKnown(key string) bool { 420 r := d.get(strings.Split(key, "."), "newDiff") 421 return !r.Computed 422 } 423 424 // HasChange checks to see if there is a change between state and the diff, or 425 // in the overridden diff. 426 func (d *ResourceDiff) HasChange(key string) bool { 427 old, new := d.GetChange(key) 428 429 // If the type implements the Equal interface, then call that 430 // instead of just doing a reflect.DeepEqual. An example where this is 431 // needed is *Set 432 if eq, ok := old.(Equal); ok { 433 return !eq.Equal(new) 434 } 435 436 return !reflect.DeepEqual(old, new) 437 } 438 439 // Id returns the ID of this resource. 440 // 441 // Note that technically, ID does not change during diffs (it either has 442 // already changed in the refresh, or will change on update), hence we do not 443 // support updating the ID or fetching it from anything else other than state. 444 func (d *ResourceDiff) Id() string { 445 var result string 446 447 if d.state != nil { 448 result = d.state.ID 449 } 450 return result 451 } 452 453 // getChange gets values from two different levels, designed for use in 454 // diffChange, HasChange, and GetChange. 455 // 456 // This implementation differs from ResourceData's in the way that we first get 457 // results from the exact levels for the new diff, then from state and diff as 458 // per normal. 459 func (d *ResourceDiff) getChange(key string) (getResult, getResult, bool) { 460 old := d.get(strings.Split(key, "."), "state") 461 var new getResult 462 for p := range d.updatedKeys { 463 if childAddrOf(key, p) { 464 new = d.getExact(strings.Split(key, "."), "newDiff") 465 return old, new, true 466 } 467 } 468 new = d.get(strings.Split(key, "."), "newDiff") 469 return old, new, false 470 } 471 472 // removed checks to see if the key is present in the existing, pre-customized 473 // diff and if it was marked as NewRemoved. 474 func (d *ResourceDiff) removed(k string) bool { 475 diff, ok := d.diff.Attributes[k] 476 if !ok { 477 return false 478 } 479 return diff.NewRemoved 480 } 481 482 // get performs the appropriate multi-level reader logic for ResourceDiff, 483 // starting at source. Refer to newResourceDiff for the level order. 484 func (d *ResourceDiff) get(addr []string, source string) getResult { 485 result, err := d.multiReader.ReadFieldMerge(addr, source) 486 if err != nil { 487 panic(err) 488 } 489 490 return d.finalizeResult(addr, result) 491 } 492 493 // getExact gets an attribute from the exact level referenced by source. 494 func (d *ResourceDiff) getExact(addr []string, source string) getResult { 495 result, err := d.multiReader.ReadFieldExact(addr, source) 496 if err != nil { 497 panic(err) 498 } 499 500 return d.finalizeResult(addr, result) 501 } 502 503 // finalizeResult does some post-processing of the result produced by get and getExact. 504 func (d *ResourceDiff) finalizeResult(addr []string, result FieldReadResult) getResult { 505 // If the result doesn't exist, then we set the value to the zero value 506 var schema *Schema 507 if schemaL := addrToSchema(addr, d.schema); len(schemaL) > 0 { 508 schema = schemaL[len(schemaL)-1] 509 } 510 511 if result.Value == nil && schema != nil { 512 result.Value = result.ValueOrZero(schema) 513 } 514 515 // Transform the FieldReadResult into a getResult. It might be worth 516 // merging these two structures one day. 517 return getResult{ 518 Value: result.Value, 519 ValueProcessed: result.ValueProcessed, 520 Computed: result.Computed, 521 Exists: result.Exists, 522 Schema: schema, 523 } 524 } 525 526 // childAddrOf does a comparison of two addresses to see if one is the child of 527 // the other. 528 func childAddrOf(child, parent string) bool { 529 cs := strings.Split(child, ".") 530 ps := strings.Split(parent, ".") 531 if len(ps) > len(cs) { 532 return false 533 } 534 return reflect.DeepEqual(ps, cs[:len(ps)]) 535 } 536 537 // checkKey checks the key to make sure it exists and is computed. 538 func (d *ResourceDiff) checkKey(key, caller string, nested bool) error { 539 var schema *Schema 540 if nested { 541 keyParts := strings.Split(key, ".") 542 schemaL := addrToSchema(keyParts, d.schema) 543 if len(schemaL) > 0 { 544 schema = schemaL[len(schemaL)-1] 545 } 546 } else { 547 s, ok := d.schema[key] 548 if ok { 549 schema = s 550 } 551 } 552 if schema == nil { 553 return fmt.Errorf("%s: invalid key: %s", caller, key) 554 } 555 if !schema.Computed { 556 return fmt.Errorf("%s only operates on computed keys - %s is not one", caller, key) 557 } 558 return nil 559 }