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