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