github.com/skyscape-cloud-services/terraform@v0.9.2-0.20170609144644-7ece028a1747/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 // Set to true when we're in a `terraform destroy` or a 45 // `terraform plan -destroy` 46 Destroy bool 47 } 48 49 func (t *TargetsTransformer) Transform(g *Graph) error { 50 if len(t.Targets) > 0 && len(t.ParsedTargets) == 0 { 51 addrs, err := t.parseTargetAddresses() 52 if err != nil { 53 return err 54 } 55 56 t.ParsedTargets = addrs 57 } 58 59 if len(t.ParsedTargets) > 0 { 60 targetedNodes, err := t.selectTargetedNodes(g, t.ParsedTargets) 61 if err != nil { 62 return err 63 } 64 65 for _, v := range g.Vertices() { 66 removable := false 67 if _, ok := v.(GraphNodeResource); ok { 68 removable = true 69 } 70 if vr, ok := v.(RemovableIfNotTargeted); ok { 71 removable = vr.RemoveIfNotTargeted() 72 } 73 if removable && !targetedNodes.Include(v) { 74 log.Printf("[DEBUG] Removing %q, filtered by targeting.", dag.VertexName(v)) 75 g.Remove(v) 76 } 77 } 78 } 79 80 return nil 81 } 82 83 func (t *TargetsTransformer) parseTargetAddresses() ([]ResourceAddress, error) { 84 addrs := make([]ResourceAddress, len(t.Targets)) 85 for i, target := range t.Targets { 86 ta, err := ParseResourceAddress(target) 87 if err != nil { 88 return nil, err 89 } 90 addrs[i] = *ta 91 } 92 93 return addrs, nil 94 } 95 96 // Returns the list of targeted nodes. A targeted node is either addressed 97 // directly, or is an Ancestor of a targeted node. Destroy mode keeps 98 // Descendents instead of Ancestors. 99 func (t *TargetsTransformer) selectTargetedNodes( 100 g *Graph, addrs []ResourceAddress) (*dag.Set, error) { 101 targetedNodes := new(dag.Set) 102 103 vertices := g.Vertices() 104 105 for _, v := range vertices { 106 if t.nodeIsTarget(v, addrs) { 107 targetedNodes.Add(v) 108 109 // We inform nodes that ask about the list of targets - helps for nodes 110 // that need to dynamically expand. Note that this only occurs for nodes 111 // that are already directly targeted. 112 if tn, ok := v.(GraphNodeTargetable); ok { 113 tn.SetTargets(addrs) 114 } 115 116 var deps *dag.Set 117 var err error 118 if t.Destroy { 119 deps, err = g.Descendents(v) 120 } else { 121 deps, err = g.Ancestors(v) 122 } 123 if err != nil { 124 return nil, err 125 } 126 127 for _, d := range deps.List() { 128 targetedNodes.Add(d) 129 } 130 } 131 } 132 133 // Handle nodes that need to be included if their dependencies are included. 134 // This requires multiple passes since we need to catch transitive 135 // dependencies if and only if they are via other nodes that also 136 // support TargetDownstream. For example: 137 // output -> output -> targeted-resource: both outputs need to be targeted 138 // output -> non-targeted-resource -> targeted-resource: output not targeted 139 // 140 // We'll keep looping until we stop targeting more nodes. 141 queue := targetedNodes.List() 142 for len(queue) > 0 { 143 vertices := queue 144 queue = nil // ready to append for next iteration if neccessary 145 for _, v := range vertices { 146 dependers := g.UpEdges(v) 147 if dependers == nil { 148 // indicates that there are no up edges for this node, so 149 // we have nothing to do here. 150 continue 151 } 152 153 dependers = dependers.Filter(func(dv interface{}) bool { 154 // Can ignore nodes that are already targeted 155 /*if targetedNodes.Include(dv) { 156 return false 157 }*/ 158 159 _, ok := dv.(GraphNodeTargetDownstream) 160 return ok 161 }) 162 163 if dependers.Len() == 0 { 164 continue 165 } 166 167 for _, dv := range dependers.List() { 168 if targetedNodes.Include(dv) { 169 // Already present, so nothing to do 170 continue 171 } 172 173 // We'll give the node some information about what it's 174 // depending on in case that informs its decision about whether 175 // it is safe to be targeted. 176 deps := g.DownEdges(v) 177 depsTargeted := deps.Intersection(targetedNodes) 178 depsUntargeted := deps.Difference(depsTargeted) 179 180 if dv.(GraphNodeTargetDownstream).TargetDownstream(depsTargeted, depsUntargeted) { 181 targetedNodes.Add(dv) 182 // Need to visit this node on the next pass to see if it 183 // has any transitive dependers. 184 queue = append(queue, dv) 185 } 186 } 187 } 188 } 189 190 return targetedNodes, nil 191 } 192 193 func (t *TargetsTransformer) nodeIsTarget( 194 v dag.Vertex, addrs []ResourceAddress) bool { 195 r, ok := v.(GraphNodeResource) 196 if !ok { 197 return false 198 } 199 200 addr := r.ResourceAddr() 201 for _, targetAddr := range addrs { 202 if targetAddr.Equals(addr) { 203 return true 204 } 205 } 206 207 return false 208 } 209 210 // RemovableIfNotTargeted is a special interface for graph nodes that 211 // aren't directly addressable, but need to be removed from the graph when they 212 // are not targeted. (Nodes that are not directly targeted end up in the set of 213 // targeted nodes because something that _is_ targeted depends on them.) The 214 // initial use case for this interface is GraphNodeConfigVariable, which was 215 // having trouble interpolating for module variables in targeted scenarios that 216 // filtered out the resource node being referenced. 217 type RemovableIfNotTargeted interface { 218 RemoveIfNotTargeted() bool 219 }