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