blob: a683f95431972fc2b8d915af025c172461729e72 [file] [log] [blame]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package addrs
import (
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/terraform/internal/tfdiags"
)
// Like MoveEndpoint, RemoveTarget is a wrapping struct that captures the result
// of decoding an HCL traversal representing a relative path from the current
// module to a removeable object.
//
// Remove targets are somewhat simpler than move endpoints, in that they deal
// only with resources and modules defined in configuration, not instances of
// those objects as recorded in state. We are therefore able to determine the
// ConfigMoveable up front, since specifying any resource or module instance key
// in a removed block is invalid.
//
// An interesting quirk of RemoveTarget is that RelSubject denotes a
// configuration object that, if the removed block is valid, should no longer
// exist in configuration. This "last known address" is used to locate and delete
// the appropriate state objects, or, in the case in which the user has forgotten
// to remove the object from configuration, to report the address of that block
// in an error diagnostic.
type RemoveTarget struct {
// SourceRange is the location of the target address in configuration.
SourceRange tfdiags.SourceRange
// RelSubject, like MoveEndpoint's relSubject, abuses an absolute address
// type to represent a relative address.
RelSubject ConfigMoveable
}
func (t *RemoveTarget) ObjectKind() RemoveTargetKind {
return removeTargetKind(t.RelSubject)
}
func (t *RemoveTarget) String() string {
if t.ObjectKind() == RemoveTargetModule {
return t.RelSubject.(Module).String()
} else if t.ObjectKind() == RemoveTargetResource {
return t.RelSubject.(ConfigResource).String()
}
// No other valid address types
panic("Usupported remove target kind")
}
func (t *RemoveTarget) Equal(other *RemoveTarget) bool {
switch {
case (t == nil) != (other == nil):
return false
case t == nil:
return true
default:
// We can safely compare string representations, since the Subject is a
// simple module or resource address.
return t.String() == other.String() && t.SourceRange == other.SourceRange
}
}
func ParseRemoveTarget(traversal hcl.Traversal) (*RemoveTarget, tfdiags.Diagnostics) {
path, remain, diags := parseModulePrefix(traversal)
if diags.HasErrors() {
return nil, diags
}
rng := tfdiags.SourceRangeFromHCL(traversal.SourceRange())
if len(remain) == 0 {
return &RemoveTarget{
RelSubject: path,
SourceRange: rng,
}, diags
}
rAddr, moreDiags := parseConfigResourceUnderModule(path, remain)
diags = diags.Append(moreDiags)
if diags.HasErrors() {
return nil, diags
}
if rAddr.Resource.Mode == DataResourceMode {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Data source address not allowed",
Detail: "Data sources are never destroyed, so they are not valid targets of removed blocks. To remove the data source from state, remove the data source block from configuration.",
Subject: rng.ToHCL().Ptr(),
})
}
return &RemoveTarget{
RelSubject: rAddr,
SourceRange: rng,
}, diags
}