github.com/pgray/terraform@v0.5.4-0.20170822184730-b6a464c5214d/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 if vr, ok := v.(RemovableIfNotTargeted); ok { 77 removable = vr.RemoveIfNotTargeted() 78 } 79 if removable && !targetedNodes.Include(v) { 80 log.Printf("[DEBUG] Removing %q, filtered by targeting.", dag.VertexName(v)) 81 g.Remove(v) 82 } 83 } 84 } 85 86 return nil 87 } 88 89 func (t *TargetsTransformer) parseTargetAddresses() ([]ResourceAddress, error) { 90 addrs := make([]ResourceAddress, len(t.Targets)) 91 for i, target := range t.Targets { 92 ta, err := ParseResourceAddress(target) 93 if err != nil { 94 return nil, err 95 } 96 addrs[i] = *ta 97 } 98 99 return addrs, nil 100 } 101 102 // Returns the list of targeted nodes. A targeted node is either addressed 103 // directly, or is an Ancestor of a targeted node. Destroy mode keeps 104 // Descendents instead of Ancestors. 105 func (t *TargetsTransformer) selectTargetedNodes( 106 g *Graph, addrs []ResourceAddress) (*dag.Set, error) { 107 targetedNodes := new(dag.Set) 108 109 vertices := g.Vertices() 110 111 for _, v := range vertices { 112 if t.nodeIsTarget(v, addrs) { 113 targetedNodes.Add(v) 114 115 // We inform nodes that ask about the list of targets - helps for nodes 116 // that need to dynamically expand. Note that this only occurs for nodes 117 // that are already directly targeted. 118 if tn, ok := v.(GraphNodeTargetable); ok { 119 tn.SetTargets(addrs) 120 } 121 122 var deps *dag.Set 123 var err error 124 if t.Destroy { 125 deps, err = g.Descendents(v) 126 } else { 127 deps, err = g.Ancestors(v) 128 } 129 if err != nil { 130 return nil, err 131 } 132 133 for _, d := range deps.List() { 134 targetedNodes.Add(d) 135 } 136 } 137 } 138 139 // Handle nodes that need to be included if their dependencies are included. 140 // This requires multiple passes since we need to catch transitive 141 // dependencies if and only if they are via other nodes that also 142 // support TargetDownstream. For example: 143 // output -> output -> targeted-resource: both outputs need to be targeted 144 // output -> non-targeted-resource -> targeted-resource: output not targeted 145 // 146 // We'll keep looping until we stop targeting more nodes. 147 queue := targetedNodes.List() 148 for len(queue) > 0 { 149 vertices := queue 150 queue = nil // ready to append for next iteration if neccessary 151 for _, v := range vertices { 152 dependers := g.UpEdges(v) 153 if dependers == nil { 154 // indicates that there are no up edges for this node, so 155 // we have nothing to do here. 156 continue 157 } 158 159 dependers = dependers.Filter(func(dv interface{}) bool { 160 // Can ignore nodes that are already targeted 161 /*if targetedNodes.Include(dv) { 162 return false 163 }*/ 164 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 depsTargeted := deps.Intersection(targetedNodes) 184 depsUntargeted := deps.Difference(depsTargeted) 185 186 if dv.(GraphNodeTargetDownstream).TargetDownstream(depsTargeted, depsUntargeted) { 187 targetedNodes.Add(dv) 188 // Need to visit this node on the next pass to see if it 189 // has any transitive dependers. 190 queue = append(queue, dv) 191 } 192 } 193 } 194 } 195 196 return targetedNodes, nil 197 } 198 199 func (t *TargetsTransformer) nodeIsTarget( 200 v dag.Vertex, addrs []ResourceAddress) bool { 201 r, ok := v.(GraphNodeResource) 202 if !ok { 203 return false 204 } 205 206 addr := r.ResourceAddr() 207 for _, targetAddr := range addrs { 208 if t.IgnoreIndices { 209 // targetAddr is not a pointer, so we can safely mutate it without 210 // interfering with references elsewhere. 211 targetAddr.Index = -1 212 } 213 if targetAddr.Contains(addr) { 214 return true 215 } 216 } 217 218 return false 219 } 220 221 // RemovableIfNotTargeted is a special interface for graph nodes that 222 // aren't directly addressable, but need to be removed from the graph when they 223 // are not targeted. (Nodes that are not directly targeted end up in the set of 224 // targeted nodes because something that _is_ targeted depends on them.) The 225 // initial use case for this interface is GraphNodeConfigVariable, which was 226 // having trouble interpolating for module variables in targeted scenarios that 227 // filtered out the resource node being referenced. 228 type RemovableIfNotTargeted interface { 229 RemoveIfNotTargeted() bool 230 }