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)) } } // ResourceAddr 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) ResourceAddr() (addrs.AbsResource, bool) { moduleInstance := addrs.RootModuleInstance switch container := r.ContainerAddr.(type) { case addrs.ModuleInstance: moduleInstance = container switch ref := r.LocalRef.Subject.(type) { case addrs.Resource: return ref.Absolute(moduleInstance), true case addrs.ResourceInstance: return ref.ContainingResource().Absolute(moduleInstance), true } return addrs.AbsResource{}, false case addrs.AbsResourceInstance: return container.ContainingResource(), 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.ResourceAddr() 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.AbsResource Attr cty.Path } func (r ResourceAttr) DebugString() string { return r.Resource.String() + tfdiags.FormatCtyPath(r.Attr) }