github.com/jpedro/terraform@v0.11.12-beta1/helper/schema/resource_data.go (about) 1 package schema 2 3 import ( 4 "log" 5 "reflect" 6 "strings" 7 "sync" 8 "time" 9 10 "github.com/hashicorp/terraform/terraform" 11 ) 12 13 // ResourceData is used to query and set the attributes of a resource. 14 // 15 // ResourceData is the primary argument received for CRUD operations on 16 // a resource as well as configuration of a provider. It is a powerful 17 // structure that can be used to not only query data, but check for changes, 18 // define partial state updates, etc. 19 // 20 // The most relevant methods to take a look at are Get, Set, and Partial. 21 type ResourceData struct { 22 // Settable (internally) 23 schema map[string]*Schema 24 config *terraform.ResourceConfig 25 state *terraform.InstanceState 26 diff *terraform.InstanceDiff 27 meta map[string]interface{} 28 timeouts *ResourceTimeout 29 30 // Don't set 31 multiReader *MultiLevelFieldReader 32 setWriter *MapFieldWriter 33 newState *terraform.InstanceState 34 partial bool 35 partialMap map[string]struct{} 36 once sync.Once 37 isNew bool 38 39 panicOnError bool 40 } 41 42 // getResult is the internal structure that is generated when a Get 43 // is called that contains some extra data that might be used. 44 type getResult struct { 45 Value interface{} 46 ValueProcessed interface{} 47 Computed bool 48 Exists bool 49 Schema *Schema 50 } 51 52 // UnsafeSetFieldRaw allows setting arbitrary values in state to arbitrary 53 // values, bypassing schema. This MUST NOT be used in normal circumstances - 54 // it exists only to support the remote_state data source. 55 func (d *ResourceData) UnsafeSetFieldRaw(key string, value string) { 56 d.once.Do(d.init) 57 58 d.setWriter.unsafeWriteField(key, value) 59 } 60 61 // Get returns the data for the given key, or nil if the key doesn't exist 62 // in the schema. 63 // 64 // If the key does exist in the schema but doesn't exist in the configuration, 65 // then the default value for that type will be returned. For strings, this is 66 // "", for numbers it is 0, etc. 67 // 68 // If you want to test if something is set at all in the configuration, 69 // use GetOk. 70 func (d *ResourceData) Get(key string) interface{} { 71 v, _ := d.GetOk(key) 72 return v 73 } 74 75 // GetChange returns the old and new value for a given key. 76 // 77 // HasChange should be used to check if a change exists. It is possible 78 // that both the old and new value are the same if the old value was not 79 // set and the new value is. This is common, for example, for boolean 80 // fields which have a zero value of false. 81 func (d *ResourceData) GetChange(key string) (interface{}, interface{}) { 82 o, n := d.getChange(key, getSourceState, getSourceDiff) 83 return o.Value, n.Value 84 } 85 86 // GetOk returns the data for the given key and whether or not the key 87 // has been set to a non-zero value at some point. 88 // 89 // The first result will not necessarilly be nil if the value doesn't exist. 90 // The second result should be checked to determine this information. 91 func (d *ResourceData) GetOk(key string) (interface{}, bool) { 92 r := d.getRaw(key, getSourceSet) 93 exists := r.Exists && !r.Computed 94 if exists { 95 // If it exists, we also want to verify it is not the zero-value. 96 value := r.Value 97 zero := r.Schema.Type.Zero() 98 99 if eq, ok := value.(Equal); ok { 100 exists = !eq.Equal(zero) 101 } else { 102 exists = !reflect.DeepEqual(value, zero) 103 } 104 } 105 106 return r.Value, exists 107 } 108 109 // GetOkExists returns the data for a given key and whether or not the key 110 // has been set to a non-zero value. This is only useful for determining 111 // if boolean attributes have been set, if they are Optional but do not 112 // have a Default value. 113 // 114 // This is nearly the same function as GetOk, yet it does not check 115 // for the zero value of the attribute's type. This allows for attributes 116 // without a default, to fully check for a literal assignment, regardless 117 // of the zero-value for that type. 118 // This should only be used if absolutely required/needed. 119 func (d *ResourceData) GetOkExists(key string) (interface{}, bool) { 120 r := d.getRaw(key, getSourceSet) 121 exists := r.Exists && !r.Computed 122 return r.Value, exists 123 } 124 125 func (d *ResourceData) getRaw(key string, level getSource) getResult { 126 var parts []string 127 if key != "" { 128 parts = strings.Split(key, ".") 129 } 130 131 return d.get(parts, level) 132 } 133 134 // HasChange returns whether or not the given key has been changed. 135 func (d *ResourceData) HasChange(key string) bool { 136 o, n := d.GetChange(key) 137 138 // If the type implements the Equal interface, then call that 139 // instead of just doing a reflect.DeepEqual. An example where this is 140 // needed is *Set 141 if eq, ok := o.(Equal); ok { 142 return !eq.Equal(n) 143 } 144 145 return !reflect.DeepEqual(o, n) 146 } 147 148 // Partial turns partial state mode on/off. 149 // 150 // When partial state mode is enabled, then only key prefixes specified 151 // by SetPartial will be in the final state. This allows providers to return 152 // partial states for partially applied resources (when errors occur). 153 func (d *ResourceData) Partial(on bool) { 154 d.partial = on 155 if on { 156 if d.partialMap == nil { 157 d.partialMap = make(map[string]struct{}) 158 } 159 } else { 160 d.partialMap = nil 161 } 162 } 163 164 // Set sets the value for the given key. 165 // 166 // If the key is invalid or the value is not a correct type, an error 167 // will be returned. 168 func (d *ResourceData) Set(key string, value interface{}) error { 169 d.once.Do(d.init) 170 171 // If the value is a pointer to a non-struct, get its value and 172 // use that. This allows Set to take a pointer to primitives to 173 // simplify the interface. 174 reflectVal := reflect.ValueOf(value) 175 if reflectVal.Kind() == reflect.Ptr { 176 if reflectVal.IsNil() { 177 // If the pointer is nil, then the value is just nil 178 value = nil 179 } else { 180 // Otherwise, we dereference the pointer as long as its not 181 // a pointer to a struct, since struct pointers are allowed. 182 reflectVal = reflect.Indirect(reflectVal) 183 if reflectVal.Kind() != reflect.Struct { 184 value = reflectVal.Interface() 185 } 186 } 187 } 188 189 err := d.setWriter.WriteField(strings.Split(key, "."), value) 190 if err != nil && d.panicOnError { 191 panic(err) 192 } 193 return err 194 } 195 196 // SetPartial adds the key to the final state output while 197 // in partial state mode. The key must be a root key in the schema (i.e. 198 // it cannot be "list.0"). 199 // 200 // If partial state mode is disabled, then this has no effect. Additionally, 201 // whenever partial state mode is toggled, the partial data is cleared. 202 func (d *ResourceData) SetPartial(k string) { 203 if d.partial { 204 d.partialMap[k] = struct{}{} 205 } 206 } 207 208 func (d *ResourceData) MarkNewResource() { 209 d.isNew = true 210 } 211 212 func (d *ResourceData) IsNewResource() bool { 213 return d.isNew 214 } 215 216 // Id returns the ID of the resource. 217 func (d *ResourceData) Id() string { 218 var result string 219 220 if d.state != nil { 221 result = d.state.ID 222 } 223 224 if d.newState != nil { 225 result = d.newState.ID 226 } 227 228 return result 229 } 230 231 // ConnInfo returns the connection info for this resource. 232 func (d *ResourceData) ConnInfo() map[string]string { 233 if d.newState != nil { 234 return d.newState.Ephemeral.ConnInfo 235 } 236 237 if d.state != nil { 238 return d.state.Ephemeral.ConnInfo 239 } 240 241 return nil 242 } 243 244 // SetId sets the ID of the resource. If the value is blank, then the 245 // resource is destroyed. 246 func (d *ResourceData) SetId(v string) { 247 d.once.Do(d.init) 248 d.newState.ID = v 249 } 250 251 // SetConnInfo sets the connection info for a resource. 252 func (d *ResourceData) SetConnInfo(v map[string]string) { 253 d.once.Do(d.init) 254 d.newState.Ephemeral.ConnInfo = v 255 } 256 257 // SetType sets the ephemeral type for the data. This is only required 258 // for importing. 259 func (d *ResourceData) SetType(t string) { 260 d.once.Do(d.init) 261 d.newState.Ephemeral.Type = t 262 } 263 264 // State returns the new InstanceState after the diff and any Set 265 // calls. 266 func (d *ResourceData) State() *terraform.InstanceState { 267 var result terraform.InstanceState 268 result.ID = d.Id() 269 result.Meta = d.meta 270 271 // If we have no ID, then this resource doesn't exist and we just 272 // return nil. 273 if result.ID == "" { 274 return nil 275 } 276 277 if d.timeouts != nil { 278 if err := d.timeouts.StateEncode(&result); err != nil { 279 log.Printf("[ERR] Error encoding Timeout meta to Instance State: %s", err) 280 } 281 } 282 283 // Look for a magic key in the schema that determines we skip the 284 // integrity check of fields existing in the schema, allowing dynamic 285 // keys to be created. 286 hasDynamicAttributes := false 287 for k, _ := range d.schema { 288 if k == "__has_dynamic_attributes" { 289 hasDynamicAttributes = true 290 log.Printf("[INFO] Resource %s has dynamic attributes", result.ID) 291 } 292 } 293 294 // In order to build the final state attributes, we read the full 295 // attribute set as a map[string]interface{}, write it to a MapFieldWriter, 296 // and then use that map. 297 rawMap := make(map[string]interface{}) 298 for k := range d.schema { 299 source := getSourceSet 300 if d.partial { 301 source = getSourceState 302 if _, ok := d.partialMap[k]; ok { 303 source = getSourceSet 304 } 305 } 306 307 raw := d.get([]string{k}, source) 308 if raw.Exists && !raw.Computed { 309 rawMap[k] = raw.Value 310 if raw.ValueProcessed != nil { 311 rawMap[k] = raw.ValueProcessed 312 } 313 } 314 } 315 316 mapW := &MapFieldWriter{Schema: d.schema} 317 if err := mapW.WriteField(nil, rawMap); err != nil { 318 log.Printf("[ERR] Error writing fields: %s", err) 319 return nil 320 } 321 322 result.Attributes = mapW.Map() 323 324 if hasDynamicAttributes { 325 // If we have dynamic attributes, just copy the attributes map 326 // one for one into the result attributes. 327 for k, v := range d.setWriter.Map() { 328 // Don't clobber schema values. This limits usage of dynamic 329 // attributes to names which _do not_ conflict with schema 330 // keys! 331 if _, ok := result.Attributes[k]; !ok { 332 result.Attributes[k] = v 333 } 334 } 335 } 336 337 if d.newState != nil { 338 result.Ephemeral = d.newState.Ephemeral 339 } 340 341 // TODO: This is hacky and we can remove this when we have a proper 342 // state writer. We should instead have a proper StateFieldWriter 343 // and use that. 344 for k, schema := range d.schema { 345 if schema.Type != TypeMap { 346 continue 347 } 348 349 if result.Attributes[k] == "" { 350 delete(result.Attributes, k) 351 } 352 } 353 354 if v := d.Id(); v != "" { 355 result.Attributes["id"] = d.Id() 356 } 357 358 if d.state != nil { 359 result.Tainted = d.state.Tainted 360 } 361 362 return &result 363 } 364 365 // Timeout returns the data for the given timeout key 366 // Returns a duration of 20 minutes for any key not found, or not found and no default. 367 func (d *ResourceData) Timeout(key string) time.Duration { 368 key = strings.ToLower(key) 369 370 // System default of 20 minutes 371 defaultTimeout := 20 * time.Minute 372 373 if d.timeouts == nil { 374 return defaultTimeout 375 } 376 377 var timeout *time.Duration 378 switch key { 379 case TimeoutCreate: 380 timeout = d.timeouts.Create 381 case TimeoutRead: 382 timeout = d.timeouts.Read 383 case TimeoutUpdate: 384 timeout = d.timeouts.Update 385 case TimeoutDelete: 386 timeout = d.timeouts.Delete 387 } 388 389 if timeout != nil { 390 return *timeout 391 } 392 393 if d.timeouts.Default != nil { 394 return *d.timeouts.Default 395 } 396 397 return defaultTimeout 398 } 399 400 func (d *ResourceData) init() { 401 // Initialize the field that will store our new state 402 var copyState terraform.InstanceState 403 if d.state != nil { 404 copyState = *d.state.DeepCopy() 405 } 406 d.newState = ©State 407 408 // Initialize the map for storing set data 409 d.setWriter = &MapFieldWriter{Schema: d.schema} 410 411 // Initialize the reader for getting data from the 412 // underlying sources (config, diff, etc.) 413 readers := make(map[string]FieldReader) 414 var stateAttributes map[string]string 415 if d.state != nil { 416 stateAttributes = d.state.Attributes 417 readers["state"] = &MapFieldReader{ 418 Schema: d.schema, 419 Map: BasicMapReader(stateAttributes), 420 } 421 } 422 if d.config != nil { 423 readers["config"] = &ConfigFieldReader{ 424 Schema: d.schema, 425 Config: d.config, 426 } 427 } 428 if d.diff != nil { 429 readers["diff"] = &DiffFieldReader{ 430 Schema: d.schema, 431 Diff: d.diff, 432 Source: &MultiLevelFieldReader{ 433 Levels: []string{"state", "config"}, 434 Readers: readers, 435 }, 436 } 437 } 438 readers["set"] = &MapFieldReader{ 439 Schema: d.schema, 440 Map: BasicMapReader(d.setWriter.Map()), 441 } 442 d.multiReader = &MultiLevelFieldReader{ 443 Levels: []string{ 444 "state", 445 "config", 446 "diff", 447 "set", 448 }, 449 450 Readers: readers, 451 } 452 } 453 454 func (d *ResourceData) diffChange( 455 k string) (interface{}, interface{}, bool, bool, bool) { 456 // Get the change between the state and the config. 457 o, n := d.getChange(k, getSourceState, getSourceConfig|getSourceExact) 458 if !o.Exists { 459 o.Value = nil 460 } 461 if !n.Exists { 462 n.Value = nil 463 } 464 465 // Return the old, new, and whether there is a change 466 return o.Value, n.Value, !reflect.DeepEqual(o.Value, n.Value), n.Computed, false 467 } 468 469 func (d *ResourceData) getChange( 470 k string, 471 oldLevel getSource, 472 newLevel getSource) (getResult, getResult) { 473 var parts, parts2 []string 474 if k != "" { 475 parts = strings.Split(k, ".") 476 parts2 = strings.Split(k, ".") 477 } 478 479 o := d.get(parts, oldLevel) 480 n := d.get(parts2, newLevel) 481 return o, n 482 } 483 484 func (d *ResourceData) get(addr []string, source getSource) getResult { 485 d.once.Do(d.init) 486 487 level := "set" 488 flags := source & ^getSourceLevelMask 489 exact := flags&getSourceExact != 0 490 source = source & getSourceLevelMask 491 if source >= getSourceSet { 492 level = "set" 493 } else if source >= getSourceDiff { 494 level = "diff" 495 } else if source >= getSourceConfig { 496 level = "config" 497 } else { 498 level = "state" 499 } 500 501 var result FieldReadResult 502 var err error 503 if exact { 504 result, err = d.multiReader.ReadFieldExact(addr, level) 505 } else { 506 result, err = d.multiReader.ReadFieldMerge(addr, level) 507 } 508 if err != nil { 509 panic(err) 510 } 511 512 // If the result doesn't exist, then we set the value to the zero value 513 var schema *Schema 514 if schemaL := addrToSchema(addr, d.schema); len(schemaL) > 0 { 515 schema = schemaL[len(schemaL)-1] 516 } 517 518 if result.Value == nil && schema != nil { 519 result.Value = result.ValueOrZero(schema) 520 } 521 522 // Transform the FieldReadResult into a getResult. It might be worth 523 // merging these two structures one day. 524 return getResult{ 525 Value: result.Value, 526 ValueProcessed: result.ValueProcessed, 527 Computed: result.Computed, 528 Exists: result.Exists, 529 Schema: schema, 530 } 531 }