addrs: PartialExpandedModule and PartialExpandedResource

In preparation for making it not an error to have unknown values in count
or for_each arguments, we will need a way to talk about the
as-yet-undetermined set of module or resource instance addresses that
will result once we _do_ learn the instance keys in a later step.

Unlike many of our address types these have unexported internals so that
we can do some semi-uncool things with our address types that external
callers are not normally supposed to do. This means they therefore need
a bunch more methods than our address types typically need so that it's
possible to access the relevant parts of the internal representation.
pull/34313/head
Martin Atkins 3 years ago
parent a58ee3eceb
commit 4d0087e3ef

@ -0,0 +1,323 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package addrs
import (
"strings"
)
// PartialExpandedModule represents a set of module instances which all share
// a common known parent module instance but the remaining call instance keys
// are not yet known.
type PartialExpandedModule struct {
// expandedPrefix is the initial part of the module address whose expansion
// is already complete and so has exact instance keys.
expandedPrefix ModuleInstance
// unexpandedSuffix is the remainder of the module address whose instance
// keys are not known yet. This is a slight abuse of type [Module] because
// it's representing a relative path from expandedPrefix rather than a
// path from the root module as usual, so this value must never be exposed
// in the public API of this package.
//
// This can be zero-length in PartialExpandedModule values used as part
// of the internals of a PartialExpandedResource, but should never be
// zero-length in a publicly-exposed PartialExpandedModule because that
// would make this just a degenerate ModuleInstance.
unexpandedSuffix Module
}
func (m ModuleInstance) UnexpandedChild(call ModuleCall) PartialExpandedModule {
return PartialExpandedModule{
expandedPrefix: m,
unexpandedSuffix: Module{call.Name},
}
}
// MatchesInstance returns true if and only if the given module instance
// belongs to the recieving partially-expanded module address pattern.
func (pem PartialExpandedModule) MatchesInstance(inst ModuleInstance) bool {
// Total length must always match.
if len(inst) != (len(pem.expandedPrefix) + len(pem.unexpandedSuffix)) {
return false
}
// The known prefix must match exactly.
givenExpandedPrefix := inst[:len(pem.expandedPrefix)]
if !givenExpandedPrefix.Equal(pem.expandedPrefix) {
return false
}
// The known suffix must match the call names, even though we don't yet
// know the specific instance keys.
givenExpandedSuffix := inst[len(pem.expandedPrefix):]
for i := range pem.unexpandedSuffix {
if pem.unexpandedSuffix[i] != givenExpandedSuffix[i].Name {
return false
}
}
// If we passed all the filters above then it's a match.
return true
}
// Module returns the unexpanded module address that this pattern originated
// from.
func (pem PartialExpandedModule) Module() Module {
ret := pem.expandedPrefix.Module()
return append(ret, pem.unexpandedSuffix...)
}
// KnownPrefix returns the longest possible ModuleInstance address made of
// known segments of this partially-expanded module instance address.
func (pem PartialExpandedModule) KnownPrefix() ModuleInstance {
if len(pem.expandedPrefix) == 0 {
return nil
}
// Although we can't enforce it with the Go compiler, our convention is
// that we never mutate address values outside of this package and so
// we'll expose our pem.expandedPrefix buffer directly here and trust that
// the caller will play nice with it. However, we do force the unused
// capacity to zero so that the caller can safely construct child addresses,
// which would append new steps to the end.
return pem.expandedPrefix[:len(pem.expandedPrefix):len(pem.expandedPrefix)]
}
// FirstUnexpandedCall returns the address of the first step in the module
// path whose instance keys are not yet known, discarding any subsequent
// calls beneath it.
func (pem PartialExpandedModule) FirstUnexpandedCall() AbsModuleCall {
// NOTE: This assumes that there's always at least one element in
// unexpandedSuffix because it should only be used with the public-facing
// version of PartialExpandedModule where that contract always holds. It's
// not safe to use this for the PartialExpandedModule value hidden in the
// internals of PartialExpandedResource.
return AbsModuleCall{
Module: pem.KnownPrefix(),
Call: ModuleCall{
Name: pem.unexpandedSuffix[0],
},
}
}
// UnexpandedSuffix returns the local addresses of all of the calls whose
// instances are not yet expanded, in the module tree traversal order.
//
// Method KnownPrefix concatenated with UnexpandedSuffix (assuming that were
// actually possible) represents the whole module path that the
// PartialExpandedModule encapsulates.
func (pem PartialExpandedModule) UnexpandedSuffix() []ModuleCall {
if len(pem.unexpandedSuffix) == 0 {
// Should never happen for any publicly-visible value of this type,
// because we should always have at least one unexpanded call,
// but we'll allow it anyway since we have a reasonable return value
// for that case.
return nil
}
// A []ModuleCall is the only representation of a non-rooted chain of
// module calls that we're allowed to export in our public API, and so
// we'll transform our not-quite-allowed unrooted "Module" value in that
// form externally.
ret := make([]ModuleCall, len(pem.unexpandedSuffix))
for i, name := range pem.unexpandedSuffix {
ret[i].Name = name
}
return ret
}
// Child returns the address of a child of the receiver that belongs to the
// given module call.
func (pem PartialExpandedModule) Child(call ModuleCall) PartialExpandedModule {
return PartialExpandedModule{
expandedPrefix: pem.expandedPrefix,
unexpandedSuffix: append(pem.unexpandedSuffix, call.Name),
}
}
// Resource returns the address of a resource within the receiver.
func (pem PartialExpandedModule) Resource(resource Resource) PartialExpandedResource {
return PartialExpandedResource{
module: pem,
resource: resource,
}
}
// String returns a string representation of the pattern where the known
// prefix uses the normal module instance address syntax and the unknown
// suffix steps use a similar syntax but with "[*]" as a placeholder to
// represent instance keys that aren't yet known.
func (pem PartialExpandedModule) String() string {
var buf strings.Builder
if len(pem.expandedPrefix) != 0 {
buf.WriteString(pem.expandedPrefix.String())
}
for i, callName := range pem.unexpandedSuffix {
if i > 0 || len(pem.unexpandedSuffix) != 0 {
buf.WriteByte('.')
}
buf.WriteString("module.")
buf.WriteString(callName)
buf.WriteString("[*]")
}
return buf.String()
}
func (per PartialExpandedModule) UniqueKey() UniqueKey {
return partialExpandedModuleKey(per.String())
}
type partialExpandedModuleKey string
var _ UniqueKey = partialExpandedModuleKey("")
func (partialExpandedModuleKey) uniqueKeySigil() {}
// PartialExpandedResource represents a set of resource instances which all share
// a common known parent module instance but the remaining call instance keys
// are not yet known and the resource's own instance keys are not yet known.
//
// A PartialExpandedResource with a fully-known module instance address is
// semantically interchangable with an [AbsResource], which is useful when we
// need to represent an assortment of variously-unknown resource instance
// addresses, but [AbsResource] is preferable in situations where the module
// instance address is _always_ known and it's only the resource instance
// key that is not represented.
type PartialExpandedResource struct {
// module is the partially-expanded module instance address that this
// resource belongs to.
//
// This value can actually represent a fully-expanded module if its
// unexpandedSuffix field is zero-length, in which case it's only the
// resource itself that's unexpanded, which would make this equivalent
// to an AbsResource.
//
// We mustn't directly expose this value in the public API because
// external callers must never see a PartialExpandedModule that is
// actually fully-expanded; that should be a ModuleInstance instead.
module PartialExpandedModule
resource Resource
}
// UnexpandedResource returns the address of a child resource expressed as a
// [PartialExpandedResource].
//
// The result always has a fully-qualified module instance address and is
// therefore semantically equivalent to an [AbsResource], so this variannt
// should be used only in contexts where we might also be storing resources
// belonging to not-fully-expanded modules and need to use the same static
// address type for all of them.
func (m ModuleInstance) UnexpandedResource(resource Resource) PartialExpandedResource {
return PartialExpandedResource{
module: PartialExpandedModule{
expandedPrefix: m,
},
resource: resource,
}
}
// UnexpandedResource returns the receiver reinterpreted as a
// [PartialExpandedResource], which is an alternative form we use in situations
// where we might also need to mix in resources belonging to not-yet-fully-known
// module instance addresses.
func (r AbsResource) UnexpandedResource() PartialExpandedResource {
return PartialExpandedResource{
module: PartialExpandedModule{
expandedPrefix: r.Module,
},
resource: r.Resource,
}
}
// MatchesInstance returns true if and only if the given resource instance
// belongs to the recieving partially-expanded resource address pattern.
func (per PartialExpandedResource) MatchesInstance(inst AbsResourceInstance) bool {
if !per.module.MatchesInstance(inst.Module) {
return false
}
return inst.Resource.Resource.Equal(per.resource)
}
// MatchesResource returns true if and only if the given resource belongs to
// the recieving partially-expanded resource address pattern.
func (per PartialExpandedResource) MatchesResource(inst AbsResource) bool {
if !per.module.MatchesInstance(inst.Module) {
return false
}
return inst.Resource.Equal(per.resource)
}
// AbsResource returns the single [AbsResource] that this address represents
// if this pattern is specific enough to match only a single resource, or
// the zero value of AbsResource if not.
//
// The second return value is true if and only if the returned address is valid.
func (per PartialExpandedResource) AbsResource() (AbsResource, bool) {
if len(per.module.unexpandedSuffix) != 0 {
return AbsResource{}, false
}
return AbsResource{
Module: per.module.expandedPrefix,
Resource: per.resource,
}, true
}
// ConfigResource returns the unexpanded resource address that this
// partially-expanded resource address originates from.
func (per PartialExpandedResource) ConfigResource() ConfigResource {
return ConfigResource{
Module: per.module.Module(),
Resource: per.resource,
}
}
// Resource returns just the leaf resource address that this partially-expanded
// resource address uses, discarding the containing module instance information
// altogether.
func (per PartialExpandedResource) Resource() Resource {
return per.resource
}
// KnownModuleInstancePrefix returns the longest possible ModuleInstance address
// made of known segments of the module instances that this set of resource
// instances all belong to.
//
// If the whole module instance address is known and only the resource
// instances are not then this returns the full prefix, which will be the same
// as the module from a successful return value from
// [PartialExpandedResource.AbsResource].
func (per PartialExpandedResource) KnownModuleInstancePrefix() ModuleInstance {
return per.module.KnownPrefix()
}
// String returns a string representation of the pattern which uses the special
// placeholder "[*]" to represent positions where instance keys are not yet
// known.
func (per PartialExpandedResource) String() string {
moduleAddr := per.module.String()
if len(moduleAddr) != 0 {
return moduleAddr + "." + per.resource.String() + "[*]"
}
return per.resource.String() + "[*]"
}
func (per PartialExpandedResource) UniqueKey() UniqueKey {
// If this address is equivalent to an AbsResource address then we'll
// return its instance key here so that function Equivalent will consider
// the two as equivalent.
if ar, ok := per.AbsResource(); ok {
return ar.UniqueKey()
}
// For not-fully-expanded module paths we'll use a distinct address type
// since there is no other address type equivalent to those.
return partialExpandedResourceKey(per.String())
}
type partialExpandedResourceKey string
var _ UniqueKey = partialExpandedModuleKey("")
func (partialExpandedResourceKey) uniqueKeySigil() {}
Loading…
Cancel
Save