| package globalref |
| |
| import ( |
| "fmt" |
| |
| "github.com/hashicorp/hcl/v2" |
| "github.com/hashicorp/terraform/internal/addrs" |
| "github.com/hashicorp/terraform/internal/tfdiags" |
| "github.com/zclconf/go-cty/cty" |
| ) |
| |
| // Reference combines an addrs.Reference with the address of the module |
| // instance or resource instance where it was found. |
| // |
| // Because of the design of the Terraform language, our main model of |
| // references only captures the module-local part of the reference and assumes |
| // that it's always clear from context which module a reference belongs to. |
| // That's not true for globalref because our whole purpose is to work across |
| // module boundaries, and so this package in particular has its own |
| // representation of references. |
| type Reference struct { |
| // ContainerAddr is always either addrs.ModuleInstance or |
| // addrs.AbsResourceInstance. The latter is required if LocalRef's |
| // subject is either an addrs.CountAddr or addrs.ForEachAddr, so |
| // we can know which resource's repetition expression it's |
| // referring to. |
| ContainerAddr addrs.Targetable |
| |
| // LocalRef is a reference that would be resolved in the context |
| // of the module instance or resource instance given in ContainerAddr. |
| LocalRef *addrs.Reference |
| } |
| |
| func absoluteRef(containerAddr addrs.Targetable, localRef *addrs.Reference) Reference { |
| ret := Reference{ |
| ContainerAddr: containerAddr, |
| LocalRef: localRef, |
| } |
| // For simplicity's sake, we always reduce the ContainerAddr to be |
| // just the module address unless it's a count.index, each.key, or |
| // each.value reference, because for anything else it's immaterial |
| // which resource it belongs to. |
| switch localRef.Subject.(type) { |
| case addrs.CountAttr, addrs.ForEachAttr: |
| // nothing to do |
| default: |
| ret.ContainerAddr = ret.ModuleAddr() |
| } |
| return ret |
| } |
| |
| func absoluteRefs(containerAddr addrs.Targetable, refs []*addrs.Reference) []Reference { |
| if len(refs) == 0 { |
| return nil |
| } |
| |
| ret := make([]Reference, len(refs)) |
| for i, ref := range refs { |
| ret[i] = absoluteRef(containerAddr, ref) |
| } |
| return ret |
| } |
| |
| // ModuleAddr returns the address of the module where the reference would |
| // be resolved. |
| // |
| // This is either ContainerAddr directly if it's already just a module |
| // instance, or the module instance part of it if it's a resource instance. |
| func (r Reference) ModuleAddr() addrs.ModuleInstance { |
| switch addr := r.ContainerAddr.(type) { |
| case addrs.ModuleInstance: |
| return addr |
| case addrs.AbsResourceInstance: |
| return addr.Module |
| default: |
| // NOTE: We're intentionally using only a subset of possible |
| // addrs.Targetable implementations here, so anything else |
| // is invalid. |
| panic(fmt.Sprintf("reference has invalid container address type %T", addr)) |
| } |
| } |
| |
| // ResourceInstance returns the address of the resource where the reference |
| // would be resolved, if there is one. |
| // |
| // Because not all references belong to resources, the extra boolean return |
| // value indicates whether the returned address is valid. |
| func (r Reference) ResourceInstance() (addrs.AbsResourceInstance, bool) { |
| switch container := r.ContainerAddr.(type) { |
| case addrs.ModuleInstance: |
| moduleInstance := container |
| |
| switch ref := r.LocalRef.Subject.(type) { |
| case addrs.Resource: |
| return ref.Instance(addrs.NoKey).Absolute(moduleInstance), true |
| case addrs.ResourceInstance: |
| return ref.Absolute(moduleInstance), true |
| } |
| |
| return addrs.AbsResourceInstance{}, false |
| |
| case addrs.AbsResourceInstance: |
| return container, true |
| default: |
| // NOTE: We're intentionally using only a subset of possible |
| // addrs.Targetable implementations here, so anything else |
| // is invalid. |
| panic(fmt.Sprintf("reference has invalid container address type %T", container)) |
| } |
| } |
| |
| // DebugString returns an internal (but still somewhat Terraform-language-like) |
| // compact string representation of the reciever, which isn't an address that |
| // any of our usual address parsers could accept but still captures the |
| // essence of what the reference represents. |
| // |
| // The DebugString result is not suitable for end-user-oriented messages. |
| // |
| // DebugString is also not suitable for use as a unique key for a reference, |
| // because it's ambiguous (between a no-key resource instance and a resource) |
| // and because it discards the source location information in the LocalRef. |
| func (r Reference) DebugString() string { |
| // As the doc comment insinuates, we don't have any real syntax for |
| // "absolute references": references are always local, and targets are |
| // always absolute but only include modules and resources. |
| return r.ContainerAddr.String() + "::" + r.LocalRef.DisplayString() |
| } |
| |
| // ResourceAttr converts the Reference value to a more specific ResourceAttr |
| // value. |
| // |
| // Because not all references belong to resources, the extra boolean return |
| // value indicates whether the returned address is valid. |
| func (r Reference) ResourceAttr() (ResourceAttr, bool) { |
| res, ok := r.ResourceInstance() |
| if !ok { |
| return ResourceAttr{}, ok |
| } |
| |
| traversal := r.LocalRef.Remaining |
| |
| path := make(cty.Path, len(traversal)) |
| for si, step := range traversal { |
| switch ts := step.(type) { |
| case hcl.TraverseRoot: |
| path[si] = cty.GetAttrStep{ |
| Name: ts.Name, |
| } |
| case hcl.TraverseAttr: |
| path[si] = cty.GetAttrStep{ |
| Name: ts.Name, |
| } |
| case hcl.TraverseIndex: |
| path[si] = cty.IndexStep{ |
| Key: ts.Key, |
| } |
| default: |
| panic(fmt.Sprintf("unsupported traversal step %#v", step)) |
| } |
| } |
| |
| return ResourceAttr{ |
| Resource: res, |
| Attr: path, |
| }, true |
| } |
| |
| // addrKey returns the referenceAddrKey value for the item that |
| // this reference refers to, discarding any source location information. |
| // |
| // See the referenceAddrKey doc comment for more information on what this |
| // is suitable for. |
| func (r Reference) addrKey() referenceAddrKey { |
| // This is a pretty arbitrary bunch of stuff. We include the type here |
| // just to differentiate between no-key resource instances and resources. |
| return referenceAddrKey(fmt.Sprintf("%s(%T)%s", r.ContainerAddr.String(), r.LocalRef.Subject, r.LocalRef.DisplayString())) |
| } |
| |
| // referenceAddrKey is a special string type which conventionally contains |
| // a unique string representation of the object that a reference refers to, |
| // although not of the reference itself because it ignores the information |
| // that would differentiate two different references to the same object. |
| // |
| // The actual content of a referenceAddrKey is arbitrary, for internal use |
| // only. and subject to change in future. We use a named type here only to |
| // make it easier to see when we're intentionally using strings to uniquely |
| // identify absolute reference addresses. |
| type referenceAddrKey string |
| |
| // ResourceAttr represents a global resource and attribute reference. |
| // This is a more specific form of the Reference type since it can only refer |
| // to a specific AbsResource and one of its attributes. |
| type ResourceAttr struct { |
| Resource addrs.AbsResourceInstance |
| Attr cty.Path |
| } |
| |
| func (r ResourceAttr) DebugString() string { |
| return r.Resource.String() + tfdiags.FormatCtyPath(r.Attr) |
| } |