mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-27 09:21:14 -06:00
24c45fcd5d
When targeting, only Addressable untargeted nodes were being removed from the graph. Variable nodes are not directly Addressable, so they were hanging around. This caused problems with module variables that referred to Resource nodes. The Resource node would be filtered out of the graph, but the module Variable node would not, so it would try to interpolate during the graph walk and be unable to find it's referent. This would present itself as strange "cannot find variable" errors for variables that were uninvolved with the currently targeted set of resources. Here, we introduce a new interface that can be implemented by graph nodes to indicate they should be filtered out from targeting even though they are not directly addressable themselves.
129 lines
3.4 KiB
Go
129 lines
3.4 KiB
Go
package terraform
|
|
|
|
import (
|
|
"log"
|
|
|
|
"github.com/hashicorp/terraform/dag"
|
|
)
|
|
|
|
// TargetsTransformer is a GraphTransformer that, when the user specifies a
|
|
// list of resources to target, limits the graph to only those resources and
|
|
// their dependencies.
|
|
type TargetsTransformer struct {
|
|
// List of targeted resource names specified by the user
|
|
Targets []string
|
|
|
|
// List of parsed targets, provided by callers like ResourceCountTransform
|
|
// that already have the targets parsed
|
|
ParsedTargets []ResourceAddress
|
|
|
|
// Set to true when we're in a `terraform destroy` or a
|
|
// `terraform plan -destroy`
|
|
Destroy bool
|
|
}
|
|
|
|
func (t *TargetsTransformer) Transform(g *Graph) error {
|
|
if len(t.Targets) > 0 && len(t.ParsedTargets) == 0 {
|
|
addrs, err := t.parseTargetAddresses()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
t.ParsedTargets = addrs
|
|
}
|
|
if len(t.ParsedTargets) > 0 {
|
|
targetedNodes, err := t.selectTargetedNodes(g, t.ParsedTargets)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, v := range g.Vertices() {
|
|
removable := false
|
|
if _, ok := v.(GraphNodeAddressable); ok {
|
|
removable = true
|
|
}
|
|
if vr, ok := v.(RemovableIfNotTargeted); ok {
|
|
removable = vr.RemoveIfNotTargeted()
|
|
}
|
|
if removable && !targetedNodes.Include(v) {
|
|
log.Printf("[DEBUG] Removing %q, filtered by targeting.", dag.VertexName(v))
|
|
g.Remove(v)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t *TargetsTransformer) parseTargetAddresses() ([]ResourceAddress, error) {
|
|
addrs := make([]ResourceAddress, len(t.Targets))
|
|
for i, target := range t.Targets {
|
|
ta, err := ParseResourceAddress(target)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
addrs[i] = *ta
|
|
}
|
|
return addrs, nil
|
|
}
|
|
|
|
// Returns the list of targeted nodes. A targeted node is either addressed
|
|
// directly, or is an Ancestor of a targeted node. Destroy mode keeps
|
|
// Descendents instead of Ancestors.
|
|
func (t *TargetsTransformer) selectTargetedNodes(
|
|
g *Graph, addrs []ResourceAddress) (*dag.Set, error) {
|
|
targetedNodes := new(dag.Set)
|
|
for _, v := range g.Vertices() {
|
|
if t.nodeIsTarget(v, addrs) {
|
|
targetedNodes.Add(v)
|
|
|
|
// We inform nodes that ask about the list of targets - helps for nodes
|
|
// that need to dynamically expand. Note that this only occurs for nodes
|
|
// that are already directly targeted.
|
|
if tn, ok := v.(GraphNodeTargetable); ok {
|
|
tn.SetTargets(addrs)
|
|
}
|
|
|
|
var deps *dag.Set
|
|
var err error
|
|
if t.Destroy {
|
|
deps, err = g.Descendents(v)
|
|
} else {
|
|
deps, err = g.Ancestors(v)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, d := range deps.List() {
|
|
targetedNodes.Add(d)
|
|
}
|
|
}
|
|
}
|
|
return targetedNodes, nil
|
|
}
|
|
|
|
func (t *TargetsTransformer) nodeIsTarget(
|
|
v dag.Vertex, addrs []ResourceAddress) bool {
|
|
r, ok := v.(GraphNodeAddressable)
|
|
if !ok {
|
|
return false
|
|
}
|
|
addr := r.ResourceAddress()
|
|
for _, targetAddr := range addrs {
|
|
if targetAddr.Equals(addr) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// RemovableIfNotTargeted is a special interface for graph nodes that
|
|
// aren't directly addressable, but need to be removed from the graph when they
|
|
// are not targeted. (Nodes that are not directly targeted end up in the set of
|
|
// targeted nodes because something that _is_ targeted depends on them.) The
|
|
// initial use case for this interface is GraphNodeConfigVariable, which was
|
|
// having trouble interpolating for module variables in targeted scenarios that
|
|
// filtered out the resource node being referenced.
|
|
type RemovableIfNotTargeted interface {
|
|
RemoveIfNotTargeted() bool
|
|
}
|