mirror of
https://github.com/hashicorp/terraform.git
synced 2026-02-19 02:39:17 -05:00
400 lines
12 KiB
Go
400 lines
12 KiB
Go
// Copyright IBM Corp. 2014, 2026
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package stackconfig
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
|
"github.com/hashicorp/terraform/internal/collections"
|
|
"github.com/hashicorp/terraform/internal/stacks/stackaddrs"
|
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
|
)
|
|
|
|
// Declarations represent the various items that can be declared in a stack
|
|
// configuration.
|
|
//
|
|
// This just represents the fields that both [Stack] and [File] have in common,
|
|
// so we can share some code between the two.
|
|
type Declarations struct {
|
|
// EmbeddedStacks are calls to other stack configurations that should
|
|
// be treated as a part of the overall desired state produced from this
|
|
// stack. These are declared with "stack" blocks in the stack language.
|
|
EmbeddedStacks map[string]*EmbeddedStack
|
|
|
|
// Components are calls to trees of Terraform modules that represent the
|
|
// real infrastructure described by a stack.
|
|
Components map[string]*Component
|
|
|
|
// InputVariables, LocalValues, and OutputValues together represent all
|
|
// of the "named values" in the stack configuration, which are just glue
|
|
// to pass values between scopes or to factor out common expressions for
|
|
// reuse in multiple locations.
|
|
InputVariables map[string]*InputVariable
|
|
LocalValues map[string]*LocalValue
|
|
OutputValues map[string]*OutputValue
|
|
|
|
// RequiredProviders represents the single required_providers block
|
|
// that's allowed in any stack, declaring which providers this stack
|
|
// depends on and which versions of those providers it is compatible with.
|
|
RequiredProviders *ProviderRequirements
|
|
|
|
// ProviderConfigs are the provider configurations declared in this
|
|
// particular stack configuration. Other stack configurations in the
|
|
// overall tree might have their own provider configurations.
|
|
ProviderConfigs map[addrs.LocalProviderConfig]*ProviderConfig
|
|
|
|
// RemovedComponents is the list of components that have been removed from
|
|
// the configuration.
|
|
RemovedComponents collections.Map[stackaddrs.ConfigComponent, []*Removed]
|
|
|
|
// RemovedEmbeddedStacks is the list of embedded stacks that have been removed
|
|
// from the configuration.
|
|
RemovedEmbeddedStacks collections.Map[stackaddrs.ConfigStackCall, []*Removed]
|
|
}
|
|
|
|
func makeDeclarations() Declarations {
|
|
return Declarations{
|
|
EmbeddedStacks: make(map[string]*EmbeddedStack),
|
|
Components: make(map[string]*Component),
|
|
InputVariables: make(map[string]*InputVariable),
|
|
LocalValues: make(map[string]*LocalValue),
|
|
OutputValues: make(map[string]*OutputValue),
|
|
ProviderConfigs: make(map[addrs.LocalProviderConfig]*ProviderConfig),
|
|
RemovedComponents: collections.NewMap[stackaddrs.ConfigComponent, []*Removed](),
|
|
RemovedEmbeddedStacks: collections.NewMap[stackaddrs.ConfigStackCall, []*Removed](),
|
|
}
|
|
}
|
|
|
|
func (d *Declarations) addComponent(decl *Component) tfdiags.Diagnostics {
|
|
if decl == nil {
|
|
return nil
|
|
}
|
|
var diags tfdiags.Diagnostics
|
|
|
|
name := decl.Name
|
|
if existing, exists := d.Components[name]; exists {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Duplicate component declaration",
|
|
Detail: fmt.Sprintf(
|
|
"An component named %q was already declared at %s.",
|
|
name, existing.DeclRange.ToHCL(),
|
|
),
|
|
Subject: decl.DeclRange.ToHCL().Ptr(),
|
|
})
|
|
return diags
|
|
}
|
|
|
|
if blocks, exists := d.RemovedComponents.GetOk(stackaddrs.ConfigComponent{
|
|
Stack: nil,
|
|
Item: stackaddrs.Component{
|
|
Name: name,
|
|
},
|
|
}); exists {
|
|
for _, removed := range blocks {
|
|
if removed.From.Component.Index == nil {
|
|
// If a component has been removed, we should not also find it
|
|
// in the configuration.
|
|
//
|
|
// If the removed block has an index, then it's possible that
|
|
// only a specific instance was removed and not the whole thing.
|
|
// This is okay at this point, and will be validated more later.
|
|
// See the addRemoved method for more information.
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Component exists for removed block",
|
|
Detail: fmt.Sprintf(
|
|
"A removed block for component %q was declared without an index, but a component block with the same name was declared at %s.\n\nA removed block without an index indicates that the component and all instances were removed from the configuration, and this is not the case.",
|
|
name, decl.DeclRange.ToHCL(),
|
|
),
|
|
Subject: removed.DeclRange.ToHCL().Ptr(),
|
|
})
|
|
return diags
|
|
}
|
|
}
|
|
}
|
|
|
|
d.Components[name] = decl
|
|
return diags
|
|
}
|
|
|
|
func (d *Declarations) addEmbeddedStack(decl *EmbeddedStack) tfdiags.Diagnostics {
|
|
if decl == nil {
|
|
return nil
|
|
}
|
|
var diags tfdiags.Diagnostics
|
|
|
|
name := decl.Name
|
|
if existing, exists := d.EmbeddedStacks[name]; exists {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Duplicate embedded stack call",
|
|
Detail: fmt.Sprintf(
|
|
"An embedded stack call named %q was already declared at %s.",
|
|
name, existing.DeclRange.ToHCL(),
|
|
),
|
|
Subject: decl.DeclRange.ToHCL().Ptr(),
|
|
})
|
|
return diags
|
|
}
|
|
|
|
if blocks, exists := d.RemovedEmbeddedStacks.GetOk(stackaddrs.ConfigStackCall{
|
|
Stack: nil,
|
|
Item: stackaddrs.StackCall{
|
|
Name: name,
|
|
},
|
|
}); exists {
|
|
for _, removed := range blocks {
|
|
if removed.From.Stack[0].Index == nil {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Stack exists for removed block",
|
|
Detail: fmt.Sprintf(
|
|
"A removed block for stack %q was declared without an index, but a stack block with the same name was declared at %s.\n\nA removed block without an index indicates that the stack and all instances were removed from the configuration, and this is not the case.",
|
|
name, decl.DeclRange.ToHCL(),
|
|
),
|
|
Subject: removed.DeclRange.ToHCL().Ptr(),
|
|
})
|
|
return diags
|
|
}
|
|
}
|
|
}
|
|
|
|
d.EmbeddedStacks[name] = decl
|
|
return diags
|
|
}
|
|
|
|
func (d *Declarations) addInputVariable(decl *InputVariable) tfdiags.Diagnostics {
|
|
if decl == nil {
|
|
return nil
|
|
}
|
|
var diags tfdiags.Diagnostics
|
|
|
|
name := decl.Name
|
|
if existing, exists := d.InputVariables[name]; exists {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Duplicate input variable declaration",
|
|
Detail: fmt.Sprintf(
|
|
"An input variable named %q was already declared at %s.",
|
|
name, existing.DeclRange.ToHCL(),
|
|
),
|
|
Subject: decl.DeclRange.ToHCL().Ptr(),
|
|
})
|
|
return diags
|
|
}
|
|
|
|
d.InputVariables[name] = decl
|
|
return diags
|
|
}
|
|
|
|
func (d *Declarations) addLocalValue(decl *LocalValue) tfdiags.Diagnostics {
|
|
if decl == nil {
|
|
return nil
|
|
}
|
|
var diags tfdiags.Diagnostics
|
|
|
|
name := decl.Name
|
|
if existing, exists := d.LocalValues[name]; exists {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Duplicate local value declaration",
|
|
Detail: fmt.Sprintf(
|
|
"A local value named %q was already declared at %s.",
|
|
name, existing.DeclRange.ToHCL(),
|
|
),
|
|
Subject: decl.DeclRange.ToHCL().Ptr(),
|
|
})
|
|
return diags
|
|
}
|
|
|
|
d.LocalValues[name] = decl
|
|
return diags
|
|
}
|
|
|
|
func (d *Declarations) addOutputValue(decl *OutputValue) tfdiags.Diagnostics {
|
|
if decl == nil {
|
|
return nil
|
|
}
|
|
var diags tfdiags.Diagnostics
|
|
|
|
name := decl.Name
|
|
if existing, exists := d.OutputValues[name]; exists {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Duplicate output value declaration",
|
|
Detail: fmt.Sprintf(
|
|
"An output value named %q was already declared at %s.",
|
|
name, existing.DeclRange.ToHCL(),
|
|
),
|
|
Subject: decl.DeclRange.ToHCL().Ptr(),
|
|
})
|
|
return diags
|
|
}
|
|
|
|
d.OutputValues[name] = decl
|
|
return diags
|
|
}
|
|
|
|
func (d *Declarations) addRequiredProviders(decl *ProviderRequirements) tfdiags.Diagnostics {
|
|
if decl == nil {
|
|
return nil
|
|
}
|
|
var diags tfdiags.Diagnostics
|
|
if d.RequiredProviders != nil {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Duplicate provider requirements",
|
|
Detail: fmt.Sprintf(
|
|
"This stack's provider requirements were already declared at %s.",
|
|
d.RequiredProviders.DeclRange.ToHCL(),
|
|
),
|
|
Subject: decl.DeclRange.ToHCL().Ptr(),
|
|
})
|
|
return diags
|
|
}
|
|
d.RequiredProviders = decl
|
|
return diags
|
|
}
|
|
|
|
func (d *Declarations) addProviderConfig(decl *ProviderConfig) tfdiags.Diagnostics {
|
|
if decl == nil {
|
|
return nil
|
|
}
|
|
var diags tfdiags.Diagnostics
|
|
|
|
addr := decl.LocalAddr
|
|
if existing, exists := d.ProviderConfigs[addr]; exists {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Duplicate provider configuration",
|
|
Detail: fmt.Sprintf(
|
|
"An configuration named %q for provider %q was already declared at %s.",
|
|
addr.LocalName, addr.Alias, existing.DeclRange.ToHCL(),
|
|
),
|
|
Subject: decl.DeclRange.ToHCL().Ptr(),
|
|
})
|
|
return diags
|
|
}
|
|
|
|
d.ProviderConfigs[addr] = decl
|
|
return diags
|
|
}
|
|
|
|
func (d *Declarations) addRemoved(decl *Removed) tfdiags.Diagnostics {
|
|
var diags tfdiags.Diagnostics
|
|
|
|
if decl == nil {
|
|
return diags
|
|
}
|
|
|
|
if decl.From.Component != nil {
|
|
addr := decl.From.TargetConfigComponent()
|
|
|
|
if decl.From.Component.Index == nil && len(decl.From.Stack) == 0 {
|
|
// If the removed block does not have an index, then we shouldn't also
|
|
// have a component block with the same name. A removed block without
|
|
// an index indicates that the component and all instances were removed
|
|
// from the configuration.
|
|
//
|
|
// Note that a removed block with an index is allowed to coexist with a
|
|
// component block with the same name, because it indicates that only
|
|
// a specific instance was removed and not the whole thing. During the
|
|
// validate and planning stages we will validate that the clashing
|
|
// component and removed blocks are not both pointing to the same index.
|
|
if component, exists := d.Components[decl.From.Component.Name]; exists {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Component exists for removed block",
|
|
Detail: fmt.Sprintf(
|
|
"A removed block for component %q was declared without an index, but a component block with the same name was declared at %s.\n\nA removed block without an index indicates that the component and all instances were removed from the configuration, and this is not the case.",
|
|
decl.From.Component.Name, component.DeclRange.ToHCL(),
|
|
),
|
|
Subject: decl.DeclRange.ToHCL().Ptr(),
|
|
})
|
|
return diags
|
|
}
|
|
}
|
|
|
|
d.RemovedComponents.Put(addr, append(d.RemovedComponents.Get(addr), decl))
|
|
} else {
|
|
addr := decl.From.TargetStack().ToStackCall()
|
|
|
|
if len(decl.From.Stack) == 1 && decl.From.Stack[0].Index == nil {
|
|
// Same logic as for components, we can just error a bit earlier
|
|
// here if the user is targeting a stack that definitely exists
|
|
// in the configuration.
|
|
if stack, exists := d.EmbeddedStacks[decl.From.Stack[0].Name]; exists {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Stack exists for removed block",
|
|
Detail: fmt.Sprintf(
|
|
"A removed block for stack %q was declared without an index, but a stack block with the same name was declared at %s.\n\nA removed block without an index indicates that the stack and all instances were removed from the configuration, and this is not the case.",
|
|
decl.From.Component.Name, stack.DeclRange.ToHCL(),
|
|
),
|
|
Subject: decl.DeclRange.ToHCL().Ptr(),
|
|
})
|
|
return diags
|
|
}
|
|
}
|
|
|
|
d.RemovedEmbeddedStacks.Put(addr, append(d.RemovedEmbeddedStacks.Get(addr), decl))
|
|
}
|
|
|
|
return diags
|
|
}
|
|
|
|
func (d *Declarations) merge(other *Declarations) tfdiags.Diagnostics {
|
|
var diags tfdiags.Diagnostics
|
|
for _, decl := range other.EmbeddedStacks {
|
|
diags = diags.Append(
|
|
d.addEmbeddedStack(decl),
|
|
)
|
|
}
|
|
for _, blocks := range other.RemovedEmbeddedStacks.All() {
|
|
for _, decl := range blocks {
|
|
diags = diags.Append(d.addRemoved(decl))
|
|
}
|
|
}
|
|
for _, decl := range other.Components {
|
|
diags = diags.Append(
|
|
d.addComponent(decl),
|
|
)
|
|
}
|
|
for _, decl := range other.InputVariables {
|
|
diags = diags.Append(
|
|
d.addInputVariable(decl),
|
|
)
|
|
}
|
|
for _, decl := range other.LocalValues {
|
|
diags = diags.Append(
|
|
d.addLocalValue(decl),
|
|
)
|
|
}
|
|
for _, decl := range other.OutputValues {
|
|
diags = diags.Append(
|
|
d.addOutputValue(decl),
|
|
)
|
|
}
|
|
if other.RequiredProviders != nil {
|
|
d.addRequiredProviders(other.RequiredProviders)
|
|
}
|
|
for _, decl := range other.ProviderConfigs {
|
|
diags = diags.Append(
|
|
d.addProviderConfig(decl),
|
|
)
|
|
}
|
|
for _, blocks := range other.RemovedComponents.All() {
|
|
for _, decl := range blocks {
|
|
diags = diags.Append(
|
|
d.addRemoved(decl),
|
|
)
|
|
}
|
|
}
|
|
|
|
return diags
|
|
}
|