mirror of
https://github.com/hashicorp/terraform.git
synced 2026-02-03 20:50:59 -05:00
137 lines
4.6 KiB
Go
137 lines
4.6 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package refactoring
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
|
"github.com/hashicorp/terraform/internal/configs"
|
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
|
)
|
|
|
|
// RemoveStatement is the fully-specified form of addrs.Remove
|
|
type RemoveStatement struct {
|
|
// From is the absolute address of the configuration object being removed.
|
|
From addrs.ConfigMoveable
|
|
|
|
// Destroy indicates that the resource should be destroyed, not just removed
|
|
// from state.
|
|
Destroy bool
|
|
DeclRange tfdiags.SourceRange
|
|
}
|
|
|
|
// FindRemoveStatements recurses through the modules of the given configuration
|
|
// and returns a set of all "removed" blocks defined within after deduplication
|
|
// on the From address.
|
|
//
|
|
// Error diagnostics are returned if any resource or module targeted by a remove
|
|
// block is still defined in configuration.
|
|
//
|
|
// A "removed" block in a parent module overrides a removed block in a child
|
|
// module when both target the same configuration object.
|
|
func FindRemoveStatements(rootCfg *configs.Config) (addrs.Map[addrs.ConfigMoveable, RemoveStatement], tfdiags.Diagnostics) {
|
|
stmts := findRemoveStatements(rootCfg, addrs.MakeMap[addrs.ConfigMoveable, RemoveStatement]())
|
|
diags := validateRemoveStatements(rootCfg, stmts)
|
|
return stmts, diags
|
|
}
|
|
|
|
func validateRemoveStatements(cfg *configs.Config, stmts addrs.Map[addrs.ConfigMoveable, RemoveStatement]) (diags tfdiags.Diagnostics) {
|
|
for _, rst := range stmts.Keys() {
|
|
switch rst := rst.(type) {
|
|
case addrs.ConfigResource:
|
|
m := cfg.Descendant(rst.Module)
|
|
if m == nil {
|
|
break
|
|
}
|
|
|
|
if r := m.Module.ResourceByAddr(rst.Resource); r != nil {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Removed resource still exists",
|
|
Detail: fmt.Sprintf("This statement declares that %s was removed, but it is still declared in configuration.", rst),
|
|
Subject: r.DeclRange.Ptr(),
|
|
})
|
|
}
|
|
case addrs.Module:
|
|
if m := cfg.Descendant(rst); m != nil {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Removed module still exists",
|
|
Detail: fmt.Sprintf("This statement declares that %s was removed, but it is still declared in configuration.", rst),
|
|
Subject: m.CallRange.Ptr(),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
return diags
|
|
}
|
|
|
|
func findRemoveStatements(cfg *configs.Config, into addrs.Map[addrs.ConfigMoveable, RemoveStatement]) addrs.Map[addrs.ConfigMoveable, RemoveStatement] {
|
|
for _, mc := range cfg.Module.Removed {
|
|
switch mc.From.ObjectKind() {
|
|
case addrs.RemoveTargetResource:
|
|
// First, stitch together the module path and the RelSubject to form
|
|
// the absolute address of the config object being removed.
|
|
res := mc.From.RelSubject.(addrs.ConfigResource)
|
|
fromAddr := addrs.ConfigResource{
|
|
Module: append(cfg.Path, res.Module...),
|
|
Resource: res.Resource,
|
|
}
|
|
|
|
// If we already have a remove statement for this ConfigResource, it
|
|
// must have come from a parent module, because duplicate removed
|
|
// blocks in the same module are ignored during parsing.
|
|
// The removed block in the parent module overrides the block in the
|
|
// child module.
|
|
existingStatement, ok := into.GetOk(fromAddr)
|
|
if ok {
|
|
if existingResource, ok := existingStatement.From.(addrs.ConfigResource); ok &&
|
|
existingResource.Equal(fromAddr) {
|
|
continue
|
|
}
|
|
}
|
|
|
|
into.Put(fromAddr, RemoveStatement{
|
|
From: fromAddr,
|
|
Destroy: mc.Destroy,
|
|
DeclRange: tfdiags.SourceRangeFromHCL(mc.DeclRange),
|
|
})
|
|
case addrs.RemoveTargetModule:
|
|
// First, stitch together the module path and the RelSubject to form
|
|
// the absolute address of the config object being removed.
|
|
mod := mc.From.RelSubject.(addrs.Module)
|
|
absMod := append(cfg.Path, mod...)
|
|
|
|
// If there is already a statement for this Module, it must
|
|
// have come from a parent module, because duplicate removed blocks
|
|
// in the same module are ignored during parsing.
|
|
// The removed block in the parent module overrides the block in the
|
|
// child module.
|
|
existingStatement, ok := into.GetOk(mc.From.RelSubject)
|
|
if ok {
|
|
if existingModule, ok := existingStatement.From.(addrs.Module); ok &&
|
|
existingModule.Equal(absMod) {
|
|
continue
|
|
}
|
|
}
|
|
|
|
into.Put(absMod, RemoveStatement{
|
|
From: absMod,
|
|
Destroy: mc.Destroy,
|
|
DeclRange: tfdiags.SourceRangeFromHCL(mc.DeclRange),
|
|
})
|
|
default:
|
|
panic("Unsupported remove target kind")
|
|
}
|
|
}
|
|
|
|
for _, childCfg := range cfg.Children {
|
|
into = findRemoveStatements(childCfg, into)
|
|
}
|
|
|
|
return into
|
|
}
|