package terraform import ( "fmt" "reflect" "sort" "strconv" "strings" "github.com/mitchellh/copystructure" "github.com/mitchellh/reflectwalk" "github.com/zclconf/go-cty/cty" "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/configs/hcl2shim" ) // Resource is a legacy way to identify a particular resource instance. // // New code should use addrs.ResourceInstance instead. This is still here // only for codepaths that haven't been updated yet. type Resource struct { // These are all used by the new EvalNode stuff. Name string Type string CountIndex int // These aren't really used anymore anywhere, but we keep them around // since we haven't done a proper cleanup yet. Id string Info *InstanceInfo Config *ResourceConfig Dependencies []string Diff *InstanceDiff Provider ResourceProvider State *InstanceState Flags ResourceFlag } // NewResource constructs a legacy Resource object from an // addrs.ResourceInstance value. // // This is provided to shim to old codepaths that haven't been updated away // from this type yet. Since this old type is not able to represent instances // that have string keys, this function will panic if given a resource address // that has a string key. func NewResource(addr addrs.ResourceInstance) *Resource { ret := &Resource{ Name: addr.Resource.Name, Type: addr.Resource.Type, } if addr.Key != addrs.NoKey { switch tk := addr.Key.(type) { case addrs.IntKey: ret.CountIndex = int(tk) default: panic(fmt.Errorf("resource instance with key %#v is not supported", addr.Key)) } } return ret } // ResourceKind specifies what kind of instance we're working with, whether // its a primary instance, a tainted instance, or an orphan. type ResourceFlag byte // InstanceInfo is used to hold information about the instance and/or // resource being modified. type InstanceInfo struct { // Id is a unique name to represent this instance. This is not related // to InstanceState.ID in any way. Id string // ModulePath is the complete path of the module containing this // instance. ModulePath []string // Type is the resource type of this instance Type string // uniqueExtra is an internal field that can be populated to supply // extra metadata that is used to identify a unique instance in // the graph walk. This will be appended to HumanID when uniqueId // is called. uniqueExtra string } // NewInstanceInfo constructs an InstanceInfo from an addrs.AbsResourceInstance. // // InstanceInfo is a legacy type, and uses of it should be gradually replaced // by direct use of addrs.AbsResource or addrs.AbsResourceInstance as // appropriate. // // The legacy InstanceInfo type cannot represent module instances with instance // keys, so this function will panic if given such a path. Uses of this type // should all be removed or replaced before implementing "count" and "for_each" // arguments on modules in order to avoid such panics. // // This legacy type also cannot represent resource instances with string // instance keys. It will panic if the given key is not either NoKey or an // IntKey. func NewInstanceInfo(addr addrs.AbsResourceInstance) *InstanceInfo { // We need an old-style []string module path for InstanceInfo. path := make([]string, len(addr.Module)) for i, step := range addr.Module { if step.InstanceKey != addrs.NoKey { panic("NewInstanceInfo cannot convert module instance with key") } path[i] = step.Name } // This is a funny old meaning of "id" that is no longer current. It should // not be used for anything users might see. Note that it does not include // a representation of the resource mode, and so it's impossible to // determine from an InstanceInfo alone whether it is a managed or data // resource that is being referred to. id := fmt.Sprintf("%s.%s", addr.Resource.Resource.Type, addr.Resource.Resource.Name) if addr.Resource.Resource.Mode == addrs.DataResourceMode { id = "data." + id } if addr.Resource.Key != addrs.NoKey { switch k := addr.Resource.Key.(type) { case addrs.IntKey: id = id + fmt.Sprintf(".%d", int(k)) default: panic(fmt.Sprintf("NewInstanceInfo cannot convert resource instance with %T instance key", addr.Resource.Key)) } } return &InstanceInfo{ Id: id, ModulePath: path, Type: addr.Resource.Resource.Type, } } // ResourceAddress returns the address of the resource that the receiver is describing. func (i *InstanceInfo) ResourceAddress() *ResourceAddress { // GROSS: for tainted and deposed instances, their status gets appended // to i.Id to create a unique id for the graph node. Historically these // ids were displayed to the user, so it's designed to be human-readable: // "aws_instance.bar.0 (deposed #0)" // // So here we detect such suffixes and try to interpret them back to // their original meaning so we can then produce a ResourceAddress // with a suitable InstanceType. id := i.Id instanceType := TypeInvalid if idx := strings.Index(id, " ("); idx != -1 { remain := id[idx:] id = id[:idx] switch { case strings.Contains(remain, "tainted"): instanceType = TypeTainted case strings.Contains(remain, "deposed"): instanceType = TypeDeposed } } addr, err := parseResourceAddressInternal(id) if err != nil { // should never happen, since that would indicate a bug in the // code that constructed this InstanceInfo. panic(fmt.Errorf("InstanceInfo has invalid Id %s", id)) } if len(i.ModulePath) > 1 { addr.Path = i.ModulePath[1:] // trim off "root" prefix, which is implied } if instanceType != TypeInvalid { addr.InstanceTypeSet = true addr.InstanceType = instanceType } return addr } // ResourceConfig is a legacy type that was formerly used to represent // interpolatable configuration blocks. It is now only used to shim to old // APIs that still use this type, via NewResourceConfigShimmed. type ResourceConfig struct { ComputedKeys []string Raw map[string]interface{} Config map[string]interface{} } // NewResourceConfigRaw constructs a ResourceConfig whose content is exactly // the given value. // // The given value may contain hcl2shim.UnknownVariableValue to signal that // something is computed, but it must not contain unprocessed interpolation // sequences as we might've seen in Terraform v0.11 and prior. func NewResourceConfigRaw(raw map[string]interface{}) *ResourceConfig { v := hcl2shim.HCL2ValueFromConfigValue(raw) // This is a little weird but we round-trip the value through the hcl2shim // package here for two reasons: firstly, because that reduces the risk // of it including something unlike what NewResourceConfigShimmed would // produce, and secondly because it creates a copy of "raw" just in case // something is relying on the fact that in the old world the raw and // config maps were always distinct, and thus you could in principle mutate // one without affecting the other. (I sure hope nobody was doing that, though!) cfg := hcl2shim.ConfigValueFromHCL2(v).(map[string]interface{}) return &ResourceConfig{ Raw: raw, Config: cfg, ComputedKeys: newResourceConfigShimmedComputedKeys(v, ""), } } // NewResourceConfigShimmed wraps a cty.Value of object type in a legacy // ResourceConfig object, so that it can be passed to older APIs that expect // this wrapping. // // The returned ResourceConfig is already interpolated and cannot be // re-interpolated. It is, therefore, useful only to functions that expect // an already-populated ResourceConfig which they then treat as read-only. // // If the given value is not of an object type that conforms to the given // schema then this function will panic. func NewResourceConfigShimmed(val cty.Value, schema *configschema.Block) *ResourceConfig { if !val.Type().IsObjectType() { panic(fmt.Errorf("NewResourceConfigShimmed given %#v; an object type is required", val.Type())) } ret := &ResourceConfig{} legacyVal := hcl2shim.ConfigValueFromHCL2Block(val, schema) if legacyVal != nil { ret.Config = legacyVal // Now we need to walk through our structure and find any unknown values, // producing the separate list ComputedKeys to represent these. We use the // schema here so that we can preserve the expected invariant // that an attribute is always either wholly known or wholly unknown, while // a child block can be partially unknown. ret.ComputedKeys = newResourceConfigShimmedComputedKeys(val, "") } else { ret.Config = make(map[string]interface{}) } ret.Raw = ret.Config return ret } // Record the any config values in ComputedKeys. This field had been unused in // helper/schema, but in the new protocol we're using this so that the SDK can // now handle having an unknown collection. The legacy diff code doesn't // properly handle the unknown, because it can't be expressed in the same way // between the config and diff. func newResourceConfigShimmedComputedKeys(val cty.Value, path string) []string { var ret []string ty := val.Type() if val.IsNull() { return ret } if !val.IsKnown() { // we shouldn't have an entirely unknown resource, but prevent empty // strings just in case if len(path) > 0 { ret = append(ret, path) } return ret } if path != "" { path += "." } switch { case ty.IsListType(), ty.IsTupleType(), ty.IsSetType(): i := 0 for it := val.ElementIterator(); it.Next(); i++ { _, subVal := it.Element() keys := newResourceConfigShimmedComputedKeys(subVal, fmt.Sprintf("%s%d", path, i)) ret = append(ret, keys...) } case ty.IsMapType(), ty.IsObjectType(): for it := val.ElementIterator(); it.Next(); { subK, subVal := it.Element() keys := newResourceConfigShimmedComputedKeys(subVal, fmt.Sprintf("%s%s", path, subK.AsString())) ret = append(ret, keys...) } } return ret } // DeepCopy performs a deep copy of the configuration. This makes it safe // to modify any of the structures that are part of the resource config without // affecting the original configuration. func (c *ResourceConfig) DeepCopy() *ResourceConfig { // DeepCopying a nil should return a nil to avoid panics if c == nil { return nil } // Copy, this will copy all the exported attributes copy, err := copystructure.Config{Lock: true}.Copy(c) if err != nil { panic(err) } // Force the type result := copy.(*ResourceConfig) return result } // Equal checks the equality of two resource configs. func (c *ResourceConfig) Equal(c2 *ResourceConfig) bool { // If either are nil, then they're only equal if they're both nil if c == nil || c2 == nil { return c == c2 } // Sort the computed keys so they're deterministic sort.Strings(c.ComputedKeys) sort.Strings(c2.ComputedKeys) // Two resource configs if their exported properties are equal. // We don't compare "raw" because it is never used again after // initialization and for all intents and purposes they are equal // if the exported properties are equal. check := [][2]interface{}{ {c.ComputedKeys, c2.ComputedKeys}, {c.Raw, c2.Raw}, {c.Config, c2.Config}, } for _, pair := range check { if !reflect.DeepEqual(pair[0], pair[1]) { return false } } return true } // CheckSet checks that the given list of configuration keys is // properly set. If not, errors are returned for each unset key. // // This is useful to be called in the Validate method of a ResourceProvider. func (c *ResourceConfig) CheckSet(keys []string) []error { var errs []error for _, k := range keys { if !c.IsSet(k) { errs = append(errs, fmt.Errorf("%s must be set", k)) } } return errs } // Get looks up a configuration value by key and returns the value. // // The second return value is true if the get was successful. Get will // return the raw value if the key is computed, so you should pair this // with IsComputed. func (c *ResourceConfig) Get(k string) (interface{}, bool) { // We aim to get a value from the configuration. If it is computed, // then we return the pure raw value. source := c.Config if c.IsComputed(k) { source = c.Raw } return c.get(k, source) } // GetRaw looks up a configuration value by key and returns the value, // from the raw, uninterpolated config. // // The second return value is true if the get was successful. Get will // not succeed if the value is being computed. func (c *ResourceConfig) GetRaw(k string) (interface{}, bool) { return c.get(k, c.Raw) } // IsComputed returns whether the given key is computed or not. func (c *ResourceConfig) IsComputed(k string) bool { // The next thing we do is check the config if we get a computed // value out of it. v, ok := c.get(k, c.Config) if !ok { return false } // If value is nil, then it isn't computed if v == nil { return false } // Test if the value contains an unknown value var w unknownCheckWalker if err := reflectwalk.Walk(v, &w); err != nil { panic(err) } return w.Unknown } // IsSet checks if the key in the configuration is set. A key is set if // it has a value or the value is being computed (is unknown currently). // // This function should be used rather than checking the keys of the // raw configuration itself, since a key may be omitted from the raw // configuration if it is being computed. func (c *ResourceConfig) IsSet(k string) bool { if c == nil { return false } if c.IsComputed(k) { return true } if _, ok := c.Get(k); ok { return true } return false } func (c *ResourceConfig) get( k string, raw map[string]interface{}) (interface{}, bool) { parts := strings.Split(k, ".") if len(parts) == 1 && parts[0] == "" { parts = nil } var current interface{} = raw var previous interface{} = nil for i, part := range parts { if current == nil { return nil, false } cv := reflect.ValueOf(current) switch cv.Kind() { case reflect.Map: previous = current v := cv.MapIndex(reflect.ValueOf(part)) if !v.IsValid() { if i > 0 && i != (len(parts)-1) { tryKey := strings.Join(parts[i:], ".") v := cv.MapIndex(reflect.ValueOf(tryKey)) if !v.IsValid() { return nil, false } return v.Interface(), true } return nil, false } current = v.Interface() case reflect.Slice: previous = current if part == "#" { // If any value in a list is computed, this whole thing // is computed and we can't read any part of it. for i := 0; i < cv.Len(); i++ { if v := cv.Index(i).Interface(); v == hcl2shim.UnknownVariableValue { return v, true } } current = cv.Len() } else { i, err := strconv.ParseInt(part, 0, 0) if err != nil { return nil, false } if int(i) < 0 || int(i) >= cv.Len() { return nil, false } current = cv.Index(int(i)).Interface() } case reflect.String: // This happens when map keys contain "." and have a common // prefix so were split as path components above. actualKey := strings.Join(parts[i-1:], ".") if prevMap, ok := previous.(map[string]interface{}); ok { v, ok := prevMap[actualKey] return v, ok } return nil, false default: panic(fmt.Sprintf("Unknown kind: %s", cv.Kind())) } } return current, true } // unknownCheckWalker type unknownCheckWalker struct { Unknown bool } func (w *unknownCheckWalker) Primitive(v reflect.Value) error { if v.Interface() == hcl2shim.UnknownVariableValue { w.Unknown = true } return nil }