github.com/trawler/terraform@v0.10.8-0.20171106022149-4b1c7a1d9b48/terraform/transform_targets.go (about) 1 package terraform 2 3 import ( 4 "log" 5 6 "github.com/hashicorp/terraform/dag" 7 ) 8 9 // GraphNodeTargetable is an interface for graph nodes to implement when they 10 // need to be told about incoming targets. This is useful for nodes that need 11 // to respect targets as they dynamically expand. Note that the list of targets 12 // provided will contain every target provided, and each implementing graph 13 // node must filter this list to targets considered relevant. 14 type GraphNodeTargetable interface { 15 SetTargets([]ResourceAddress) 16 } 17 18 // GraphNodeTargetDownstream is an interface for graph nodes that need to 19 // be remain present under targeting if any of their dependencies are targeted. 20 // TargetDownstream is called with the set of vertices that are direct 21 // dependencies for the node, and it should return true if the node must remain 22 // in the graph in support of those dependencies. 23 // 24 // This is used in situations where the dependency edges are representing an 25 // ordering relationship but the dependency must still be visited if its 26 // dependencies are visited. This is true for outputs, for example, since 27 // they must get updated if any of their dependent resources get updated, 28 // which would not normally be true if one of their dependencies were targeted. 29 type GraphNodeTargetDownstream interface { 30 TargetDownstream(targeted, untargeted *dag.Set) bool 31 } 32 33 // TargetsTransformer is a GraphTransformer that, when the user specifies a 34 // list of resources to target, limits the graph to only those resources and 35 // their dependencies. 36 type TargetsTransformer struct { 37 // List of targeted resource names specified by the user 38 Targets []string 39 40 // List of parsed targets, provided by callers like ResourceCountTransform 41 // that already have the targets parsed 42 ParsedTargets []ResourceAddress 43 44 // If set, the index portions of resource addresses will be ignored 45 // for comparison. This is used when transforming a graph where 46 // counted resources have not yet been expanded, since otherwise 47 // the unexpanded nodes (which never have indices) would not match. 48 IgnoreIndices bool 49 50 // Set to true when we're in a `terraform destroy` or a 51 // `terraform plan -destroy` 52 Destroy bool 53 } 54 55 func (t *TargetsTransformer) Transform(g *Graph) error { 56 if len(t.Targets) > 0 && len(t.ParsedTargets) == 0 { 57 addrs, err := t.parseTargetAddresses() 58 if err != nil { 59 return err 60 } 61 62 t.ParsedTargets = addrs 63 } 64 65 if len(t.ParsedTargets) > 0 { 66 targetedNodes, err := t.selectTargetedNodes(g, t.ParsedTargets) 67 if err != nil { 68 return err 69 } 70 71 for _, v := range g.Vertices() { 72 removable := false 73 if _, ok := v.(GraphNodeResource); ok { 74 removable = true 75 } 76 77 if vr, ok := v.(RemovableIfNotTargeted); ok { 78 removable = vr.RemoveIfNotTargeted() 79 } 80 81 if removable && !targetedNodes.Include(v) { 82 log.Printf("[DEBUG] Removing %q, filtered by targeting.", dag.VertexName(v)) 83 g.Remove(v) 84 } 85 } 86 } 87 88 return nil 89 } 90 91 func (t *TargetsTransformer) parseTargetAddresses() ([]ResourceAddress, error) { 92 addrs := make([]ResourceAddress, len(t.Targets)) 93 for i, target := range t.Targets { 94 ta, err := ParseResourceAddress(target) 95 if err != nil { 96 return nil, err 97 } 98 addrs[i] = *ta 99 } 100 101 return addrs, nil 102 } 103 104 // Returns the list of targeted nodes. A targeted node is either addressed 105 // directly, or is an Ancestor of a targeted node. Destroy mode keeps 106 // Descendents instead of Ancestors. 107 func (t *TargetsTransformer) selectTargetedNodes( 108 g *Graph, addrs []ResourceAddress) (*dag.Set, error) { 109 targetedNodes := new(dag.Set) 110 111 vertices := g.Vertices() 112 113 for _, v := range vertices { 114 if t.nodeIsTarget(v, addrs) { 115 targetedNodes.Add(v) 116 117 // We inform nodes that ask about the list of targets - helps for nodes 118 // that need to dynamically expand. Note that this only occurs for nodes 119 // that are already directly targeted. 120 if tn, ok := v.(GraphNodeTargetable); ok { 121 tn.SetTargets(addrs) 122 } 123 124 var deps *dag.Set 125 var err error 126 if t.Destroy { 127 deps, err = g.Descendents(v) 128 } else { 129 deps, err = g.Ancestors(v) 130 } 131 if err != nil { 132 return nil, err 133 } 134 135 for _, d := range deps.List() { 136 targetedNodes.Add(d) 137 } 138 } 139 } 140 return t.addDependencies(targetedNodes, g) 141 } 142 143 func (t *TargetsTransformer) addDependencies(targetedNodes *dag.Set, g *Graph) (*dag.Set, error) { 144 // Handle nodes that need to be included if their dependencies are included. 145 // This requires multiple passes since we need to catch transitive 146 // dependencies if and only if they are via other nodes that also 147 // support TargetDownstream. For example: 148 // output -> output -> targeted-resource: both outputs need to be targeted 149 // output -> non-targeted-resource -> targeted-resource: output not targeted 150 // 151 // We'll keep looping until we stop targeting more nodes. 152 queue := targetedNodes.List() 153 for len(queue) > 0 { 154 vertices := queue 155 queue = nil // ready to append for next iteration if neccessary 156 for _, v := range vertices { 157 dependers := g.UpEdges(v) 158 if dependers == nil { 159 // indicates that there are no up edges for this node, so 160 // we have nothing to do here. 161 continue 162 } 163 164 dependers = dependers.Filter(func(dv interface{}) bool { 165 _, ok := dv.(GraphNodeTargetDownstream) 166 return ok 167 }) 168 169 if dependers.Len() == 0 { 170 continue 171 } 172 173 for _, dv := range dependers.List() { 174 if targetedNodes.Include(dv) { 175 // Already present, so nothing to do 176 continue 177 } 178 179 // We'll give the node some information about what it's 180 // depending on in case that informs its decision about whether 181 // it is safe to be targeted. 182 deps := g.DownEdges(v) 183 184 depsTargeted := deps.Intersection(targetedNodes) 185 depsUntargeted := deps.Difference(depsTargeted) 186 187 if dv.(GraphNodeTargetDownstream).TargetDownstream(depsTargeted, depsUntargeted) { 188 targetedNodes.Add(dv) 189 // Need to visit this node on the next pass to see if it 190 // has any transitive dependers. 191 queue = append(queue, dv) 192 } 193 } 194 } 195 } 196 197 return targetedNodes.Filter(func(dv interface{}) bool { 198 return filterPartialOutputs(dv, targetedNodes, g) 199 }), nil 200 } 201 202 // Outputs may have been included transitively, but if any of their 203 // dependencies have been pruned they won't be resolvable. 204 // If nothing depends on the output, and the output is missing any 205 // dependencies, remove it from the graph. 206 // This essentially maintains the previous behavior where interpolation in 207 // outputs would fail silently, but can now surface errors where the output 208 // is required. 209 func filterPartialOutputs(v interface{}, targetedNodes *dag.Set, g *Graph) bool { 210 // should this just be done with TargetDownstream? 211 if _, ok := v.(*NodeApplyableOutput); !ok { 212 return true 213 } 214 215 dependers := g.UpEdges(v) 216 for _, d := range dependers.List() { 217 if _, ok := d.(*NodeCountBoundary); ok { 218 continue 219 } 220 // as soon as we see a real dependency, we mark this as 221 // non-removable 222 return true 223 } 224 225 depends := g.DownEdges(v) 226 227 for _, d := range depends.List() { 228 if !targetedNodes.Include(d) { 229 log.Printf("[WARN] %s missing targeted dependency %s, removing from the graph", 230 dag.VertexName(v), dag.VertexName(d)) 231 return false 232 } 233 } 234 return true 235 } 236 237 func (t *TargetsTransformer) nodeIsTarget( 238 v dag.Vertex, addrs []ResourceAddress) bool { 239 r, ok := v.(GraphNodeResource) 240 if !ok { 241 return false 242 } 243 244 addr := r.ResourceAddr() 245 for _, targetAddr := range addrs { 246 if t.IgnoreIndices { 247 // targetAddr is not a pointer, so we can safely mutate it without 248 // interfering with references elsewhere. 249 targetAddr.Index = -1 250 } 251 if targetAddr.Contains(addr) { 252 return true 253 } 254 } 255 256 return false 257 } 258 259 // RemovableIfNotTargeted is a special interface for graph nodes that 260 // aren't directly addressable, but need to be removed from the graph when they 261 // are not targeted. (Nodes that are not directly targeted end up in the set of 262 // targeted nodes because something that _is_ targeted depends on them.) The 263 // initial use case for this interface is GraphNodeConfigVariable, which was 264 // having trouble interpolating for module variables in targeted scenarios that 265 // filtered out the resource node being referenced. 266 type RemovableIfNotTargeted interface { 267 RemoveIfNotTargeted() bool 268 }