// Copyright IBM Corp. 2014, 2026 // 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 }