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