// Copyright (c) The OpenTofu Authors // SPDX-License-Identifier: MPL-2.0 // Copyright (c) 2023 HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package tofu import ( "log" "github.com/opentofu/opentofu/internal/addrs" "github.com/opentofu/opentofu/internal/dag" ) // GraphNodeTargetable is an interface for graph nodes to implement when they // need to be told about incoming targets. This is useful for nodes that need // to respect targets as they dynamically expand. Note that the list of targets // provided will contain every target provided, and each implementing graph // node must filter this list to targets considered relevant. type GraphNodeTargetable interface { SetTargets([]addrs.Targetable) } // 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 []addrs.Targetable } func (t *TargetsTransformer) Transform(g *Graph) error { if len(t.Targets) > 0 { targetedNodes, err := t.selectTargetedNodes(g, t.Targets) if err != nil { return err } for _, v := range g.Vertices() { if !targetedNodes.Include(v) { log.Printf("[DEBUG] Removing %q, filtered by targeting.", dag.VertexName(v)) g.Remove(v) } } } return nil } // Returns a set of targeted nodes. A targeted node is either addressed // directly, address indirectly via its container, or it's a dependency of a // targeted node. func (t *TargetsTransformer) selectTargetedNodes(g *Graph, addrs []addrs.Targetable) (dag.Set, error) { targetedNodes := make(dag.Set) vertices := g.Vertices() for _, v := range 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) } deps, _ := g.Ancestors(v) for _, d := range deps { targetedNodes.Add(d) } } } // It is expected that outputs which are only derived from targeted // resources are also updated. While we don't include any other possible // side effects from the targeted nodes, these are added because outputs // cannot be targeted on their own. // Start by finding the root module output nodes themselves for _, v := range vertices { // outputs are all temporary value types tv, ok := v.(graphNodeTemporaryValue) if !ok { continue } // root module outputs indicate that while they are an output type, // they not temporary and will return false here. if tv.temporaryValue() { continue } // If this output is descended only from targeted resources, then we // will keep it deps, _ := g.Ancestors(v) found := 0 for _, d := range deps { switch d.(type) { case GraphNodeResourceInstance: case GraphNodeConfigResource: default: continue } if !targetedNodes.Include(d) { // this dependency isn't being targeted, so we can't process this // output found = 0 break } found++ } if found > 0 { // we found an output we can keep; add it, and all it's dependencies targetedNodes.Add(v) for _, d := range deps { targetedNodes.Add(d) } } } return targetedNodes, nil } func (t *TargetsTransformer) nodeIsTarget(v dag.Vertex, targets []addrs.Targetable) bool { var vertexAddr addrs.Targetable switch r := v.(type) { case GraphNodeResourceInstance: vertexAddr = r.ResourceInstanceAddr() case GraphNodeConfigResource: vertexAddr = r.ResourceAddr() default: // Only resource and resource instance nodes can be targeted. return false } for _, targetAddr := range targets { switch vertexAddr.(type) { case addrs.ConfigResource: // Before expansion happens, we only have nodes that know their // ConfigResource address. We need to take the more specific // target addresses and generalize them in order to compare with a // ConfigResource. switch target := targetAddr.(type) { case addrs.AbsResourceInstance: targetAddr = target.ContainingResource().Config() case addrs.AbsResource: targetAddr = target.Config() case addrs.ModuleInstance: targetAddr = target.Module() } } if targetAddr.TargetContains(vertexAddr) { return true } } return false }