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