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  }