github.com/hashicorp/terraform-plugin-sdk@v1.17.2/terraform/resource.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "reflect" 6 "sort" 7 "strconv" 8 "strings" 9 10 "github.com/mitchellh/copystructure" 11 "github.com/mitchellh/reflectwalk" 12 "github.com/zclconf/go-cty/cty" 13 14 "github.com/hashicorp/terraform-plugin-sdk/internal/addrs" 15 "github.com/hashicorp/terraform-plugin-sdk/internal/configs/configschema" 16 "github.com/hashicorp/terraform-plugin-sdk/internal/configs/hcl2shim" 17 ) 18 19 // Resource is a legacy way to identify a particular resource instance. 20 // 21 // New code should use addrs.ResourceInstance instead. This is still here 22 // only for codepaths that haven't been updated yet. 23 type Resource struct { 24 // These are all used by the new EvalNode stuff. 25 Name string 26 Type string 27 CountIndex int 28 29 // These aren't really used anymore anywhere, but we keep them around 30 // since we haven't done a proper cleanup yet. 31 Id string 32 Info *InstanceInfo 33 Config *ResourceConfig 34 Dependencies []string 35 Diff *InstanceDiff 36 Provider ResourceProvider 37 State *InstanceState 38 Flags ResourceFlag 39 } 40 41 // NewResource constructs a legacy Resource object from an 42 // addrs.ResourceInstance value. 43 // 44 // This is provided to shim to old codepaths that haven't been updated away 45 // from this type yet. Since this old type is not able to represent instances 46 // that have string keys, this function will panic if given a resource address 47 // that has a string key. 48 func NewResource(addr addrs.ResourceInstance) *Resource { 49 ret := &Resource{ 50 Name: addr.Resource.Name, 51 Type: addr.Resource.Type, 52 } 53 54 if addr.Key != addrs.NoKey { 55 switch tk := addr.Key.(type) { 56 case addrs.IntKey: 57 ret.CountIndex = int(tk) 58 default: 59 panic(fmt.Errorf("resource instance with key %#v is not supported", addr.Key)) 60 } 61 } 62 63 return ret 64 } 65 66 // ResourceKind specifies what kind of instance we're working with, whether 67 // its a primary instance, a tainted instance, or an orphan. 68 type ResourceFlag byte 69 70 // InstanceInfo is used to hold information about the instance and/or 71 // resource being modified. 72 type InstanceInfo struct { 73 // Id is a unique name to represent this instance. This is not related 74 // to InstanceState.ID in any way. 75 Id string 76 77 // ModulePath is the complete path of the module containing this 78 // instance. 79 ModulePath []string 80 81 // Type is the resource type of this instance 82 Type string 83 } 84 85 // NewInstanceInfo constructs an InstanceInfo from an addrs.AbsResourceInstance. 86 // 87 // InstanceInfo is a legacy type, and uses of it should be gradually replaced 88 // by direct use of addrs.AbsResource or addrs.AbsResourceInstance as 89 // appropriate. 90 // 91 // The legacy InstanceInfo type cannot represent module instances with instance 92 // keys, so this function will panic if given such a path. Uses of this type 93 // should all be removed or replaced before implementing "count" and "for_each" 94 // arguments on modules in order to avoid such panics. 95 // 96 // This legacy type also cannot represent resource instances with string 97 // instance keys. It will panic if the given key is not either NoKey or an 98 // IntKey. 99 func NewInstanceInfo(addr addrs.AbsResourceInstance) *InstanceInfo { 100 // We need an old-style []string module path for InstanceInfo. 101 path := make([]string, len(addr.Module)) 102 for i, step := range addr.Module { 103 if step.InstanceKey != addrs.NoKey { 104 panic("NewInstanceInfo cannot convert module instance with key") 105 } 106 path[i] = step.Name 107 } 108 109 // This is a funny old meaning of "id" that is no longer current. It should 110 // not be used for anything users might see. Note that it does not include 111 // a representation of the resource mode, and so it's impossible to 112 // determine from an InstanceInfo alone whether it is a managed or data 113 // resource that is being referred to. 114 id := fmt.Sprintf("%s.%s", addr.Resource.Resource.Type, addr.Resource.Resource.Name) 115 if addr.Resource.Resource.Mode == addrs.DataResourceMode { 116 id = "data." + id 117 } 118 if addr.Resource.Key != addrs.NoKey { 119 switch k := addr.Resource.Key.(type) { 120 case addrs.IntKey: 121 id = id + fmt.Sprintf(".%d", int(k)) 122 default: 123 panic(fmt.Sprintf("NewInstanceInfo cannot convert resource instance with %T instance key", addr.Resource.Key)) 124 } 125 } 126 127 return &InstanceInfo{ 128 Id: id, 129 ModulePath: path, 130 Type: addr.Resource.Resource.Type, 131 } 132 } 133 134 // ResourceAddress returns the address of the resource that the receiver is describing. 135 func (i *InstanceInfo) ResourceAddress() *ResourceAddress { 136 // GROSS: for tainted and deposed instances, their status gets appended 137 // to i.Id to create a unique id for the graph node. Historically these 138 // ids were displayed to the user, so it's designed to be human-readable: 139 // "aws_instance.bar.0 (deposed #0)" 140 // 141 // So here we detect such suffixes and try to interpret them back to 142 // their original meaning so we can then produce a ResourceAddress 143 // with a suitable InstanceType. 144 id := i.Id 145 instanceType := TypeInvalid 146 if idx := strings.Index(id, " ("); idx != -1 { 147 remain := id[idx:] 148 id = id[:idx] 149 150 switch { 151 case strings.Contains(remain, "tainted"): 152 instanceType = TypeTainted 153 case strings.Contains(remain, "deposed"): 154 instanceType = TypeDeposed 155 } 156 } 157 158 addr, err := parseResourceAddressInternal(id) 159 if err != nil { 160 // should never happen, since that would indicate a bug in the 161 // code that constructed this InstanceInfo. 162 panic(fmt.Errorf("InstanceInfo has invalid Id %s", id)) 163 } 164 if len(i.ModulePath) > 1 { 165 addr.Path = i.ModulePath[1:] // trim off "root" prefix, which is implied 166 } 167 if instanceType != TypeInvalid { 168 addr.InstanceTypeSet = true 169 addr.InstanceType = instanceType 170 } 171 return addr 172 } 173 174 // ResourceConfig is a legacy type that was formerly used to represent 175 // interpolatable configuration blocks. It is now only used to shim to old 176 // APIs that still use this type, via NewResourceConfigShimmed. 177 type ResourceConfig struct { 178 ComputedKeys []string 179 Raw map[string]interface{} 180 Config map[string]interface{} 181 } 182 183 // NewResourceConfigRaw constructs a ResourceConfig whose content is exactly 184 // the given value. 185 // 186 // The given value may contain hcl2shim.UnknownVariableValue to signal that 187 // something is computed, but it must not contain unprocessed interpolation 188 // sequences as we might've seen in Terraform v0.11 and prior. 189 func NewResourceConfigRaw(raw map[string]interface{}) *ResourceConfig { 190 v := hcl2shim.HCL2ValueFromConfigValue(raw) 191 192 // This is a little weird but we round-trip the value through the hcl2shim 193 // package here for two reasons: firstly, because that reduces the risk 194 // of it including something unlike what NewResourceConfigShimmed would 195 // produce, and secondly because it creates a copy of "raw" just in case 196 // something is relying on the fact that in the old world the raw and 197 // config maps were always distinct, and thus you could in principle mutate 198 // one without affecting the other. (I sure hope nobody was doing that, though!) 199 cfg := hcl2shim.ConfigValueFromHCL2(v).(map[string]interface{}) 200 201 return &ResourceConfig{ 202 Raw: raw, 203 Config: cfg, 204 205 ComputedKeys: newResourceConfigShimmedComputedKeys(v, ""), 206 } 207 } 208 209 // NewResourceConfigShimmed wraps a cty.Value of object type in a legacy 210 // ResourceConfig object, so that it can be passed to older APIs that expect 211 // this wrapping. 212 // 213 // The returned ResourceConfig is already interpolated and cannot be 214 // re-interpolated. It is, therefore, useful only to functions that expect 215 // an already-populated ResourceConfig which they then treat as read-only. 216 // 217 // If the given value is not of an object type that conforms to the given 218 // schema then this function will panic. 219 func NewResourceConfigShimmed(val cty.Value, schema *configschema.Block) *ResourceConfig { 220 if !val.Type().IsObjectType() { 221 panic(fmt.Errorf("NewResourceConfigShimmed given %#v; an object type is required", val.Type())) 222 } 223 ret := &ResourceConfig{} 224 225 legacyVal := hcl2shim.ConfigValueFromHCL2Block(val, schema) 226 if legacyVal != nil { 227 ret.Config = legacyVal 228 229 // Now we need to walk through our structure and find any unknown values, 230 // producing the separate list ComputedKeys to represent these. We use the 231 // schema here so that we can preserve the expected invariant 232 // that an attribute is always either wholly known or wholly unknown, while 233 // a child block can be partially unknown. 234 ret.ComputedKeys = newResourceConfigShimmedComputedKeys(val, "") 235 } else { 236 ret.Config = make(map[string]interface{}) 237 } 238 ret.Raw = ret.Config 239 240 return ret 241 } 242 243 // Record the any config values in ComputedKeys. This field had been unused in 244 // helper/schema, but in the new protocol we're using this so that the SDK can 245 // now handle having an unknown collection. The legacy diff code doesn't 246 // properly handle the unknown, because it can't be expressed in the same way 247 // between the config and diff. 248 func newResourceConfigShimmedComputedKeys(val cty.Value, path string) []string { 249 var ret []string 250 ty := val.Type() 251 252 if val.IsNull() { 253 return ret 254 } 255 256 if !val.IsKnown() { 257 // we shouldn't have an entirely unknown resource, but prevent empty 258 // strings just in case 259 if len(path) > 0 { 260 ret = append(ret, path) 261 } 262 return ret 263 } 264 265 if path != "" { 266 path += "." 267 } 268 switch { 269 case ty.IsListType(), ty.IsTupleType(), ty.IsSetType(): 270 i := 0 271 for it := val.ElementIterator(); it.Next(); i++ { 272 _, subVal := it.Element() 273 keys := newResourceConfigShimmedComputedKeys(subVal, fmt.Sprintf("%s%d", path, i)) 274 ret = append(ret, keys...) 275 } 276 277 case ty.IsMapType(), ty.IsObjectType(): 278 for it := val.ElementIterator(); it.Next(); { 279 subK, subVal := it.Element() 280 keys := newResourceConfigShimmedComputedKeys(subVal, fmt.Sprintf("%s%s", path, subK.AsString())) 281 ret = append(ret, keys...) 282 } 283 } 284 285 return ret 286 } 287 288 // DeepCopy performs a deep copy of the configuration. This makes it safe 289 // to modify any of the structures that are part of the resource config without 290 // affecting the original configuration. 291 func (c *ResourceConfig) DeepCopy() *ResourceConfig { 292 // DeepCopying a nil should return a nil to avoid panics 293 if c == nil { 294 return nil 295 } 296 297 // Copy, this will copy all the exported attributes 298 copy, err := copystructure.Config{Lock: true}.Copy(c) 299 if err != nil { 300 panic(err) 301 } 302 303 // Force the type 304 result := copy.(*ResourceConfig) 305 306 return result 307 } 308 309 // Equal checks the equality of two resource configs. 310 func (c *ResourceConfig) Equal(c2 *ResourceConfig) bool { 311 // If either are nil, then they're only equal if they're both nil 312 if c == nil || c2 == nil { 313 return c == c2 314 } 315 316 // Sort the computed keys so they're deterministic 317 sort.Strings(c.ComputedKeys) 318 sort.Strings(c2.ComputedKeys) 319 320 // Two resource configs if their exported properties are equal. 321 // We don't compare "raw" because it is never used again after 322 // initialization and for all intents and purposes they are equal 323 // if the exported properties are equal. 324 check := [][2]interface{}{ 325 {c.ComputedKeys, c2.ComputedKeys}, 326 {c.Raw, c2.Raw}, 327 {c.Config, c2.Config}, 328 } 329 for _, pair := range check { 330 if !reflect.DeepEqual(pair[0], pair[1]) { 331 return false 332 } 333 } 334 335 return true 336 } 337 338 // CheckSet checks that the given list of configuration keys is 339 // properly set. If not, errors are returned for each unset key. 340 // 341 // This is useful to be called in the Validate method of a ResourceProvider. 342 func (c *ResourceConfig) CheckSet(keys []string) []error { 343 var errs []error 344 345 for _, k := range keys { 346 if !c.IsSet(k) { 347 errs = append(errs, fmt.Errorf("%s must be set", k)) 348 } 349 } 350 351 return errs 352 } 353 354 // Get looks up a configuration value by key and returns the value. 355 // 356 // The second return value is true if the get was successful. Get will 357 // return the raw value if the key is computed, so you should pair this 358 // with IsComputed. 359 func (c *ResourceConfig) Get(k string) (interface{}, bool) { 360 // We aim to get a value from the configuration. If it is computed, 361 // then we return the pure raw value. 362 source := c.Config 363 if c.IsComputed(k) { 364 source = c.Raw 365 } 366 367 return c.get(k, source) 368 } 369 370 // GetRaw looks up a configuration value by key and returns the value, 371 // from the raw, uninterpolated config. 372 // 373 // The second return value is true if the get was successful. Get will 374 // not succeed if the value is being computed. 375 func (c *ResourceConfig) GetRaw(k string) (interface{}, bool) { 376 return c.get(k, c.Raw) 377 } 378 379 // IsComputed returns whether the given key is computed or not. 380 func (c *ResourceConfig) IsComputed(k string) bool { 381 // The next thing we do is check the config if we get a computed 382 // value out of it. 383 v, ok := c.get(k, c.Config) 384 if !ok { 385 return false 386 } 387 388 // If value is nil, then it isn't computed 389 if v == nil { 390 return false 391 } 392 393 // Test if the value contains an unknown value 394 var w unknownCheckWalker 395 if err := reflectwalk.Walk(v, &w); err != nil { 396 panic(err) 397 } 398 399 return w.Unknown 400 } 401 402 // IsSet checks if the key in the configuration is set. A key is set if 403 // it has a value or the value is being computed (is unknown currently). 404 // 405 // This function should be used rather than checking the keys of the 406 // raw configuration itself, since a key may be omitted from the raw 407 // configuration if it is being computed. 408 func (c *ResourceConfig) IsSet(k string) bool { 409 if c == nil { 410 return false 411 } 412 413 if c.IsComputed(k) { 414 return true 415 } 416 417 if _, ok := c.Get(k); ok { 418 return true 419 } 420 421 return false 422 } 423 424 func (c *ResourceConfig) get( 425 k string, raw map[string]interface{}) (interface{}, bool) { 426 parts := strings.Split(k, ".") 427 if len(parts) == 1 && parts[0] == "" { 428 parts = nil 429 } 430 431 var current interface{} = raw 432 var previous interface{} = nil 433 for i, part := range parts { 434 if current == nil { 435 return nil, false 436 } 437 438 cv := reflect.ValueOf(current) 439 switch cv.Kind() { 440 case reflect.Map: 441 previous = current 442 v := cv.MapIndex(reflect.ValueOf(part)) 443 if !v.IsValid() { 444 if i > 0 && i != (len(parts)-1) { 445 tryKey := strings.Join(parts[i:], ".") 446 v := cv.MapIndex(reflect.ValueOf(tryKey)) 447 if !v.IsValid() { 448 return nil, false 449 } 450 451 return v.Interface(), true 452 } 453 454 return nil, false 455 } 456 457 current = v.Interface() 458 case reflect.Slice: 459 previous = current 460 461 if part == "#" { 462 // If any value in a list is computed, this whole thing 463 // is computed and we can't read any part of it. 464 for i := 0; i < cv.Len(); i++ { 465 if v := cv.Index(i).Interface(); v == hcl2shim.UnknownVariableValue { 466 return v, true 467 } 468 } 469 470 current = cv.Len() 471 } else { 472 i, err := strconv.ParseInt(part, 0, 0) 473 if err != nil { 474 return nil, false 475 } 476 if int(i) < 0 || int(i) >= cv.Len() { 477 return nil, false 478 } 479 current = cv.Index(int(i)).Interface() 480 } 481 case reflect.String: 482 // This happens when map keys contain "." and have a common 483 // prefix so were split as path components above. 484 actualKey := strings.Join(parts[i-1:], ".") 485 if prevMap, ok := previous.(map[string]interface{}); ok { 486 v, ok := prevMap[actualKey] 487 return v, ok 488 } 489 490 return nil, false 491 default: 492 panic(fmt.Sprintf("Unknown kind: %s", cv.Kind())) 493 } 494 } 495 496 return current, true 497 } 498 499 // unknownCheckWalker 500 type unknownCheckWalker struct { 501 Unknown bool 502 } 503 504 func (w *unknownCheckWalker) Primitive(v reflect.Value) error { 505 if v.Interface() == hcl2shim.UnknownVariableValue { 506 w.Unknown = true 507 } 508 509 return nil 510 }