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