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