github.com/opentofu/opentofu@v1.7.1/internal/tofu/transform_targets.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package tofu 7 8 import ( 9 "log" 10 11 "github.com/opentofu/opentofu/internal/addrs" 12 "github.com/opentofu/opentofu/internal/dag" 13 ) 14 15 // GraphNodeTargetable is an interface for graph nodes to implement when they 16 // need to be told about incoming targets. This is useful for nodes that need 17 // to respect targets as they dynamically expand. Note that the list of targets 18 // provided will contain every target provided, and each implementing graph 19 // node must filter this list to targets considered relevant. 20 type GraphNodeTargetable interface { 21 SetTargets([]addrs.Targetable) 22 } 23 24 // TargetsTransformer is a GraphTransformer that, when the user specifies a 25 // list of resources to target, limits the graph to only those resources and 26 // their dependencies. 27 type TargetsTransformer struct { 28 // List of targeted resource names specified by the user 29 Targets []addrs.Targetable 30 } 31 32 func (t *TargetsTransformer) Transform(g *Graph) error { 33 if len(t.Targets) > 0 { 34 targetedNodes, err := t.selectTargetedNodes(g, t.Targets) 35 if err != nil { 36 return err 37 } 38 39 for _, v := range g.Vertices() { 40 if !targetedNodes.Include(v) { 41 log.Printf("[DEBUG] Removing %q, filtered by targeting.", dag.VertexName(v)) 42 g.Remove(v) 43 } 44 } 45 } 46 47 return nil 48 } 49 50 // Returns a set of targeted nodes. A targeted node is either addressed 51 // directly, address indirectly via its container, or it's a dependency of a 52 // targeted node. 53 func (t *TargetsTransformer) selectTargetedNodes(g *Graph, addrs []addrs.Targetable) (dag.Set, error) { 54 targetedNodes := make(dag.Set) 55 56 vertices := g.Vertices() 57 58 for _, v := range vertices { 59 if t.nodeIsTarget(v, addrs) { 60 targetedNodes.Add(v) 61 62 // We inform nodes that ask about the list of targets - helps for nodes 63 // that need to dynamically expand. Note that this only occurs for nodes 64 // that are already directly targeted. 65 if tn, ok := v.(GraphNodeTargetable); ok { 66 tn.SetTargets(addrs) 67 } 68 69 deps, _ := g.Ancestors(v) 70 for _, d := range deps { 71 targetedNodes.Add(d) 72 } 73 } 74 } 75 76 // It is expected that outputs which are only derived from targeted 77 // resources are also updated. While we don't include any other possible 78 // side effects from the targeted nodes, these are added because outputs 79 // cannot be targeted on their own. 80 // Start by finding the root module output nodes themselves 81 for _, v := range vertices { 82 // outputs are all temporary value types 83 tv, ok := v.(graphNodeTemporaryValue) 84 if !ok { 85 continue 86 } 87 88 // root module outputs indicate that while they are an output type, 89 // they not temporary and will return false here. 90 if tv.temporaryValue() { 91 continue 92 } 93 94 // If this output is descended only from targeted resources, then we 95 // will keep it 96 deps, _ := g.Ancestors(v) 97 found := 0 98 for _, d := range deps { 99 switch d.(type) { 100 case GraphNodeResourceInstance: 101 case GraphNodeConfigResource: 102 default: 103 continue 104 } 105 106 if !targetedNodes.Include(d) { 107 // this dependency isn't being targeted, so we can't process this 108 // output 109 found = 0 110 break 111 } 112 113 found++ 114 } 115 116 if found > 0 { 117 // we found an output we can keep; add it, and all it's dependencies 118 targetedNodes.Add(v) 119 for _, d := range deps { 120 targetedNodes.Add(d) 121 } 122 } 123 } 124 125 return targetedNodes, nil 126 } 127 128 func (t *TargetsTransformer) nodeIsTarget(v dag.Vertex, targets []addrs.Targetable) bool { 129 var vertexAddr addrs.Targetable 130 switch r := v.(type) { 131 case GraphNodeResourceInstance: 132 vertexAddr = r.ResourceInstanceAddr() 133 case GraphNodeConfigResource: 134 vertexAddr = r.ResourceAddr() 135 136 default: 137 // Only resource and resource instance nodes can be targeted. 138 return false 139 } 140 141 for _, targetAddr := range targets { 142 switch vertexAddr.(type) { 143 case addrs.ConfigResource: 144 // Before expansion happens, we only have nodes that know their 145 // ConfigResource address. We need to take the more specific 146 // target addresses and generalize them in order to compare with a 147 // ConfigResource. 148 switch target := targetAddr.(type) { 149 case addrs.AbsResourceInstance: 150 targetAddr = target.ContainingResource().Config() 151 case addrs.AbsResource: 152 targetAddr = target.Config() 153 case addrs.ModuleInstance: 154 targetAddr = target.Module() 155 } 156 } 157 158 if targetAddr.TargetContains(vertexAddr) { 159 return true 160 } 161 } 162 163 return false 164 }