From e0797ab9132aeda64236223c6daa1c3b97607d67 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 14 Jul 2023 16:11:27 -0700 Subject: [PATCH] stackaddrs: Addresses for things inside components In the stacks model a component is essentially a container for a tree of normal Terraform modules, and so anything that can appear in a Terraform module can in principle appear inside a component. With that in mind here we define a generic type that can represent anything from package addrs belonging either to a stack configuration (before instance expansion) or to a stack instance (after instance expansion), and some aliases for a few combinations that make sense together, such as stackaddrs.AbsResourceInstance being an addrs.AbsResourceInstance belonging to a stackaddrs.AbsComponentInstance. We'll probably add more aliases here later, but this is a starting set that I expect will arise while implementing the planning-related models for stacks. --- internal/addrs/module.go | 8 ++ internal/stacks/stackaddrs/component.go | 19 ++- internal/stacks/stackaddrs/in_component.go | 117 ++++++++++++++++++ internal/stacks/stackaddrs/in_stack.go | 50 ++++++-- internal/stacks/stackaddrs/input_variable.go | 9 ++ internal/stacks/stackaddrs/local_value.go | 9 ++ internal/stacks/stackaddrs/output_value.go | 9 ++ internal/stacks/stackaddrs/provider_config.go | 15 +++ internal/stacks/stackaddrs/stack.go | 19 +++ internal/stacks/stackaddrs/stack_call.go | 14 ++- 10 files changed, 259 insertions(+), 10 deletions(-) create mode 100644 internal/stacks/stackaddrs/in_component.go diff --git a/internal/addrs/module.go b/internal/addrs/module.go index aa309c0f4f..961babd456 100644 --- a/internal/addrs/module.go +++ b/internal/addrs/module.go @@ -176,3 +176,11 @@ func (m Module) Ancestors() []Module { func (m Module) configMoveableSigil() { // ModuleInstance is moveable } + +type moduleKey string + +func (moduleKey) uniqueKeySigil() {} + +func (m Module) UniqueKey() UniqueKey { + return moduleKey(m.String()) +} diff --git a/internal/stacks/stackaddrs/component.go b/internal/stacks/stackaddrs/component.go index ebeeff79e7..134d592fbb 100644 --- a/internal/stacks/stackaddrs/component.go +++ b/internal/stacks/stackaddrs/component.go @@ -1,6 +1,9 @@ package stackaddrs -import "github.com/hashicorp/terraform/internal/addrs" +import ( + "github.com/hashicorp/terraform/internal/addrs" + "github.com/hashicorp/terraform/internal/collections" +) // Component is the address of a "component" block within a stack config. type Component struct { @@ -15,6 +18,13 @@ func (c Component) String() string { return "component." + c.Name } +func (c Component) UniqueKey() collections.UniqueKey[Component] { + return c +} + +// A Component is its own [collections.UniqueKey]. +func (Component) IsUniqueKey(Component) {} + // ConfigComponent places a [Component] in the context of a particular [Stack]. type ConfigComponent = InStackConfig[Component] @@ -37,6 +47,13 @@ func (c ComponentInstance) String() string { return c.Component.String() + c.Key.String() } +func (c ComponentInstance) UniqueKey() collections.UniqueKey[ComponentInstance] { + return c +} + +// A ComponentInstance is its own [collections.UniqueKey]. +func (ComponentInstance) IsUniqueKey(ComponentInstance) {} + // ConfigComponentInstance places a [ComponentInstance] in the context of a // particular [Stack]. type ConfigComponentInstance = InStackConfig[ComponentInstance] diff --git a/internal/stacks/stackaddrs/in_component.go b/internal/stacks/stackaddrs/in_component.go new file mode 100644 index 0000000000..5b90f14c27 --- /dev/null +++ b/internal/stacks/stackaddrs/in_component.go @@ -0,0 +1,117 @@ +package stackaddrs + +import ( + "fmt" + + "github.com/hashicorp/terraform/internal/addrs" + "github.com/hashicorp/terraform/internal/collections" +) + +// InConfigComponent represents addresses of objects that belong to the modules +// associated with a particular component. +// +// Although the type parameter is rather unconstrained, it doesn't make sense to +// use this for types other than those from package addrs that represent +// configuration constructs, like [addrs.ConfigResource], etc. +type InConfigComponent[T InComponentable] struct { + Component ConfigComponent + Item T +} + +// ConfigResource represents a resource configuration from inside a +// particular component. +type ConfigResource = InConfigComponent[addrs.ConfigResource] + +// ConfigModule represents a module from inside a particular component. +// +// Note that the string representation of the address of the root module of +// a component is identical to the string representation of the component +// address alone. +type ConfigModule = InConfigComponent[addrs.Module] + +func (c InConfigComponent[T]) String() string { + itemStr := c.Item.String() + componentStr := c.Component.String() + if itemStr == "" { + return componentStr + } + return componentStr + "." + itemStr +} + +// UniqueKey implements collections.UniqueKeyer. +func (c InConfigComponent[T]) UniqueKey() collections.UniqueKey[InConfigComponent[T]] { + return inConfigComponentKey[T]{ + componentKey: c.Component.UniqueKey(), + itemKey: c.Item.UniqueKey(), + } +} + +type inConfigComponentKey[T InComponentable] struct { + componentKey collections.UniqueKey[ConfigComponent] + itemKey addrs.UniqueKey +} + +// IsUniqueKey implements collections.UniqueKey. +func (inConfigComponentKey[T]) IsUniqueKey(InConfigComponent[T]) {} + +// InAbsComponentInstance represents addresses of objects that belong to the module +// instances associated with a particular component instance. +// +// Although the type parameter is rather unconstrained, it doesn't make sense to +// use this for types other than those from package addrs that represent +// objects that can belong to Terraform modules, like +// [addrs.AbsResourceInstance], etc. +type InAbsComponentInstance[T InComponentable] struct { + Component AbsComponentInstance + Item T +} + +// AbsResource represents a not-yet-expanded resource from inside a particular +// component instance. +type AbsResource = InAbsComponentInstance[addrs.AbsResource] + +var _ collections.UniqueKeyer[AbsResource] = AbsResource{} + +// AbsResourceInstance represents an instance of a resource from inside a +// particular component instance. +type AbsResourceInstance = InAbsComponentInstance[addrs.AbsResourceInstance] + +// AbsModuleInstance represents an instance of a module from inside a +// particular component instance. +// +// Note that the string representation of the address of the root module of +// a component instance is identical to the string representation of the +// component instance address alone. +type AbsModuleInstance = InAbsComponentInstance[addrs.ModuleInstance] + +func (c InAbsComponentInstance[T]) String() string { + itemStr := c.Item.String() + componentStr := c.Component.String() + if itemStr == "" { + return componentStr + } + return componentStr + "." + itemStr +} + +// UniqueKey implements collections.UniqueKeyer. +func (c InAbsComponentInstance[T]) UniqueKey() collections.UniqueKey[InAbsComponentInstance[T]] { + return inAbsComponentInstanceKey[T]{ + componentKey: c.Component.UniqueKey(), + itemKey: c.Item.UniqueKey(), + } +} + +type inAbsComponentInstanceKey[T InComponentable] struct { + componentKey collections.UniqueKey[AbsComponentInstance] + itemKey addrs.UniqueKey +} + +// IsUniqueKey implements collections.UniqueKey. +func (inAbsComponentInstanceKey[T]) IsUniqueKey(InAbsComponentInstance[T]) {} + +// InComponentable just embeds the interfaces that we require for the type +// parameters of both the [InConfigComponent] and [InAbsComponent] types. +type InComponentable interface { + addrs.UniqueKeyer + fmt.Stringer +} diff --git a/internal/stacks/stackaddrs/in_stack.go b/internal/stacks/stackaddrs/in_stack.go index d86d47c7a2..07d66b0557 100644 --- a/internal/stacks/stackaddrs/in_stack.go +++ b/internal/stacks/stackaddrs/in_stack.go @@ -1,28 +1,32 @@ package stackaddrs +import "github.com/hashicorp/terraform/internal/collections" + // StackItemConfig is a type set containing all of the address types that make // sense to consider as belonging statically to a [Stack]. -type StackItemConfig interface { +type StackItemConfig[T any] interface { inStackConfigSigil() String() string + collections.UniqueKeyer[T] } // StackItemDynamic is a type set containing all of the address types that make // sense to consider as belonging dynamically to a [StackInstance]. -type StackItemDynamic interface { +type StackItemDynamic[T any] interface { inStackInstanceSigil() String() string + collections.UniqueKeyer[T] } // InStackConfig is the generic form of addresses representing configuration // objects belonging to particular nodes in the static tree of stack // configurations. -type InStackConfig[T StackItemConfig] struct { +type InStackConfig[T StackItemConfig[T]] struct { Stack Stack Item T } -func Config[T StackItemConfig](stackAddr Stack, relAddr T) InStackConfig[T] { +func Config[T StackItemConfig[T]](stackAddr Stack, relAddr T) InStackConfig[T] { return InStackConfig[T]{ Stack: stackAddr, Item: relAddr, @@ -36,14 +40,29 @@ func (ist InStackConfig[T]) String() string { return ist.Stack.String() + "." + ist.Item.String() } +func (ist InStackConfig[T]) UniqueKey() collections.UniqueKey[InStackConfig[T]] { + return inStackConfigKey[T]{ + stackKey: ist.Stack.UniqueKey(), + itemKey: ist.Item.UniqueKey(), + } +} + +type inStackConfigKey[T StackItemConfig[T]] struct { + stackKey collections.UniqueKey[Stack] + itemKey collections.UniqueKey[T] +} + +// IsUniqueKey implements collections.UniqueKey. +func (inStackConfigKey[T]) IsUniqueKey(InStackConfig[T]) {} + // InStackInstance is the generic form of addresses representing dynamic // instances of objects that exist within an instance of a stack. -type InStackInstance[T StackItemDynamic] struct { +type InStackInstance[T StackItemDynamic[T]] struct { Stack StackInstance Item T } -func Absolute[T StackItemDynamic](stackAddr StackInstance, relAddr T) InStackInstance[T] { +func Absolute[T StackItemDynamic[T]](stackAddr StackInstance, relAddr T) InStackInstance[T] { return InStackInstance[T]{ Stack: stackAddr, Item: relAddr, @@ -57,12 +76,27 @@ func (ist InStackInstance[T]) String() string { return ist.Stack.String() + "." + ist.Item.String() } +func (ist InStackInstance[T]) UniqueKey() collections.UniqueKey[InStackInstance[T]] { + return inStackInstanceKey[T]{ + stackKey: ist.Stack.UniqueKey(), + itemKey: ist.Item.UniqueKey(), + } +} + +type inStackInstanceKey[T StackItemDynamic[T]] struct { + stackKey collections.UniqueKey[StackInstance] + itemKey collections.UniqueKey[T] +} + +// IsUniqueKey implements collections.UniqueKey. +func (inStackInstanceKey[T]) IsUniqueKey(InStackInstance[T]) {} + // ConfigForAbs returns the "in stack config" equivalent of the given // "in stack instance" (absolute) address by just discarding any // instance keys from the stack instance steps. func ConfigForAbs[T interface { - StackItemDynamic - StackItemConfig + StackItemDynamic[T] + StackItemConfig[T] }](absAddr InStackInstance[T]) InStackConfig[T] { return Config(absAddr.Stack.ConfigAddr(), absAddr.Item) } diff --git a/internal/stacks/stackaddrs/input_variable.go b/internal/stacks/stackaddrs/input_variable.go index c789b68792..1c6b6caca4 100644 --- a/internal/stacks/stackaddrs/input_variable.go +++ b/internal/stacks/stackaddrs/input_variable.go @@ -1,5 +1,7 @@ package stackaddrs +import "github.com/hashicorp/terraform/internal/collections" + type InputVariable struct { Name string } @@ -12,6 +14,13 @@ func (v InputVariable) String() string { return "var." + v.Name } +func (v InputVariable) UniqueKey() collections.UniqueKey[InputVariable] { + return v +} + +// An InputVariable is its own [collections.UniqueKey]. +func (InputVariable) IsUniqueKey(InputVariable) {} + // ConfigInputVariable places an [InputVariable] in the context of a particular [Stack]. type ConfigInputVariable = InStackConfig[InputVariable] diff --git a/internal/stacks/stackaddrs/local_value.go b/internal/stacks/stackaddrs/local_value.go index 8d747954cb..7365cc5573 100644 --- a/internal/stacks/stackaddrs/local_value.go +++ b/internal/stacks/stackaddrs/local_value.go @@ -1,5 +1,7 @@ package stackaddrs +import "github.com/hashicorp/terraform/internal/collections" + type LocalValue struct { Name string } @@ -12,6 +14,13 @@ func (v LocalValue) String() string { return "local." + v.Name } +func (v LocalValue) UniqueKey() collections.UniqueKey[LocalValue] { + return v +} + +// A LocalValue is its own [collections.UniqueKey]. +func (LocalValue) IsUniqueKey(LocalValue) {} + // ConfigLocalValue places a [LocalValue] in the context of a particular [Stack]. type ConfigLocalValue = InStackConfig[LocalValue] diff --git a/internal/stacks/stackaddrs/output_value.go b/internal/stacks/stackaddrs/output_value.go index 7d90b41ddd..f5cc78fb2f 100644 --- a/internal/stacks/stackaddrs/output_value.go +++ b/internal/stacks/stackaddrs/output_value.go @@ -1,5 +1,7 @@ package stackaddrs +import "github.com/hashicorp/terraform/internal/collections" + type OutputValue struct { Name string } @@ -11,6 +13,13 @@ func (v OutputValue) String() string { return "output." + v.Name } +func (v OutputValue) UniqueKey() collections.UniqueKey[OutputValue] { + return v +} + +// An OutputValue is its own [collections.UniqueKey]. +func (OutputValue) IsUniqueKey(OutputValue) {} + // ConfigOutputValue places an [OutputValue] in the context of a particular [Stack]. type ConfigOutputValue = InStackConfig[OutputValue] diff --git a/internal/stacks/stackaddrs/provider_config.go b/internal/stacks/stackaddrs/provider_config.go index 59c312e25f..bd4ba4ad90 100644 --- a/internal/stacks/stackaddrs/provider_config.go +++ b/internal/stacks/stackaddrs/provider_config.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/hashicorp/terraform/internal/addrs" + "github.com/hashicorp/terraform/internal/collections" ) // ProviderConfigRef is a reference-only address type representing a reference @@ -37,6 +38,13 @@ func (c ProviderConfig) String() string { return fmt.Sprintf("provider[%q].%s", c.Provider, c.Name) } +func (v ProviderConfig) UniqueKey() collections.UniqueKey[ProviderConfig] { + return v +} + +// A ProviderConfig is its own [collections.UniqueKey]. +func (ProviderConfig) IsUniqueKey(ProviderConfig) {} + // ConfigProviderConfig places a [ProviderConfig] in the context of a particular [Stack]. type ConfigProviderConfig = InStackConfig[ProviderConfig] @@ -61,6 +69,13 @@ func (c ProviderConfigInstance) String() string { return c.ProviderConfig.String() + c.Key.String() } +func (v ProviderConfigInstance) UniqueKey() collections.UniqueKey[ProviderConfigInstance] { + return v +} + +// A ProviderConfigInstance is its own [collections.UniqueKey]. +func (ProviderConfigInstance) IsUniqueKey(ProviderConfigInstance) {} + // ConfigProviderConfigInstance places a [ProviderConfigInstance] in the context of a particular [Stack]. type ConfigProviderConfigInstance = InStackConfig[ProviderConfigInstance] diff --git a/internal/stacks/stackaddrs/stack.go b/internal/stacks/stackaddrs/stack.go index 5409ac5462..a1ee6f40ed 100644 --- a/internal/stacks/stackaddrs/stack.go +++ b/internal/stacks/stackaddrs/stack.go @@ -4,6 +4,7 @@ import ( "strings" "github.com/hashicorp/terraform/internal/addrs" + "github.com/hashicorp/terraform/internal/collections" ) // Stack represents the address of a stack within the tree of stacks. @@ -61,6 +62,15 @@ func (s Stack) String() string { return buf.String() } +func (s Stack) UniqueKey() collections.UniqueKey[Stack] { + return stackUniqueKey(s.String()) +} + +type stackUniqueKey string + +// IsUniqueKey implements collections.UniqueKey. +func (stackUniqueKey) IsUniqueKey(Stack) {} + // StackInstance represents the address of an instance of a stack within // the tree of stacks. // @@ -150,3 +160,12 @@ func (s StackInstance) String() string { } return buf.String() } + +func (s StackInstance) UniqueKey() collections.UniqueKey[StackInstance] { + return stackInstanceUniqueKey(s.String()) +} + +type stackInstanceUniqueKey string + +// IsUniqueKey implements collections.UniqueKey. +func (stackInstanceUniqueKey) IsUniqueKey(StackInstance) {} diff --git a/internal/stacks/stackaddrs/stack_call.go b/internal/stacks/stackaddrs/stack_call.go index 5329dffa77..2af45a5f10 100644 --- a/internal/stacks/stackaddrs/stack_call.go +++ b/internal/stacks/stackaddrs/stack_call.go @@ -1,6 +1,9 @@ package stackaddrs -import "github.com/hashicorp/terraform/internal/addrs" +import ( + "github.com/hashicorp/terraform/internal/addrs" + "github.com/hashicorp/terraform/internal/collections" +) // StackCall represents a call to an embedded stack. This is essentially the // address of a "stack" block in the configuration, before it's been fully @@ -17,6 +20,15 @@ func (c StackCall) String() string { return "stack." + c.Name } +func (c StackCall) UniqueKey() collections.UniqueKey[StackCall] { + return stackCallUniqueKey(c.String()) +} + +type stackCallUniqueKey string + +// IsUniqueKey implements collections.UniqueKey. +func (stackCallUniqueKey) IsUniqueKey(StackCall) {} + // ConfigStackCall represents a static stack call inside a particular [Stack]. type ConfigStackCall = InStackConfig[StackCall]