github.com/sarguru/terraform@v0.6.17-0.20160525232901-8fcdfd7e3dc9/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 return &result 294 } 295 296 func (d *ResourceData) init() { 297 // Initialize the field that will store our new state 298 var copyState terraform.InstanceState 299 if d.state != nil { 300 copyState = *d.state.DeepCopy() 301 } 302 d.newState = ©State 303 304 // Initialize the map for storing set data 305 d.setWriter = &MapFieldWriter{Schema: d.schema} 306 307 // Initialize the reader for getting data from the 308 // underlying sources (config, diff, etc.) 309 readers := make(map[string]FieldReader) 310 var stateAttributes map[string]string 311 if d.state != nil { 312 stateAttributes = d.state.Attributes 313 readers["state"] = &MapFieldReader{ 314 Schema: d.schema, 315 Map: BasicMapReader(stateAttributes), 316 } 317 } 318 if d.config != nil { 319 readers["config"] = &ConfigFieldReader{ 320 Schema: d.schema, 321 Config: d.config, 322 } 323 } 324 if d.diff != nil { 325 readers["diff"] = &DiffFieldReader{ 326 Schema: d.schema, 327 Diff: d.diff, 328 Source: &MultiLevelFieldReader{ 329 Levels: []string{"state", "config"}, 330 Readers: readers, 331 }, 332 } 333 } 334 readers["set"] = &MapFieldReader{ 335 Schema: d.schema, 336 Map: BasicMapReader(d.setWriter.Map()), 337 } 338 d.multiReader = &MultiLevelFieldReader{ 339 Levels: []string{ 340 "state", 341 "config", 342 "diff", 343 "set", 344 }, 345 346 Readers: readers, 347 } 348 } 349 350 func (d *ResourceData) diffChange( 351 k string) (interface{}, interface{}, bool, bool) { 352 // Get the change between the state and the config. 353 o, n := d.getChange(k, getSourceState, getSourceConfig|getSourceExact) 354 if !o.Exists { 355 o.Value = nil 356 } 357 if !n.Exists { 358 n.Value = nil 359 } 360 361 // Return the old, new, and whether there is a change 362 return o.Value, n.Value, !reflect.DeepEqual(o.Value, n.Value), n.Computed 363 } 364 365 func (d *ResourceData) getChange( 366 k string, 367 oldLevel getSource, 368 newLevel getSource) (getResult, getResult) { 369 var parts, parts2 []string 370 if k != "" { 371 parts = strings.Split(k, ".") 372 parts2 = strings.Split(k, ".") 373 } 374 375 o := d.get(parts, oldLevel) 376 n := d.get(parts2, newLevel) 377 return o, n 378 } 379 380 func (d *ResourceData) get(addr []string, source getSource) getResult { 381 d.once.Do(d.init) 382 383 level := "set" 384 flags := source & ^getSourceLevelMask 385 exact := flags&getSourceExact != 0 386 source = source & getSourceLevelMask 387 if source >= getSourceSet { 388 level = "set" 389 } else if source >= getSourceDiff { 390 level = "diff" 391 } else if source >= getSourceConfig { 392 level = "config" 393 } else { 394 level = "state" 395 } 396 397 var result FieldReadResult 398 var err error 399 if exact { 400 result, err = d.multiReader.ReadFieldExact(addr, level) 401 } else { 402 result, err = d.multiReader.ReadFieldMerge(addr, level) 403 } 404 if err != nil { 405 panic(err) 406 } 407 408 // If the result doesn't exist, then we set the value to the zero value 409 var schema *Schema 410 if schemaL := addrToSchema(addr, d.schema); len(schemaL) > 0 { 411 schema = schemaL[len(schemaL)-1] 412 } 413 414 if result.Value == nil && schema != nil { 415 result.Value = result.ValueOrZero(schema) 416 } 417 418 // Transform the FieldReadResult into a getResult. It might be worth 419 // merging these two structures one day. 420 return getResult{ 421 Value: result.Value, 422 ValueProcessed: result.ValueProcessed, 423 Computed: result.Computed, 424 Exists: result.Exists, 425 Schema: schema, 426 } 427 }