diff --git a/addrs/module.go b/addrs/module.go index 82f1d1d754..025fe8b0c1 100644 --- a/addrs/module.go +++ b/addrs/module.go @@ -1,5 +1,9 @@ package addrs +import ( + "strings" +) + // Module is an address for a module call within configuration. This is // the static counterpart of ModuleInstance, representing a traversal through // the static module call tree in configuration and does not take into account @@ -19,6 +23,13 @@ type Module []string // represented by RootModuleInstance. var RootModule Module +func (m Module) String() string { + if len(m) == 0 { + return "" + } + return strings.Join([]string(m), ".") +} + // Child returns the address of a child call in the receiver, identified by the // given name. func (m Module) Child(name string) Module { diff --git a/addrs/module_instance.go b/addrs/module_instance.go index 4cf43bc698..55fc71d879 100644 --- a/addrs/module_instance.go +++ b/addrs/module_instance.go @@ -1,5 +1,7 @@ package addrs +import "bytes" + // 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 @@ -40,3 +42,22 @@ func (m ModuleInstance) Parent() ModuleInstance { } 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 { + var buf bytes.Buffer + 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() +} diff --git a/configs/config.go b/configs/config.go index 308d2e330c..1a387d9273 100644 --- a/configs/config.go +++ b/configs/config.go @@ -3,6 +3,7 @@ package configs import ( version "github.com/hashicorp/go-version" "github.com/hashicorp/hcl2/hcl" + "github.com/hashicorp/terraform/addrs" ) // A Config is a node in the tree of modules within a configuration. @@ -27,13 +28,12 @@ type Config struct { // Path is a sequence of module logical names that traverse from the root // module to this config. Path is empty for the root module. // - // This should not be used to display a path to the end-user, since - // our UI conventions call for us to return a module address string in that - // case, and a module address string ought to be built from the dynamic - // module tree (resulting from evaluating "count" and "for_each" arguments - // on our calls to produce potentially multiple child instances per call) - // rather than from our static module tree. - Path []string + // This should only be used to display paths to the end-user in rare cases + // where we are talking about the static module tree, before module calls + // have been resolved. In most cases, a addrs.ModuleInstance describing + // a node in the dynamic module tree is better, since it will then include + // any keys resulting from evaluating "count" and "for_each" arguments. + Path addrs.Module // ChildModules points to the Config for each of the direct child modules // called from this module. The keys in this map match the keys in @@ -121,7 +121,7 @@ func (c *Config) AllModules() []*Config { // count and for_each arguments. // // An empty path will just return the receiver, and is therefore pointless. -func (c *Config) Descendent(path []string) *Config { +func (c *Config) Descendent(path addrs.Module) *Config { current := c for _, name := range path { current = current.Children[name] @@ -131,3 +131,20 @@ func (c *Config) Descendent(path []string) *Config { } return current } + +// DescendentForInstance is like Descendent except that it accepts a path +// to a particular module instance in the dynamic module graph, returning +// the node from the static module graph that corresponds to it. +// +// All instances created by a particular module call share the same +// configuration, so the keys within the given path are disregarded. +func (c *Config) DescendentForInstance(path addrs.ModuleInstance) *Config { + current := c + for _, step := range path { + current = current.Children[step.Name] + if current == nil { + return nil + } + } + return current +}