| package addrs |
| |
| import ( |
| "fmt" |
| "strings" |
| |
| "github.com/hashicorp/hcl/v2" |
| "github.com/hashicorp/hcl/v2/hclsyntax" |
| "github.com/zclconf/go-cty/cty" |
| "github.com/zclconf/go-cty/cty/gocty" |
| |
| "github.com/hashicorp/terraform/internal/tfdiags" |
| ) |
| |
| // ModuleInstance is an address for a particular module instance within the |
| // dynamic module tree. This is an extension of the static traversals |
| // represented by type Module that deals with the possibility of a single |
| // module call producing multiple instances via the "count" and "for_each" |
| // arguments. |
| // |
| // Although ModuleInstance is a slice, it should be treated as immutable after |
| // creation. |
| type ModuleInstance []ModuleInstanceStep |
| |
| var ( |
| _ Targetable = ModuleInstance(nil) |
| ) |
| |
| func ParseModuleInstance(traversal hcl.Traversal) (ModuleInstance, tfdiags.Diagnostics) { |
| mi, remain, diags := parseModuleInstancePrefix(traversal) |
| if len(remain) != 0 { |
| if len(remain) == len(traversal) { |
| diags = diags.Append(&hcl.Diagnostic{ |
| Severity: hcl.DiagError, |
| Summary: "Invalid module instance address", |
| Detail: "A module instance address must begin with \"module.\".", |
| Subject: remain.SourceRange().Ptr(), |
| }) |
| } else { |
| diags = diags.Append(&hcl.Diagnostic{ |
| Severity: hcl.DiagError, |
| Summary: "Invalid module instance address", |
| Detail: "The module instance address is followed by additional invalid content.", |
| Subject: remain.SourceRange().Ptr(), |
| }) |
| } |
| } |
| return mi, diags |
| } |
| |
| // ParseModuleInstanceStr is a helper wrapper around ParseModuleInstance |
| // that takes a string and parses it with the HCL native syntax traversal parser |
| // before interpreting it. |
| // |
| // This should be used only in specialized situations since it will cause the |
| // created references to not have any meaningful source location information. |
| // If a reference string is coming from a source that should be identified in |
| // error messages then the caller should instead parse it directly using a |
| // suitable function from the HCL API and pass the traversal itself to |
| // ParseModuleInstance. |
| // |
| // Error diagnostics are returned if either the parsing fails or the analysis |
| // of the traversal fails. There is no way for the caller to distinguish the |
| // two kinds of diagnostics programmatically. If error diagnostics are returned |
| // then the returned address is invalid. |
| func ParseModuleInstanceStr(str string) (ModuleInstance, tfdiags.Diagnostics) { |
| var diags tfdiags.Diagnostics |
| |
| traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1}) |
| diags = diags.Append(parseDiags) |
| if parseDiags.HasErrors() { |
| return nil, diags |
| } |
| |
| addr, addrDiags := ParseModuleInstance(traversal) |
| diags = diags.Append(addrDiags) |
| return addr, diags |
| } |
| |
| func parseModuleInstancePrefix(traversal hcl.Traversal) (ModuleInstance, hcl.Traversal, tfdiags.Diagnostics) { |
| remain := traversal |
| var mi ModuleInstance |
| var diags tfdiags.Diagnostics |
| |
| LOOP: |
| for len(remain) > 0 { |
| var next string |
| switch tt := remain[0].(type) { |
| case hcl.TraverseRoot: |
| next = tt.Name |
| case hcl.TraverseAttr: |
| next = tt.Name |
| default: |
| diags = diags.Append(&hcl.Diagnostic{ |
| Severity: hcl.DiagError, |
| Summary: "Invalid address operator", |
| Detail: "Module address prefix must be followed by dot and then a name.", |
| Subject: remain[0].SourceRange().Ptr(), |
| }) |
| break LOOP |
| } |
| |
| if next != "module" { |
| break |
| } |
| |
| kwRange := remain[0].SourceRange() |
| remain = remain[1:] |
| // If we have the prefix "module" then we should be followed by an |
| // module call name, as an attribute, and then optionally an index step |
| // giving the instance key. |
| if len(remain) == 0 { |
| diags = diags.Append(&hcl.Diagnostic{ |
| Severity: hcl.DiagError, |
| Summary: "Invalid address operator", |
| Detail: "Prefix \"module.\" must be followed by a module name.", |
| Subject: &kwRange, |
| }) |
| break |
| } |
| |
| var moduleName string |
| switch tt := remain[0].(type) { |
| case hcl.TraverseAttr: |
| moduleName = tt.Name |
| default: |
| diags = diags.Append(&hcl.Diagnostic{ |
| Severity: hcl.DiagError, |
| Summary: "Invalid address operator", |
| Detail: "Prefix \"module.\" must be followed by a module name.", |
| Subject: remain[0].SourceRange().Ptr(), |
| }) |
| break LOOP |
| } |
| remain = remain[1:] |
| step := ModuleInstanceStep{ |
| Name: moduleName, |
| } |
| |
| if len(remain) > 0 { |
| if idx, ok := remain[0].(hcl.TraverseIndex); ok { |
| remain = remain[1:] |
| |
| switch idx.Key.Type() { |
| case cty.String: |
| step.InstanceKey = StringKey(idx.Key.AsString()) |
| case cty.Number: |
| var idxInt int |
| err := gocty.FromCtyValue(idx.Key, &idxInt) |
| if err == nil { |
| step.InstanceKey = IntKey(idxInt) |
| } else { |
| diags = diags.Append(&hcl.Diagnostic{ |
| Severity: hcl.DiagError, |
| Summary: "Invalid address operator", |
| Detail: fmt.Sprintf("Invalid module index: %s.", err), |
| Subject: idx.SourceRange().Ptr(), |
| }) |
| } |
| default: |
| // Should never happen, because no other types are allowed in traversal indices. |
| diags = diags.Append(&hcl.Diagnostic{ |
| Severity: hcl.DiagError, |
| Summary: "Invalid address operator", |
| Detail: "Invalid module key: must be either a string or an integer.", |
| Subject: idx.SourceRange().Ptr(), |
| }) |
| } |
| } |
| } |
| |
| mi = append(mi, step) |
| } |
| |
| var retRemain hcl.Traversal |
| if len(remain) > 0 { |
| retRemain = make(hcl.Traversal, len(remain)) |
| copy(retRemain, remain) |
| // The first element here might be either a TraverseRoot or a |
| // TraverseAttr, depending on whether we had a module address on the |
| // front. To make life easier for callers, we'll normalize to always |
| // start with a TraverseRoot. |
| if tt, ok := retRemain[0].(hcl.TraverseAttr); ok { |
| retRemain[0] = hcl.TraverseRoot{ |
| Name: tt.Name, |
| SrcRange: tt.SrcRange, |
| } |
| } |
| } |
| |
| return mi, retRemain, diags |
| } |
| |
| // UnkeyedInstanceShim is a shim method for converting a Module address to the |
| // equivalent ModuleInstance address that assumes that no modules have |
| // keyed instances. |
| // |
| // This is a temporary allowance for the fact that Terraform does not presently |
| // support "count" and "for_each" on modules, and thus graph building code that |
| // derives graph nodes from configuration must just assume unkeyed modules |
| // in order to construct the graph. At a later time when "count" and "for_each" |
| // support is added for modules, all callers of this method will need to be |
| // reworked to allow for keyed module instances. |
| func (m Module) UnkeyedInstanceShim() ModuleInstance { |
| path := make(ModuleInstance, len(m)) |
| for i, name := range m { |
| path[i] = ModuleInstanceStep{Name: name} |
| } |
| return path |
| } |
| |
| // ModuleInstanceStep is a single traversal step through the dynamic module |
| // tree. It is used only as part of ModuleInstance. |
| type ModuleInstanceStep struct { |
| Name string |
| InstanceKey InstanceKey |
| } |
| |
| // RootModuleInstance is the module instance address representing the root |
| // module, which is also the zero value of ModuleInstance. |
| var RootModuleInstance ModuleInstance |
| |
| // IsRoot returns true if the receiver is the address of the root module instance, |
| // or false otherwise. |
| func (m ModuleInstance) IsRoot() bool { |
| return len(m) == 0 |
| } |
| |
| // Child returns the address of a child module instance of the receiver, |
| // identified by the given name and key. |
| func (m ModuleInstance) Child(name string, key InstanceKey) ModuleInstance { |
| ret := make(ModuleInstance, 0, len(m)+1) |
| ret = append(ret, m...) |
| return append(ret, ModuleInstanceStep{ |
| Name: name, |
| InstanceKey: key, |
| }) |
| } |
| |
| // ChildCall returns the address of a module call within the receiver, |
| // identified by the given name. |
| func (m ModuleInstance) ChildCall(name string) AbsModuleCall { |
| return AbsModuleCall{ |
| Module: m, |
| Call: ModuleCall{Name: name}, |
| } |
| } |
| |
| // Parent returns the address of the parent module instance of the receiver, or |
| // the receiver itself if there is no parent (if it's the root module address). |
| func (m ModuleInstance) Parent() ModuleInstance { |
| if len(m) == 0 { |
| return m |
| } |
| return m[:len(m)-1] |
| } |
| |
| // String returns a string representation of the receiver, in the format used |
| // within e.g. user-provided resource addresses. |
| // |
| // The address of the root module has the empty string as its representation. |
| func (m ModuleInstance) String() string { |
| if len(m) == 0 { |
| return "" |
| } |
| // Calculate minimal necessary space (no instance keys). |
| l := 0 |
| for _, step := range m { |
| l += len(step.Name) |
| } |
| buf := strings.Builder{} |
| // 8 is len(".module.") which separates entries. |
| buf.Grow(l + len(m)*8) |
| sep := "" |
| for _, step := range m { |
| buf.WriteString(sep) |
| buf.WriteString("module.") |
| buf.WriteString(step.Name) |
| if step.InstanceKey != NoKey { |
| buf.WriteString(step.InstanceKey.String()) |
| } |
| sep = "." |
| } |
| return buf.String() |
| } |
| |
| type moduleInstanceKey string |
| |
| func (m ModuleInstance) UniqueKey() UniqueKey { |
| return moduleInstanceKey(m.String()) |
| } |
| |
| func (mk moduleInstanceKey) uniqueKeySigil() {} |
| |
| // Equal returns true if the receiver and the given other value |
| // contains the exact same parts. |
| func (m ModuleInstance) Equal(o ModuleInstance) bool { |
| if len(m) != len(o) { |
| return false |
| } |
| |
| for i := range m { |
| if m[i] != o[i] { |
| return false |
| } |
| } |
| return true |
| } |
| |
| // Less returns true if the receiver should sort before the given other value |
| // in a sorted list of addresses. |
| func (m ModuleInstance) Less(o ModuleInstance) bool { |
| if len(m) != len(o) { |
| // Shorter path sorts first. |
| return len(m) < len(o) |
| } |
| |
| for i := range m { |
| mS, oS := m[i], o[i] |
| switch { |
| case mS.Name != oS.Name: |
| return mS.Name < oS.Name |
| case mS.InstanceKey != oS.InstanceKey: |
| return InstanceKeyLess(mS.InstanceKey, oS.InstanceKey) |
| } |
| } |
| |
| return false |
| } |
| |
| // Ancestors returns a slice containing the receiver and all of its ancestor |
| // module instances, all the way up to (and including) the root module. |
| // The result is ordered by depth, with the root module always first. |
| // |
| // Since the result always includes the root module, a caller may choose to |
| // ignore it by slicing the result with [1:]. |
| func (m ModuleInstance) Ancestors() []ModuleInstance { |
| ret := make([]ModuleInstance, 0, len(m)+1) |
| for i := 0; i <= len(m); i++ { |
| ret = append(ret, m[:i]) |
| } |
| return ret |
| } |
| |
| // IsAncestor returns true if the receiver is an ancestor of the given |
| // other value. |
| func (m ModuleInstance) IsAncestor(o ModuleInstance) bool { |
| // Longer or equal sized paths means the receiver cannot |
| // be an ancestor of the given module insatnce. |
| if len(m) >= len(o) { |
| return false |
| } |
| |
| for i, ms := range m { |
| if ms.Name != o[i].Name { |
| return false |
| } |
| if ms.InstanceKey != NoKey && ms.InstanceKey != o[i].InstanceKey { |
| return false |
| } |
| } |
| |
| return true |
| } |
| |
| // Call returns the module call address that corresponds to the given module |
| // instance, along with the address of the module instance that contains it. |
| // |
| // There is no call for the root module, so this method will panic if called |
| // on the root module address. |
| // |
| // A single module call can produce potentially many module instances, so the |
| // result discards any instance key that might be present on the last step |
| // of the instance. To retain this, use CallInstance instead. |
| // |
| // In practice, this just turns the last element of the receiver into a |
| // ModuleCall and then returns a slice of the receiever that excludes that |
| // last part. This is just a convenience for situations where a call address |
| // is required, such as when dealing with *Reference and Referencable values. |
| func (m ModuleInstance) Call() (ModuleInstance, ModuleCall) { |
| if len(m) == 0 { |
| panic("cannot produce ModuleCall for root module") |
| } |
| |
| inst, lastStep := m[:len(m)-1], m[len(m)-1] |
| return inst, ModuleCall{ |
| Name: lastStep.Name, |
| } |
| } |
| |
| // CallInstance returns the module call instance address that corresponds to |
| // the given module instance, along with the address of the module instance |
| // that contains it. |
| // |
| // There is no call for the root module, so this method will panic if called |
| // on the root module address. |
| // |
| // In practice, this just turns the last element of the receiver into a |
| // ModuleCallInstance and then returns a slice of the receiever that excludes |
| // that last part. This is just a convenience for situations where a call\ |
| // address is required, such as when dealing with *Reference and Referencable |
| // values. |
| func (m ModuleInstance) CallInstance() (ModuleInstance, ModuleCallInstance) { |
| if len(m) == 0 { |
| panic("cannot produce ModuleCallInstance for root module") |
| } |
| |
| inst, lastStep := m[:len(m)-1], m[len(m)-1] |
| return inst, ModuleCallInstance{ |
| Call: ModuleCall{ |
| Name: lastStep.Name, |
| }, |
| Key: lastStep.InstanceKey, |
| } |
| } |
| |
| // TargetContains implements Targetable by returning true if the given other |
| // address either matches the receiver, is a sub-module-instance of the |
| // receiver, or is a targetable absolute address within a module that |
| // is contained within the reciever. |
| func (m ModuleInstance) TargetContains(other Targetable) bool { |
| switch to := other.(type) { |
| case Module: |
| if len(to) < len(m) { |
| // Can't be contained if the path is shorter |
| return false |
| } |
| // Other is contained if its steps match for the length of our own path. |
| for i, ourStep := range m { |
| otherStep := to[i] |
| |
| // We can't contain an entire module if we have a specific instance |
| // key. The case of NoKey is OK because this address is either |
| // meant to address an unexpanded module, or a single instance of |
| // that module, and both of those are a covered in-full by the |
| // Module address. |
| if ourStep.InstanceKey != NoKey { |
| return false |
| } |
| |
| if ourStep.Name != otherStep { |
| return false |
| } |
| } |
| // If we fall out here then the prefixed matched, so it's contained. |
| return true |
| |
| case ModuleInstance: |
| if len(to) < len(m) { |
| return false |
| } |
| for i, ourStep := range m { |
| otherStep := to[i] |
| |
| if ourStep.Name != otherStep.Name { |
| return false |
| } |
| |
| // if this is our last step, because all targets are parsed as |
| // instances, this may be a ModuleInstance intended to be used as a |
| // Module. |
| if i == len(m)-1 { |
| if ourStep.InstanceKey == NoKey { |
| // If the other step is a keyed instance, then we contain that |
| // step, and if it isn't it's a match, which is true either way |
| return true |
| } |
| } |
| |
| if ourStep.InstanceKey != otherStep.InstanceKey { |
| return false |
| } |
| |
| } |
| return true |
| |
| case ConfigResource: |
| return m.TargetContains(to.Module) |
| |
| case AbsResource: |
| return m.TargetContains(to.Module) |
| |
| case AbsResourceInstance: |
| return m.TargetContains(to.Module) |
| |
| default: |
| return false |
| } |
| } |
| |
| // Module returns the address of the module that this instance is an instance |
| // of. |
| func (m ModuleInstance) Module() Module { |
| if len(m) == 0 { |
| return nil |
| } |
| ret := make(Module, len(m)) |
| for i, step := range m { |
| ret[i] = step.Name |
| } |
| return ret |
| } |
| |
| func (m ModuleInstance) AddrType() TargetableAddrType { |
| return ModuleInstanceAddrType |
| } |
| |
| func (m ModuleInstance) targetableSigil() { |
| // ModuleInstance is targetable |
| } |
| |
| func (m ModuleInstance) absMoveableSigil() { |
| // ModuleInstance is moveable |
| } |
| |
| // IsDeclaredByCall returns true if the receiver is an instance of the given |
| // AbsModuleCall. |
| func (m ModuleInstance) IsDeclaredByCall(other AbsModuleCall) bool { |
| // Compare len(m) to len(other.Module+1) because the final module instance |
| // step in other is stored in the AbsModuleCall.Call |
| if len(m) > len(other.Module)+1 || len(m) == 0 && len(other.Module) == 0 { |
| return false |
| } |
| |
| // Verify that the other's ModuleInstance matches the receiver. |
| inst, lastStep := other.Module, other.Call |
| for i := range inst { |
| if inst[i] != m[i] { |
| return false |
| } |
| } |
| |
| // Now compare the final step of the received with the other Call, where |
| // only the name needs to match. |
| return lastStep.Name == m[len(m)-1].Name |
| } |
| |
| func (s ModuleInstanceStep) String() string { |
| if s.InstanceKey != NoKey { |
| return s.Name + s.InstanceKey.String() |
| } |
| return s.Name |
| } |