| // Copyright IBM Corp. 2014, 2026 | |
| // SPDX-License-Identifier: BUSL-1.1 | |
| package stackeval | |
| import ( | |
| "context" | |
| "fmt" | |
| "sync" | |
| "time" | |
| "github.com/hashicorp/hcl/v2" | |
| "github.com/zclconf/go-cty/cty" | |
| "github.com/hashicorp/terraform/internal/addrs" | |
| "github.com/hashicorp/terraform/internal/collections" | |
| "github.com/hashicorp/terraform/internal/instances" | |
| "github.com/hashicorp/terraform/internal/lang" | |
| "github.com/hashicorp/terraform/internal/stacks/stackaddrs" | |
| "github.com/hashicorp/terraform/internal/stacks/stackconfig" | |
| "github.com/hashicorp/terraform/internal/stacks/stackplan" | |
| "github.com/hashicorp/terraform/internal/tfdiags" | |
| ) | |
| // StackConfig represents a stack as represented in the configuration: either the | |
| // root stack or one of the embedded stacks before it's been expanded into | |
| // individual instances. | |
| // | |
| // After instance expansion we use [StackInstance] to represent each of the | |
| // individual instances. | |
| type StackConfig struct { | |
| addr stackaddrs.Stack | |
| config *stackconfig.ConfigNode | |
| parent *StackConfig | |
| main *Main | |
| // The remaining fields are where we memoize related objects that we've | |
| // constructed and returned. Must lock "mu" before interacting with these. | |
| mu sync.Mutex | |
| children map[stackaddrs.StackStep]*StackConfig | |
| inputVariables map[stackaddrs.InputVariable]*InputVariableConfig | |
| localValues map[stackaddrs.LocalValue]*LocalValueConfig | |
| outputValues map[stackaddrs.OutputValue]*OutputValueConfig | |
| stackCalls map[stackaddrs.StackCall]*StackCallConfig | |
| removedStackCalls collections.Map[stackaddrs.ConfigStackCall, []*RemovedStackCallConfig] | |
| components map[stackaddrs.Component]*ComponentConfig | |
| removedComponents collections.Map[stackaddrs.ConfigComponent, []*RemovedComponentConfig] | |
| providers map[stackaddrs.ProviderConfig]*ProviderConfig | |
| } | |
| var ( | |
| _ ExpressionScope = (*StackConfig)(nil) | |
| _ StaticEvaler = (*StackConfig)(nil) | |
| ) | |
| func newStackConfig(main *Main, addr stackaddrs.Stack, parent *StackConfig, config *stackconfig.ConfigNode) *StackConfig { | |
| return &StackConfig{ | |
| addr: addr, | |
| parent: parent, | |
| config: config, | |
| main: main, | |
| children: make(map[stackaddrs.StackStep]*StackConfig, len(config.Children)), | |
| inputVariables: make(map[stackaddrs.InputVariable]*InputVariableConfig, len(config.Stack.Declarations.InputVariables)), | |
| localValues: make(map[stackaddrs.LocalValue]*LocalValueConfig, len(config.Stack.Declarations.LocalValues)), | |
| outputValues: make(map[stackaddrs.OutputValue]*OutputValueConfig, len(config.Stack.Declarations.OutputValues)), | |
| stackCalls: make(map[stackaddrs.StackCall]*StackCallConfig, len(config.Stack.Declarations.EmbeddedStacks)), | |
| removedStackCalls: collections.NewMap[stackaddrs.ConfigStackCall, []*RemovedStackCallConfig](), | |
| components: make(map[stackaddrs.Component]*ComponentConfig, len(config.Stack.Declarations.Components)), | |
| removedComponents: collections.NewMap[stackaddrs.ConfigComponent, []*RemovedComponentConfig](), | |
| providers: make(map[stackaddrs.ProviderConfig]*ProviderConfig, len(config.Stack.Declarations.ProviderConfigs)), | |
| } | |
| } | |
| // ChildConfig returns a [StackConfig] representing the embedded stack matching | |
| // the given address step, or nil if there is no such stack. | |
| func (s *StackConfig) ChildConfig(step stackaddrs.StackStep) *StackConfig { | |
| s.mu.Lock() | |
| defer s.mu.Unlock() | |
| ret, ok := s.children[step] | |
| if !ok { | |
| childNode, ok := s.config.Children[step.Name] | |
| if !ok { | |
| return nil | |
| } | |
| childAddr := s.addr.Child(step.Name) | |
| s.children[step] = newStackConfig(s.main, childAddr, s, childNode) | |
| ret = s.children[step] | |
| } | |
| return ret | |
| } | |
| func (s *StackConfig) ChildConfigs() map[stackaddrs.StackStep]*StackConfig { | |
| if len(s.config.Children) == 0 { | |
| return nil | |
| } | |
| ret := make(map[stackaddrs.StackStep]*StackConfig, len(s.config.Children)) | |
| for n := range s.config.Children { | |
| stepAddr := stackaddrs.StackStep{Name: n} | |
| ret[stepAddr] = s.ChildConfig(stepAddr) | |
| } | |
| return ret | |
| } | |
| // InputVariables returns a map of the objects representing all of the | |
| // input variables declared inside this stack configuration. | |
| func (s *StackConfig) InputVariables() map[stackaddrs.InputVariable]*InputVariableConfig { | |
| if len(s.config.Stack.InputVariables) == 0 { | |
| return nil | |
| } | |
| ret := make(map[stackaddrs.InputVariable]*InputVariableConfig, len(s.config.Stack.InputVariables)) | |
| for name := range s.config.Stack.InputVariables { | |
| addr := stackaddrs.InputVariable{Name: name} | |
| ret[addr] = s.InputVariable(addr) | |
| } | |
| return ret | |
| } | |
| // InputVariable returns an [InputVariableConfig] representing the input | |
| // variable declared within this stack config that matches the given | |
| // address, or nil if there is no such declaration. | |
| func (s *StackConfig) InputVariable(addr stackaddrs.InputVariable) *InputVariableConfig { | |
| s.mu.Lock() | |
| defer s.mu.Unlock() | |
| ret, ok := s.inputVariables[addr] | |
| if !ok { | |
| cfg, ok := s.config.Stack.InputVariables[addr.Name] | |
| if !ok { | |
| return nil | |
| } | |
| cfgAddr := stackaddrs.Config(s.addr, addr) | |
| ret = newInputVariableConfig(s.main, cfgAddr, s, cfg) | |
| s.inputVariables[addr] = ret | |
| } | |
| return ret | |
| } | |
| // LocalValues returns a map of the objects representing all of the | |
| // local values declared inside this stack configuration. | |
| func (s *StackConfig) LocalValues() map[stackaddrs.LocalValue]*LocalValueConfig { | |
| if len(s.config.Stack.LocalValues) == 0 { | |
| return nil | |
| } | |
| ret := make(map[stackaddrs.LocalValue]*LocalValueConfig, len(s.config.Stack.LocalValues)) | |
| for name := range s.config.Stack.LocalValues { | |
| addr := stackaddrs.LocalValue{Name: name} | |
| ret[addr] = s.LocalValue(addr) | |
| } | |
| return ret | |
| } | |
| // LocalValue returns an [LocalValueConfig] representing the input | |
| // variable declared within this stack config that matches the given | |
| // address, or nil if there is no such declaration. | |
| func (s *StackConfig) LocalValue(addr stackaddrs.LocalValue) *LocalValueConfig { | |
| s.mu.Lock() | |
| defer s.mu.Unlock() | |
| ret, ok := s.localValues[addr] | |
| if !ok { | |
| cfg, ok := s.config.Stack.LocalValues[addr.Name] | |
| if !ok { | |
| return nil | |
| } | |
| cfgAddr := stackaddrs.Config(s.addr, addr) | |
| ret = newLocalValueConfig(s.main, cfgAddr, s, cfg) | |
| s.localValues[addr] = ret | |
| } | |
| return ret | |
| } | |
| // OutputValue returns an [OutputValueConfig] representing the output | |
| // value declared within this stack config that matches the given | |
| // address, or nil if there is no such declaration. | |
| func (s *StackConfig) OutputValue(addr stackaddrs.OutputValue) *OutputValueConfig { | |
| s.mu.Lock() | |
| defer s.mu.Unlock() | |
| ret, ok := s.outputValues[addr] | |
| if !ok { | |
| cfg, ok := s.config.Stack.OutputValues[addr.Name] | |
| if !ok { | |
| return nil | |
| } | |
| cfgAddr := stackaddrs.Config(s.addr, addr) | |
| ret = newOutputValueConfig(s.main, cfgAddr, s, cfg) | |
| s.outputValues[addr] = ret | |
| } | |
| return ret | |
| } | |
| // OutputValues returns a map of the objects representing all of the | |
| // output values declared inside this stack configuration. | |
| func (s *StackConfig) OutputValues() map[stackaddrs.OutputValue]*OutputValueConfig { | |
| if len(s.config.Stack.OutputValues) == 0 { | |
| return nil | |
| } | |
| ret := make(map[stackaddrs.OutputValue]*OutputValueConfig, len(s.config.Stack.OutputValues)) | |
| for name := range s.config.Stack.OutputValues { | |
| addr := stackaddrs.OutputValue{Name: name} | |
| ret[addr] = s.OutputValue(addr) | |
| } | |
| return ret | |
| } | |
| // ResultType returns the type of the result object that will be produced | |
| // by this stack configuration, based on the output values declared within | |
| // it. | |
| func (s *StackConfig) ResultType() cty.Type { | |
| os := s.OutputValues() | |
| atys := make(map[string]cty.Type, len(os)) | |
| for addr, o := range os { | |
| atys[addr.Name] = o.ValueTypeConstraint() | |
| } | |
| return cty.Object(atys) | |
| } | |
| // Providers returns a map of the objects representing all of the provider | |
| // configurations declared inside this stack configuration. | |
| func (s *StackConfig) Providers() map[stackaddrs.ProviderConfig]*ProviderConfig { | |
| if len(s.config.Stack.ProviderConfigs) == 0 { | |
| return nil | |
| } | |
| ret := make(map[stackaddrs.ProviderConfig]*ProviderConfig, len(s.config.Stack.ProviderConfigs)) | |
| for configAddr := range s.config.Stack.ProviderConfigs { | |
| provider, ok := s.config.Stack.RequiredProviders.ProviderForLocalName(configAddr.LocalName) | |
| if !ok { | |
| // Then we are missing a provider declaration, this will be caught | |
| // elsewhere so we'll just skip it here. | |
| continue | |
| } | |
| addr := stackaddrs.ProviderConfig{ | |
| Provider: provider, | |
| Name: configAddr.Alias, | |
| } | |
| ret[addr] = s.Provider(addr) | |
| } | |
| return ret | |
| } | |
| // Provider returns a [ProviderConfig] representing the provider configuration | |
| // block within the stack configuration that matches the given address, | |
| // or nil if there is no such declaration. | |
| func (s *StackConfig) Provider(addr stackaddrs.ProviderConfig) *ProviderConfig { | |
| s.mu.Lock() | |
| defer s.mu.Unlock() | |
| ret, ok := s.providers[addr] | |
| if !ok { | |
| localName, ok := s.config.Stack.RequiredProviders.LocalNameForProvider(addr.Provider) | |
| if !ok { | |
| return nil | |
| } | |
| // FIXME: stackconfig package currently uses addrs.LocalProviderConfig | |
| // instead of stackaddrs.ProviderConfigRef. | |
| configAddr := addrs.LocalProviderConfig{ | |
| LocalName: localName, | |
| Alias: addr.Name, | |
| } | |
| cfg, ok := s.config.Stack.ProviderConfigs[configAddr] | |
| if !ok { | |
| return nil | |
| } | |
| cfgAddr := stackaddrs.Config(s.addr, addr) | |
| ret = newProviderConfig(s.main, cfgAddr, s, cfg) | |
| s.providers[addr] = ret | |
| } | |
| return ret | |
| } | |
| // ProviderByLocalAddr returns a [ProviderConfig] representing the provider | |
| // configuration block within the stack configuration that matches the given | |
| // local address, or nil if there is no such declaration. | |
| // | |
| // This is equivalent to calling [Provider] just using a reference address | |
| // instead of a config address. | |
| func (s *StackConfig) ProviderByLocalAddr(localAddr stackaddrs.ProviderConfigRef) *ProviderConfig { | |
| s.mu.Lock() | |
| defer s.mu.Unlock() | |
| provider, ok := s.config.Stack.RequiredProviders.ProviderForLocalName(localAddr.ProviderLocalName) | |
| if !ok { | |
| return nil | |
| } | |
| addr := stackaddrs.ProviderConfig{ | |
| Provider: provider, | |
| Name: localAddr.Name, | |
| } | |
| ret, ok := s.providers[addr] | |
| if !ok { | |
| configAddr := addrs.LocalProviderConfig{ | |
| LocalName: localAddr.ProviderLocalName, | |
| Alias: localAddr.Name, | |
| } | |
| cfg, ok := s.config.Stack.ProviderConfigs[configAddr] | |
| if !ok { | |
| return nil | |
| } | |
| cfgAddr := stackaddrs.Config(s.addr, addr) | |
| ret = newProviderConfig(s.main, cfgAddr, s, cfg) | |
| s.providers[addr] = ret | |
| } | |
| return ret | |
| } | |
| // ProviderLocalName returns the local name used for the given provider | |
| // in this particular stack configuration, based on the declarations in | |
| // the required_providers configuration block. | |
| // | |
| // If the second return value is false then there is no local name declared | |
| // for the given provider, and so the first return value is invalid. | |
| func (s *StackConfig) ProviderLocalName(addr addrs.Provider) (string, bool) { | |
| return s.config.Stack.RequiredProviders.LocalNameForProvider(addr) | |
| } | |
| // ProviderForLocalName returns the provider for the given local name in this | |
| // particular stack configuration, based on the declarations in the | |
| // required_providers configuration block. | |
| // | |
| // If the second return value is false then there is no provider declared | |
| // for the given local name, and so the first return value is invalid. | |
| func (s *StackConfig) ProviderForLocalName(localName string) (addrs.Provider, bool) { | |
| return s.config.Stack.RequiredProviders.ProviderForLocalName(localName) | |
| } | |
| // StackCall returns a [StackCallConfig] representing the "stack" block | |
| // matching the given address declared within this stack config, or nil if | |
| // there is no such declaration. | |
| func (s *StackConfig) StackCall(addr stackaddrs.StackCall) *StackCallConfig { | |
| s.mu.Lock() | |
| defer s.mu.Unlock() | |
| ret, ok := s.stackCalls[addr] | |
| if !ok { | |
| cfg, ok := s.config.Stack.EmbeddedStacks[addr.Name] | |
| if !ok { | |
| return nil | |
| } | |
| cfgAddr := stackaddrs.Config(s.addr, addr) | |
| ret = newStackCallConfig(s.main, cfgAddr, s, cfg) | |
| s.stackCalls[addr] = ret | |
| } | |
| return ret | |
| } | |
| // StackCalls returns a map of objects representing all of the embedded stack | |
| // calls inside this stack configuration. | |
| func (s *StackConfig) StackCalls() map[stackaddrs.StackCall]*StackCallConfig { | |
| if len(s.config.Children) == 0 { | |
| return nil | |
| } | |
| ret := make(map[stackaddrs.StackCall]*StackCallConfig, len(s.config.Children)) | |
| for n := range s.config.Stack.EmbeddedStacks { | |
| stepAddr := stackaddrs.StackCall{Name: n} | |
| ret[stepAddr] = s.StackCall(stepAddr) | |
| } | |
| return ret | |
| } | |
| func (s *StackConfig) RemovedStackCall(addr stackaddrs.ConfigStackCall) []*RemovedStackCallConfig { | |
| s.mu.Lock() | |
| defer s.mu.Unlock() | |
| ret, ok := s.removedStackCalls.GetOk(addr) | |
| if !ok { | |
| for _, cfg := range s.config.Stack.RemovedEmbeddedStacks.Get(addr) { | |
| removed := newRemovedStackCallConfig(s.main, addr, s, cfg) | |
| ret = append(ret, removed) | |
| } | |
| s.removedStackCalls.Put(addr, ret) | |
| } | |
| return ret | |
| } | |
| func (s *StackConfig) RemovedStackCalls() collections.Map[stackaddrs.ConfigStackCall, []*RemovedStackCallConfig] { | |
| ret := collections.NewMap[stackaddrs.ConfigStackCall, []*RemovedStackCallConfig]() | |
| for addr := range s.config.Stack.RemovedEmbeddedStacks.All() { | |
| ret.Put(addr, s.RemovedStackCall(addr)) | |
| } | |
| return ret | |
| } | |
| // Component returns a [ComponentConfig] representing the component call | |
| // declared within this stack config that matches the given address, or nil if | |
| // there is no such declaration. | |
| func (s *StackConfig) Component(addr stackaddrs.Component) *ComponentConfig { | |
| s.mu.Lock() | |
| defer s.mu.Unlock() | |
| ret, ok := s.components[addr] | |
| if !ok { | |
| cfg, ok := s.config.Stack.Components[addr.Name] | |
| if !ok { | |
| return nil | |
| } | |
| cfgAddr := stackaddrs.Config(s.addr, addr) | |
| ret = newComponentConfig(s.main, cfgAddr, s, cfg) | |
| s.components[addr] = ret | |
| } | |
| return ret | |
| } | |
| // Components returns a map of the objects representing all of the | |
| // component calls declared inside this stack configuration. | |
| func (s *StackConfig) Components() map[stackaddrs.Component]*ComponentConfig { | |
| if len(s.config.Stack.Components) == 0 { | |
| return nil | |
| } | |
| ret := make(map[stackaddrs.Component]*ComponentConfig, len(s.config.Stack.Components)) | |
| for name := range s.config.Stack.Components { | |
| addr := stackaddrs.Component{Name: name} | |
| ret[addr] = s.Component(addr) | |
| } | |
| return ret | |
| } | |
| // RemovedComponent returns a [RemovedComponentConfig] representing the | |
| // component call declared within this stack config that matches the given | |
| // address, or nil if there is no such declaration. | |
| func (s *StackConfig) RemovedComponent(addr stackaddrs.ConfigComponent) []*RemovedComponentConfig { | |
| s.mu.Lock() | |
| defer s.mu.Unlock() | |
| ret, ok := s.removedComponents.GetOk(addr) | |
| if !ok { | |
| for _, cfg := range s.config.Stack.RemovedComponents.Get(addr) { | |
| cfgAddr := stackaddrs.ConfigComponent{ | |
| Stack: append(s.addr, addr.Stack...), | |
| Item: addr.Item, | |
| } | |
| removed := newRemovedComponentConfig(s.main, cfgAddr, s, cfg) | |
| ret = append(ret, removed) | |
| } | |
| s.removedComponents.Put(addr, ret) | |
| } | |
| return ret | |
| } | |
| // RemovedComponents returns a map of the objects representing all of the | |
| // removed calls declared inside this stack configuration. | |
| func (s *StackConfig) RemovedComponents() collections.Map[stackaddrs.ConfigComponent, []*RemovedComponentConfig] { | |
| ret := collections.NewMap[stackaddrs.ConfigComponent, []*RemovedComponentConfig]() | |
| for addr := range s.config.Stack.RemovedComponents.All() { | |
| ret.Put(addr, s.RemovedComponent(addr)) | |
| } | |
| return ret | |
| } | |
| // ResolveExpressionReference implements ExpressionScope, providing the | |
| // global scope for evaluation within an unexpanded stack during the validate | |
| // phase. | |
| func (s *StackConfig) ResolveExpressionReference(ctx context.Context, ref stackaddrs.Reference) (Referenceable, tfdiags.Diagnostics) { | |
| return s.resolveExpressionReference(ctx, ref, nil, instances.RepetitionData{}) | |
| } | |
| // resolveExpressionReference is the shared implementation of various | |
| // validation-time ResolveExpressionReference methods, factoring out all | |
| // of the common parts into one place. | |
| func (s *StackConfig) resolveExpressionReference( | |
| ctx context.Context, | |
| ref stackaddrs.Reference, | |
| selfAddr stackaddrs.Referenceable, | |
| repetition instances.RepetitionData, | |
| ) (Referenceable, tfdiags.Diagnostics) { | |
| var diags tfdiags.Diagnostics | |
| // "Test-only globals" is a special affordance we have only when running | |
| // unit tests in this package. The function called in this branch will | |
| // return an error itself if we're not running in a suitable test situation. | |
| if addr, ok := ref.Target.(stackaddrs.TestOnlyGlobal); ok { | |
| return s.main.resolveTestOnlyGlobalReference(addr, ref.SourceRange) | |
| } | |
| // TODO: Most of the below would benefit from "Did you mean..." suggestions | |
| // when something is missing but there's a similarly-named object nearby. | |
| switch addr := ref.Target.(type) { | |
| case stackaddrs.InputVariable: | |
| ret := s.InputVariable(addr) | |
| if ret == nil { | |
| diags = diags.Append(&hcl.Diagnostic{ | |
| Severity: hcl.DiagError, | |
| Summary: "Reference to undeclared input variable", | |
| Detail: fmt.Sprintf("There is no variable %q block declared in this stack.", addr.Name), | |
| Subject: ref.SourceRange.ToHCL().Ptr(), | |
| }) | |
| return nil, diags | |
| } | |
| return ret, diags | |
| case stackaddrs.LocalValue: | |
| ret := s.LocalValue(addr) | |
| if ret == nil { | |
| diags = diags.Append(&hcl.Diagnostic{ | |
| Severity: hcl.DiagError, | |
| Summary: "Reference to undeclared local value", | |
| Detail: fmt.Sprintf("There is no local %q declared in this stack.", addr.Name), | |
| Subject: ref.SourceRange.ToHCL().Ptr(), | |
| }) | |
| return nil, diags | |
| } | |
| return ret, diags | |
| case stackaddrs.Component: | |
| ret := s.Component(addr) | |
| if ret == nil { | |
| diags = diags.Append(&hcl.Diagnostic{ | |
| Severity: hcl.DiagError, | |
| Summary: "Reference to undeclared component", | |
| Detail: fmt.Sprintf("There is no component %q block declared in this stack.", addr.Name), | |
| Subject: ref.SourceRange.ToHCL().Ptr(), | |
| }) | |
| return nil, diags | |
| } | |
| return ret, diags | |
| case stackaddrs.StackCall: | |
| ret := s.StackCall(addr) | |
| if ret == nil { | |
| diags = diags.Append(&hcl.Diagnostic{ | |
| Severity: hcl.DiagError, | |
| Summary: "Reference to undeclared embedded stack", | |
| Detail: fmt.Sprintf("There is no stack %q block declared in this stack.", addr.Name), | |
| Subject: ref.SourceRange.ToHCL().Ptr(), | |
| }) | |
| return nil, diags | |
| } | |
| return ret, diags | |
| case stackaddrs.ProviderConfigRef: | |
| ret := s.ProviderByLocalAddr(addr) | |
| if ret == nil { | |
| diags = diags.Append(&hcl.Diagnostic{ | |
| Severity: hcl.DiagError, | |
| Summary: "Reference to undeclared provider configuration", | |
| Detail: fmt.Sprintf("There is no provider %q %q block declared in this stack.", addr.ProviderLocalName, addr.Name), | |
| Subject: ref.SourceRange.ToHCL().Ptr(), | |
| }) | |
| return nil, diags | |
| } | |
| return ret, diags | |
| case stackaddrs.ContextualRef: | |
| switch addr { | |
| case stackaddrs.EachKey: | |
| if repetition.EachKey == cty.NilVal { | |
| diags = diags.Append(&hcl.Diagnostic{ | |
| Severity: hcl.DiagError, | |
| Summary: "Invalid 'each' reference", | |
| Detail: "The special symbol 'each' is not defined in this location. This symbol is valid only inside multi-instance blocks that use the 'for_each' argument.", | |
| Subject: ref.SourceRange.ToHCL().Ptr(), | |
| }) | |
| return nil, diags | |
| } | |
| return JustValue{repetition.EachKey}, diags | |
| case stackaddrs.EachValue: | |
| if repetition.EachValue == cty.NilVal { | |
| diags = diags.Append(&hcl.Diagnostic{ | |
| Severity: hcl.DiagError, | |
| Summary: "Invalid 'each' reference", | |
| Detail: "The special symbol 'each' is not defined in this location. This symbol is valid only inside multi-instance blocks that use the 'for_each' argument.", | |
| Subject: ref.SourceRange.ToHCL().Ptr(), | |
| }) | |
| return nil, diags | |
| } | |
| return JustValue{repetition.EachValue}, diags | |
| case stackaddrs.CountIndex: | |
| if repetition.CountIndex == cty.NilVal { | |
| diags = diags.Append(&hcl.Diagnostic{ | |
| Severity: hcl.DiagError, | |
| Summary: "Invalid 'count' reference", | |
| Detail: "The special symbol 'count' is not defined in this location. This symbol is valid only inside multi-instance blocks that use the 'count' argument.", | |
| Subject: ref.SourceRange.ToHCL().Ptr(), | |
| }) | |
| return nil, diags | |
| } | |
| return JustValue{repetition.CountIndex}, diags | |
| case stackaddrs.Self: | |
| if selfAddr != nil { | |
| // We'll just pretend the reference was to whatever "self" | |
| // is referring to, then. | |
| ref.Target = selfAddr | |
| return s.resolveExpressionReference(ctx, ref, nil, repetition) | |
| } else { | |
| diags = diags.Append(&hcl.Diagnostic{ | |
| Severity: hcl.DiagError, | |
| Summary: "Invalid 'self' reference", | |
| Detail: "The special symbol 'self' is not defined in this location.", | |
| Context: ref.SourceRange.ToHCL().Ptr(), | |
| }) | |
| return nil, diags | |
| } | |
| default: | |
| // The above should be exhaustive for all defined values of this type. | |
| panic(fmt.Sprintf("unsupported ContextualRef %#v", addr)) | |
| } | |
| default: | |
| diags = diags.Append(&hcl.Diagnostic{ | |
| Severity: hcl.DiagError, | |
| Summary: "Invalid reference", | |
| Detail: fmt.Sprintf("The object %s is not in scope at this location.", addr.String()), | |
| Subject: ref.SourceRange.ToHCL().Ptr(), | |
| }) | |
| return nil, diags | |
| } | |
| } | |
| // ExternalFunctions implements ExpressionScope. | |
| func (s *StackConfig) ExternalFunctions(ctx context.Context) (lang.ExternalFuncs, tfdiags.Diagnostics) { | |
| return s.main.ProviderFunctions(ctx, s) | |
| } | |
| // PlanTimestamp implements ExpressionScope, providing the timestamp at which | |
| // the current plan is being run. | |
| func (s *StackConfig) PlanTimestamp() time.Time { | |
| return s.main.PlanTimestamp() | |
| } | |
| // checkRequiredProviders validates that every provider declared in the | |
| // required_providers block that does NOT have an explicit "provider" block | |
| // in this stack is still present in the lock file and that the locked version | |
| // satisfies the declared version constraints. | |
| // | |
| // Providers that DO have an explicit "provider" block are validated separately | |
| // by ProviderConfig.checkValid, so we skip them here to avoid double-reporting. | |
| func (s *StackConfig) checkRequiredProviders(phase EvalPhase) tfdiags.Diagnostics { | |
| var diags tfdiags.Diagnostics | |
| depLocks := s.main.DependencyLocks(phase) | |
| if depLocks == nil { | |
| return diags | |
| } | |
| reqs := s.config.Stack.RequiredProviders | |
| if reqs == nil { | |
| return diags | |
| } | |
| for _, req := range reqs.Requirements { | |
| providerAddr := req.Provider | |
| // Skip providers that already have an explicit "provider" block — those | |
| // are checked by ProviderConfig.checkValid which runs separately. | |
| // A stack can have multiple "provider" blocks for the same type (using | |
| // different names/aliases), so we just need to know if any exist. | |
| localName, ok := reqs.LocalNameForProvider(providerAddr) | |
| if !ok { | |
| continue | |
| } | |
| hasProviderBlock := false | |
| for cfgAddr := range s.config.Stack.ProviderConfigs { | |
| if cfgAddr.LocalName == localName { | |
| hasProviderBlock = true | |
| break | |
| } | |
| } | |
| if hasProviderBlock { | |
| continue | |
| } | |
| pTy := s.main.ProviderType(providerAddr) | |
| lockfileDiags := CheckProviderInLockfile(*depLocks, pTy, req.VersionConstraints, nil) | |
| diags = diags.Append(lockfileDiags) | |
| } | |
| return diags | |
| } | |
| // tracingName implements StaticEvaler. | |
| func (s *StackConfig) tracingName() string { | |
| if s.addr.IsRoot() { | |
| return "root stack" | |
| } | |
| return s.addr.String() | |
| } | |
| // Validate implements Validatable. | |
| func (s *StackConfig) Validate(_ context.Context) tfdiags.Diagnostics { | |
| return s.checkRequiredProviders(ValidatePhase) | |
| } | |
| // PlanChanges implements Plannable. | |
| func (s *StackConfig) PlanChanges(_ context.Context) ([]stackplan.PlannedChange, tfdiags.Diagnostics) { | |
| return nil, s.checkRequiredProviders(PlanPhase) | |
| } |