SetTargets([]ResourceAddress)
}
+// GraphNodeTargetDownstream is an interface for graph nodes that need to
+// be remain present under targeting if any of their dependencies are targeted.
+// TargetDownstream is called with the set of vertices that are direct
+// dependencies for the node, and it should return true if the node must remain
+// in the graph in support of those dependencies.
+//
+// This is used in situations where the dependency edges are representing an
+// ordering relationship but the dependency must still be visited if its
+// dependencies are visited. This is true for outputs, for example, since
+// they must get updated if any of their dependent resources get updated,
+// which would not normally be true if one of their dependencies were targeted.
+type GraphNodeTargetDownstream interface {
+ TargetDownstream(targeted, untargeted *dag.Set) bool
+}
+
// 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.
// that already have the targets parsed
ParsedTargets []ResourceAddress
+ // If set, the index portions of resource addresses will be ignored
+ // for comparison. This is used when transforming a graph where
+ // counted resources have not yet been expanded, since otherwise
+ // the unexpanded nodes (which never have indices) would not match.
+ IgnoreIndices bool
+
// Set to true when we're in a `terraform destroy` or a
// `terraform plan -destroy`
Destroy bool
func (t *TargetsTransformer) selectTargetedNodes(
g *Graph, addrs []ResourceAddress) (*dag.Set, error) {
targetedNodes := new(dag.Set)
- for _, v := range g.Vertices() {
+
+ vertices := g.Vertices()
+
+ for _, v := range vertices {
if t.nodeIsTarget(v, addrs) {
targetedNodes.Add(v)
}
}
+ // Handle nodes that need to be included if their dependencies are included.
+ // This requires multiple passes since we need to catch transitive
+ // dependencies if and only if they are via other nodes that also
+ // support TargetDownstream. For example:
+ // output -> output -> targeted-resource: both outputs need to be targeted
+ // output -> non-targeted-resource -> targeted-resource: output not targeted
+ //
+ // We'll keep looping until we stop targeting more nodes.
+ queue := targetedNodes.List()
+ for len(queue) > 0 {
+ vertices := queue
+ queue = nil // ready to append for next iteration if neccessary
+ for _, v := range vertices {
+ dependers := g.UpEdges(v)
+ if dependers == nil {
+ // indicates that there are no up edges for this node, so
+ // we have nothing to do here.
+ continue
+ }
+
+ dependers = dependers.Filter(func(dv interface{}) bool {
+ // Can ignore nodes that are already targeted
+ /*if targetedNodes.Include(dv) {
+ return false
+ }*/
+
+ _, ok := dv.(GraphNodeTargetDownstream)
+ return ok
+ })
+
+ if dependers.Len() == 0 {
+ continue
+ }
+
+ for _, dv := range dependers.List() {
+ if targetedNodes.Include(dv) {
+ // Already present, so nothing to do
+ continue
+ }
+
+ // We'll give the node some information about what it's
+ // depending on in case that informs its decision about whether
+ // it is safe to be targeted.
+ deps := g.DownEdges(v)
+ depsTargeted := deps.Intersection(targetedNodes)
+ depsUntargeted := deps.Difference(depsTargeted)
+
+ if dv.(GraphNodeTargetDownstream).TargetDownstream(depsTargeted, depsUntargeted) {
+ targetedNodes.Add(dv)
+ // Need to visit this node on the next pass to see if it
+ // has any transitive dependers.
+ queue = append(queue, dv)
+ }
+ }
+ }
+ }
+
return targetedNodes, nil
}
addr := r.ResourceAddr()
for _, targetAddr := range addrs {
- if targetAddr.Equals(addr) {
+ if t.IgnoreIndices {
+ // targetAddr is not a pointer, so we can safely mutate it without
+ // interfering with references elsewhere.
+ targetAddr.Index = -1
+ }
+ if targetAddr.Contains(addr) {
return true
}
}