diff --git a/internal/stacks/stackaddrs/component.go b/internal/stacks/stackaddrs/component.go new file mode 100644 index 0000000000..3d5e8c5d6c --- /dev/null +++ b/internal/stacks/stackaddrs/component.go @@ -0,0 +1,35 @@ +package stackaddrs + +import "github.com/hashicorp/terraform/internal/addrs" + +// Component is the address of a "component" block within a stack config. +type Component struct { + Name string +} + +func (Component) referenceableSigil() {} +func (Component) inStackConfigSigil() {} +func (Component) inStackInstanceSigil() {} + +// ConfigComponent places a [Component] in the context of a particular [Stack]. +type ConfigComponent = InStackConfig[Component] + +// AbsComponent places a [Component] in the context of a particular [StackInstance]. +type AbsComponent = InStackInstance[Component] + +// ComponentInstance is the address of a dynamic instance of a component. +type ComponentInstance struct { + Component Component + Key addrs.InstanceKey +} + +func (ComponentInstance) inStackConfigSigil() {} +func (ComponentInstance) inStackInstanceSigil() {} + +// ConfigComponentInstance places a [ComponentInstance] in the context of a +// particular [Stack]. +type ConfigComponentInstance = InStackConfig[ComponentInstance] + +// AbsComponentInstance places a [ComponentInstance] in the context of a +// particular [StackInstance]. +type AbsComponentInstance = InStackInstance[ComponentInstance] diff --git a/internal/stacks/stackaddrs/doc.go b/internal/stacks/stackaddrs/doc.go new file mode 100644 index 0000000000..adb58bfbb9 --- /dev/null +++ b/internal/stacks/stackaddrs/doc.go @@ -0,0 +1,4 @@ +// Package stackaddrs builds on the top-level "addrs" package to provide the +// addresses for the extra layer of concepts that the stacks configuration +// language and its runtime are concerned about. +package stackaddrs diff --git a/internal/stacks/stackaddrs/in_stack.go b/internal/stacks/stackaddrs/in_stack.go new file mode 100644 index 0000000000..de3ff05754 --- /dev/null +++ b/internal/stacks/stackaddrs/in_stack.go @@ -0,0 +1,42 @@ +package stackaddrs + +// 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 { + inStackConfigSigil() +} + +// 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 { + inStackInstanceSigil() +} + +// 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 { + Stack Stack + Item T +} + +func Config[T StackItemConfig](stackAddr Stack, relAddr T) InStackConfig[T] { + return InStackConfig[T]{ + Stack: stackAddr, + Item: relAddr, + } +} + +// 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 { + Stack StackInstance + Item T +} + +func Absolute[T StackItemDynamic](stackAddr StackInstance, relAddr T) InStackInstance[T] { + return InStackInstance[T]{ + Stack: stackAddr, + Item: relAddr, + } +} diff --git a/internal/stacks/stackaddrs/input_variable.go b/internal/stacks/stackaddrs/input_variable.go new file mode 100644 index 0000000000..e4752fcd48 --- /dev/null +++ b/internal/stacks/stackaddrs/input_variable.go @@ -0,0 +1,15 @@ +package stackaddrs + +type InputVariable struct { + Name string +} + +func (InputVariable) referenceableSigil() {} +func (InputVariable) inStackConfigSigil() {} +func (InputVariable) inStackInstanceSigil() {} + +// ConfigInputVariable places an [InputVariable] in the context of a particular [Stack]. +type ConfigInputVariable = InStackConfig[InputVariable] + +// AbsInputVariable places an [InputVariable] in the context of a particular [StackInstance]. +type AbsInputVariable = InStackInstance[InputVariable] diff --git a/internal/stacks/stackaddrs/local_value.go b/internal/stacks/stackaddrs/local_value.go new file mode 100644 index 0000000000..1d6b23def1 --- /dev/null +++ b/internal/stacks/stackaddrs/local_value.go @@ -0,0 +1,15 @@ +package stackaddrs + +type LocalValue struct { + Name string +} + +func (LocalValue) referenceableSigil() {} +func (LocalValue) inStackConfigSigil() {} +func (LocalValue) inStackInstanceSigil() {} + +// ConfigLocalValue places a [LocalValue] in the context of a particular [Stack]. +type ConfigLocalValue = InStackConfig[LocalValue] + +// AbsLocalValue places a [LocalValue] in the context of a particular [StackInstance]. +type AbsLocalValue = InStackInstance[LocalValue] diff --git a/internal/stacks/stackaddrs/output_value.go b/internal/stacks/stackaddrs/output_value.go new file mode 100644 index 0000000000..db9fe93d35 --- /dev/null +++ b/internal/stacks/stackaddrs/output_value.go @@ -0,0 +1,14 @@ +package stackaddrs + +type OutputValue struct { + Name string +} + +func (OutputValue) inStackConfigSigil() {} +func (OutputValue) inStackInstanceSigil() {} + +// ConfigOutputValue places an [OutputValue] in the context of a particular [Stack]. +type ConfigOutputValue = InStackConfig[OutputValue] + +// AbsOutputValue places an [OutputValue] in the context of a particular [StackInstance]. +type AbsOutputValue = InStackInstance[OutputValue] diff --git a/internal/stacks/stackaddrs/provider_config.go b/internal/stacks/stackaddrs/provider_config.go new file mode 100644 index 0000000000..bb3de4e633 --- /dev/null +++ b/internal/stacks/stackaddrs/provider_config.go @@ -0,0 +1,51 @@ +package stackaddrs + +import ( + "github.com/hashicorp/terraform/internal/addrs" +) + +// ProviderConfigRef is a reference-only address type representing a reference +// to a particular provider configuration using its local name, since local +// name is how we refer to providers when they appear in expressions. +// +// The referent of a ProviderConfigRef is a [ProviderConfig], so resolving +// the reference will always require a lookup table from local name to +// fully-qualified provider address. +type ProviderConfigRef struct { + ProviderLocalName string + Name string +} + +func (ProviderConfigRef) referenceableSigil() {} + +// ProviderConfig is the address of a "provider" block in a stack configuration. +type ProviderConfig struct { + Provider addrs.Provider + Name string +} + +func (ProviderConfig) inStackConfigSigil() {} +func (ProviderConfig) inStackInstanceSigil() {} + +// ConfigProviderConfig places a [ProviderConfig] in the context of a particular [Stack]. +type ConfigProviderConfig = InStackConfig[ProviderConfig] + +// AbsProviderConfig places a [ProviderConfig] in the context of a particular [StackInstance]. +type AbsProviderConfig = InStackInstance[ProviderConfig] + +// ProviderConfigInstance is the address of a specific provider configuration, +// of which there might potentially be many associated with a given +// [ProviderConfig] if that block uses the "for_each" argument. +type ProviderConfigInstance struct { + ProviderConfig ProviderConfig + Key addrs.InstanceKey +} + +func (ProviderConfigInstance) inStackConfigSigil() {} +func (ProviderConfigInstance) inStackInstanceSigil() {} + +// ConfigProviderConfigInstance places a [ProviderConfigInstance] in the context of a particular [Stack]. +type ConfigProviderConfigInstance = InStackConfig[ProviderConfigInstance] + +// AbsProviderConfigInstance places a [ProviderConfigInstance] in the context of a particular [StackInstance]. +type AbsProviderConfigInstance = InStackInstance[ProviderConfigInstance] diff --git a/internal/stacks/stackaddrs/referenceable.go b/internal/stacks/stackaddrs/referenceable.go new file mode 100644 index 0000000000..8636e024ab --- /dev/null +++ b/internal/stacks/stackaddrs/referenceable.go @@ -0,0 +1,13 @@ +package stackaddrs + +// Referenceable is a type set containing all address types that can be +// the target of an expression-based reference within a particular stack. +type Referenceable interface { + referenceableSigil() +} + +var _ Referenceable = Component{} +var _ Referenceable = StackCall{} +var _ Referenceable = InputVariable{} +var _ Referenceable = LocalValue{} +var _ Referenceable = ProviderConfigRef{} diff --git a/internal/stacks/stackaddrs/stack.go b/internal/stacks/stackaddrs/stack.go new file mode 100644 index 0000000000..9eaeb0de56 --- /dev/null +++ b/internal/stacks/stackaddrs/stack.go @@ -0,0 +1,97 @@ +package stackaddrs + +import ( + "github.com/hashicorp/terraform/internal/addrs" +) + +// Stack represents the address of a stack within the tree of stacks. +// +// The root stack [RootStack] represents the top-level stack and then any +// other value of this type represents an embedded stack descending from it. +type Stack []StackStep + +type StackStep struct { + Name string +} + +var RootStack Stack + +// IsRoot returns true if this object represents the root stack, or false +// otherwise. +func (s Stack) IsRoot() bool { + return len(s) == 0 +} + +// Parent returns the parent of the reciever, or panics if the receiver is +// representing the root stack. +func (s Stack) Parent() Stack { + newLen := len(s) - 1 + if newLen < 0 { + panic("root stack has no parent") + } + return s[:newLen:newLen] +} + +// Child constructs the address of an embedded stack that's a child of the +// receiver. +func (s Stack) Child(name string) Stack { + ret := make([]StackStep, len(s), len(s)+1) + copy(ret, s) + return append(ret, StackStep{name}) +} + +// StackInstance represents the address of an instance of a stack within +// the tree of stacks. +// +// [RootStackInstance] represents the singleton instance of the top-level stack +// and then any other value of this type represents an instance of an embedded +// stack descending from it. +type StackInstance []StackInstanceStep + +type StackInstanceStep struct { + Name string + Key addrs.InstanceKey +} + +var RootStackInstance StackInstance + +// IsRoot returns true if this object represents the singleton instance of the +// root stack, or false otherwise. +func (s StackInstance) IsRoot() bool { + return len(s) == 0 +} + +// Parent returns the parent of the reciever, or panics if the receiver is +// representing the root stack. +func (s StackInstance) Parent() StackInstance { + newLen := len(s) - 1 + if newLen < 0 { + panic("root stack has no parent") + } + return s[:newLen:newLen] +} + +// Child constructs the address of an embedded stack that's a child of the +// receiver. +func (s StackInstance) Child(name string, key addrs.InstanceKey) StackInstance { + ret := make([]StackInstanceStep, len(s), len(s)+1) + copy(ret, s) + return append(ret, StackInstanceStep{ + Name: name, + Key: key, + }) +} + +// Call returns the address of the embedded stack call that the receiever +// belongs to, or panics if the receiver is the root module since the root +// module is called only implicitly. +func (s StackInstance) Call() AbsStackCall { + last := s[len(s)-1] + si := s[: len(s)-1 : len(s)-1] + return AbsStackCall{ + Stack: si, + Item: StackCall{ + Name: last.Name, + }, + } +} diff --git a/internal/stacks/stackaddrs/stack_call.go b/internal/stacks/stackaddrs/stack_call.go new file mode 100644 index 0000000000..92d24a4f3a --- /dev/null +++ b/internal/stacks/stackaddrs/stack_call.go @@ -0,0 +1,30 @@ +package stackaddrs + +import "github.com/hashicorp/terraform/internal/addrs" + +// 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 +// expanded into zero or more instances. +type StackCall struct { + Name string +} + +func (StackCall) referenceableSigil() {} +func (StackCall) inStackConfigSigil() {} +func (StackCall) inStackInstanceSigil() {} + +// ConfigStackCall represents a static stack call inside a particular [Stack]. +type ConfigStackCall = InStackConfig[StackCall] + +// AbsStackCall represents an instance of a stack call inside a particular +// [StackInstance[. +type AbsStackCall = InStackInstance[StackCall] + +func AbsStackCallInstance(call AbsStackCall, key addrs.InstanceKey) StackInstance { + ret := make(StackInstance, len(call.Stack), len(call.Stack)+1) + copy(ret, call.Stack) + return append(ret, StackInstanceStep{ + Name: call.Item.Name, + Key: key, + }) +} diff --git a/internal/stacks/stackaddrs/targetable.go b/internal/stacks/stackaddrs/targetable.go new file mode 100644 index 0000000000..70ba7a2a05 --- /dev/null +++ b/internal/stacks/stackaddrs/targetable.go @@ -0,0 +1,24 @@ +package stackaddrs + +import ( + "github.com/hashicorp/terraform/internal/addrs" +) + +// Targetable is the stacks analog to [addrs.Targetable], representing something +// that can be "targeted" inside a stack configuration. +type Targetable interface { + targetableSigil() +} + +// ComponentTargetable is an adapter type that makes everything that's +// targetable in the main Terraform language also targetable through a +// component instance when in a stack configuration. +// +// To represent targeting an entire component, place [addrs.RootModuleInstance] +// in field Item to describe targeting the component's root module. +type ComponentTargetable[T addrs.Targetable] struct { + Component AbsComponentInstance + Item T +} + +func (ComponentTargetable[T]) targetableSigil() {}