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