github.com/arvindram03/terraform@v0.3.7-0.20150212015210-408f838db36d/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 26 // Don't set 27 multiReader *MultiLevelFieldReader 28 setWriter *MapFieldWriter 29 newState *terraform.InstanceState 30 partial bool 31 partialMap map[string]struct{} 32 once sync.Once 33 } 34 35 // getSource represents the level we want to get for a value (internally). 36 // Any source less than or equal to the level will be loaded (whichever 37 // has a value first). 38 type getSource byte 39 40 const ( 41 getSourceState getSource = 1 << iota 42 getSourceConfig 43 getSourceDiff 44 getSourceSet 45 getSourceExact // Only get from the _exact_ level 46 getSourceLevelMask getSource = getSourceState | getSourceConfig | getSourceDiff | getSourceSet 47 ) 48 49 // getResult is the internal structure that is generated when a Get 50 // is called that contains some extra data that might be used. 51 type getResult struct { 52 Value interface{} 53 ValueProcessed interface{} 54 Computed bool 55 Exists bool 56 Schema *Schema 57 } 58 59 var getResultEmpty getResult 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|getSourceExact) 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. 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 return r.Value, r.Exists && !r.Computed 94 } 95 96 func (d *ResourceData) getRaw(key string, level getSource) getResult { 97 var parts []string 98 if key != "" { 99 parts = strings.Split(key, ".") 100 } 101 102 return d.get(parts, level) 103 } 104 105 // HasChange returns whether or not the given key has been changed. 106 func (d *ResourceData) HasChange(key string) bool { 107 o, n := d.GetChange(key) 108 109 // If the type implements the Equal interface, then call that 110 // instead of just doing a reflect.DeepEqual. An example where this is 111 // needed is *Set 112 if eq, ok := o.(Equal); ok { 113 return !eq.Equal(n) 114 } 115 116 return !reflect.DeepEqual(o, n) 117 } 118 119 // Partial turns partial state mode on/off. 120 // 121 // When partial state mode is enabled, then only key prefixes specified 122 // by SetPartial will be in the final state. This allows providers to return 123 // partial states for partially applied resources (when errors occur). 124 func (d *ResourceData) Partial(on bool) { 125 d.partial = on 126 if on { 127 if d.partialMap == nil { 128 d.partialMap = make(map[string]struct{}) 129 } 130 } else { 131 d.partialMap = nil 132 } 133 } 134 135 // Set sets the value for the given key. 136 // 137 // If the key is invalid or the value is not a correct type, an error 138 // will be returned. 139 func (d *ResourceData) Set(key string, value interface{}) error { 140 d.once.Do(d.init) 141 return d.setWriter.WriteField(strings.Split(key, "."), value) 142 } 143 144 // SetPartial adds the key to the final state output while 145 // in partial state mode. The key must be a root key in the schema (i.e. 146 // it cannot be "list.0"). 147 // 148 // If partial state mode is disabled, then this has no effect. Additionally, 149 // whenever partial state mode is toggled, the partial data is cleared. 150 func (d *ResourceData) SetPartial(k string) { 151 if d.partial { 152 d.partialMap[k] = struct{}{} 153 } 154 } 155 156 // Id returns the ID of the resource. 157 func (d *ResourceData) Id() string { 158 var result string 159 160 if d.state != nil { 161 result = d.state.ID 162 } 163 164 if d.newState != nil { 165 result = d.newState.ID 166 } 167 168 return result 169 } 170 171 // ConnInfo returns the connection info for this resource. 172 func (d *ResourceData) ConnInfo() map[string]string { 173 if d.newState != nil { 174 return d.newState.Ephemeral.ConnInfo 175 } 176 177 if d.state != nil { 178 return d.state.Ephemeral.ConnInfo 179 } 180 181 return nil 182 } 183 184 // SetId sets the ID of the resource. If the value is blank, then the 185 // resource is destroyed. 186 func (d *ResourceData) SetId(v string) { 187 d.once.Do(d.init) 188 d.newState.ID = v 189 } 190 191 // SetConnInfo sets the connection info for a resource. 192 func (d *ResourceData) SetConnInfo(v map[string]string) { 193 d.once.Do(d.init) 194 d.newState.Ephemeral.ConnInfo = v 195 } 196 197 // State returns the new InstanceState after the diff and any Set 198 // calls. 199 func (d *ResourceData) State() *terraform.InstanceState { 200 var result terraform.InstanceState 201 result.ID = d.Id() 202 203 // If we have no ID, then this resource doesn't exist and we just 204 // return nil. 205 if result.ID == "" { 206 return nil 207 } 208 209 // In order to build the final state attributes, we read the full 210 // attribute set as a map[string]interface{}, write it to a MapFieldWriter, 211 // and then use that map. 212 rawMap := make(map[string]interface{}) 213 for k, _ := range d.schema { 214 source := getSourceSet 215 if d.partial { 216 source = getSourceState 217 if _, ok := d.partialMap[k]; ok { 218 source = getSourceSet 219 } 220 } 221 222 raw := d.get([]string{k}, source) 223 if raw.Exists && !raw.Computed { 224 rawMap[k] = raw.Value 225 if raw.ValueProcessed != nil { 226 rawMap[k] = raw.ValueProcessed 227 } 228 } 229 } 230 mapW := &MapFieldWriter{Schema: d.schema} 231 if err := mapW.WriteField(nil, rawMap); err != nil { 232 return nil 233 } 234 235 result.Attributes = mapW.Map() 236 result.Ephemeral.ConnInfo = d.ConnInfo() 237 238 // TODO: This is hacky and we can remove this when we have a proper 239 // state writer. We should instead have a proper StateFieldWriter 240 // and use that. 241 for k, schema := range d.schema { 242 if schema.Type != TypeMap { 243 continue 244 } 245 246 if result.Attributes[k] == "" { 247 delete(result.Attributes, k) 248 } 249 } 250 251 if v := d.Id(); v != "" { 252 result.Attributes["id"] = d.Id() 253 } 254 255 return &result 256 } 257 258 func (d *ResourceData) init() { 259 // Initialize the field that will store our new state 260 var copyState terraform.InstanceState 261 if d.state != nil { 262 copyState = *d.state 263 } 264 d.newState = ©State 265 266 // Initialize the map for storing set data 267 d.setWriter = &MapFieldWriter{Schema: d.schema} 268 269 // Initialize the reader for getting data from the 270 // underlying sources (config, diff, etc.) 271 readers := make(map[string]FieldReader) 272 var stateAttributes map[string]string 273 if d.state != nil { 274 stateAttributes = d.state.Attributes 275 readers["state"] = &MapFieldReader{ 276 Schema: d.schema, 277 Map: BasicMapReader(stateAttributes), 278 } 279 } 280 if d.config != nil { 281 readers["config"] = &ConfigFieldReader{ 282 Schema: d.schema, 283 Config: d.config, 284 } 285 } 286 if d.diff != nil { 287 readers["diff"] = &DiffFieldReader{ 288 Schema: d.schema, 289 Diff: d.diff, 290 Source: &MultiLevelFieldReader{ 291 Levels: []string{"state", "config"}, 292 Readers: readers, 293 }, 294 } 295 } 296 readers["set"] = &MapFieldReader{ 297 Schema: d.schema, 298 Map: BasicMapReader(d.setWriter.Map()), 299 } 300 d.multiReader = &MultiLevelFieldReader{ 301 Levels: []string{ 302 "state", 303 "config", 304 "diff", 305 "set", 306 }, 307 308 Readers: readers, 309 } 310 } 311 312 func (d *ResourceData) diffChange( 313 k string) (interface{}, interface{}, bool, bool) { 314 // Get the change between the state and the config. 315 o, n := d.getChange(k, getSourceState, getSourceConfig|getSourceExact) 316 if !o.Exists { 317 o.Value = nil 318 } 319 if !n.Exists { 320 n.Value = nil 321 } 322 323 // Return the old, new, and whether there is a change 324 return o.Value, n.Value, !reflect.DeepEqual(o.Value, n.Value), n.Computed 325 } 326 327 func (d *ResourceData) getChange( 328 key string, 329 oldLevel getSource, 330 newLevel getSource) (getResult, getResult) { 331 var parts, parts2 []string 332 if key != "" { 333 parts = strings.Split(key, ".") 334 parts2 = strings.Split(key, ".") 335 } 336 337 o := d.get(parts, oldLevel) 338 n := d.get(parts2, newLevel) 339 return o, n 340 } 341 342 func (d *ResourceData) get(addr []string, source getSource) getResult { 343 d.once.Do(d.init) 344 345 level := "set" 346 flags := source & ^getSourceLevelMask 347 exact := flags&getSourceExact != 0 348 source = source & getSourceLevelMask 349 if source >= getSourceSet { 350 level = "set" 351 } else if source >= getSourceDiff { 352 level = "diff" 353 } else if source >= getSourceConfig { 354 level = "config" 355 } else { 356 level = "state" 357 } 358 359 // Build the address of the key we're looking for and ask the FieldReader 360 for i, v := range addr { 361 if v[0] == '~' { 362 addr[i] = v[1:] 363 } 364 } 365 366 var result FieldReadResult 367 var err error 368 if exact { 369 result, err = d.multiReader.ReadFieldExact(addr, level) 370 } else { 371 result, err = d.multiReader.ReadFieldMerge(addr, level) 372 } 373 if err != nil { 374 panic(err) 375 } 376 377 // If the result doesn't exist, then we set the value to the zero value 378 if result.Value == nil { 379 if schemaL := addrToSchema(addr, d.schema); len(schemaL) > 0 { 380 schema := schemaL[len(schemaL)-1] 381 result.Value = result.ValueOrZero(schema) 382 } 383 } 384 385 // Transform the FieldReadResult into a getResult. It might be worth 386 // merging these two structures one day. 387 return getResult{ 388 Value: result.Value, 389 ValueProcessed: result.ValueProcessed, 390 Computed: result.Computed, 391 Exists: result.Exists, 392 } 393 }