Files
opentf/internal/refactoring/remove_statement.go
Ronny Orot e9fe0f1118 Add support for removed block (#1158)
Signed-off-by: Ronny Orot <ronny.orot@gmail.com>
2024-02-21 10:31:44 +02:00

125 lines
4.2 KiB
Go

// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package refactoring
import (
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/tfdiags"
)
type RemoveStatement struct {
From addrs.ConfigRemovable
DeclRange tfdiags.SourceRange
}
// GetEndpointsToRemove recurses through the modules of the given configuration
// and returns an array of all "removed" addresses within, in a
// deterministic but undefined order.
// We also validate that the removed modules/resources configuration blocks were removed.
func GetEndpointsToRemove(rootCfg *configs.Config) ([]addrs.ConfigRemovable, tfdiags.Diagnostics) {
rm := findRemoveStatements(rootCfg, nil)
diags := validateRemoveStatements(rootCfg, rm)
removedAddresses := make([]addrs.ConfigRemovable, len(rm))
for i, rs := range rm {
removedAddresses[i] = rs.From
}
return removedAddresses, diags
}
func findRemoveStatements(cfg *configs.Config, into []*RemoveStatement) []*RemoveStatement {
modAddr := cfg.Path
for _, rc := range cfg.Module.Removed {
var removedEndpoint *RemoveStatement
switch FromAddress := rc.From.RelSubject.(type) {
case addrs.ConfigResource:
// Get the absolute address of the resource by appending the module config address
// to the resource's relative address
absModule := make(addrs.Module, 0, len(modAddr)+len(FromAddress.Module))
absModule = append(absModule, modAddr...)
absModule = append(absModule, FromAddress.Module...)
var absConfigResource addrs.ConfigRemovable = addrs.ConfigResource{
Resource: FromAddress.Resource,
Module: absModule,
}
removedEndpoint = &RemoveStatement{From: absConfigResource, DeclRange: tfdiags.SourceRangeFromHCL(rc.DeclRange)}
case addrs.Module:
// Get the absolute address of the module by appending the module config address
// to the module itself
var absModule = make(addrs.Module, 0, len(modAddr)+len(FromAddress))
absModule = append(absModule, modAddr...)
absModule = append(absModule, FromAddress...)
removedEndpoint = &RemoveStatement{From: absModule, DeclRange: tfdiags.SourceRangeFromHCL(rc.DeclRange)}
default:
panic(fmt.Sprintf("unhandled address type %T", FromAddress))
}
into = append(into, removedEndpoint)
}
for _, childCfg := range cfg.Children {
into = findRemoveStatements(childCfg, into)
}
return into
}
// validateRemoveStatements validates that the removed modules/resources configuration blocks were removed.
func validateRemoveStatements(cfg *configs.Config, removeStatements []*RemoveStatement) tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
for _, rs := range removeStatements {
fromAddr := rs.From
if fromAddr == nil {
// Invalid value should've been caught during original
// configuration decoding, in the configs package.
panic(fmt.Sprintf("incompatible Remove endpoint in %s", rs.DeclRange.ToHCL()))
}
// validate that a resource/module with this address doesn't exist in the config
switch fromAddr := fromAddr.(type) {
case addrs.ConfigResource:
moduleConfig := cfg.Descendent(fromAddr.Module)
if moduleConfig != nil && moduleConfig.Module.ResourceByAddr(fromAddr.Resource) != nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Removed resource block still exists",
Detail: fmt.Sprintf(
"This statement declares a removal of the resource %s, but this resource block still exists in the configuration. Please remove the resource block.",
fromAddr,
),
Subject: rs.DeclRange.ToHCL().Ptr(),
})
}
case addrs.Module:
if cfg.Descendent(fromAddr) != nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Removed module block still exists",
Detail: fmt.Sprintf(
"This statement declares a removal of the module %s, but this module block still exists in the configuration. Please remove the module block.",
fromAddr,
),
Subject: rs.DeclRange.ToHCL().Ptr(),
})
}
default:
panic(fmt.Sprintf("incompatible Remove endpoint address type in %s", rs.DeclRange.ToHCL()))
}
}
return diags
}