github.com/wikibal01/hashicorp-terraform@v0.11.12-beta1/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  
   221  		if !targetedNodes.Include(d) {
   222  			// this one is going to be removed, so it doesn't count
   223  			continue
   224  		}
   225  
   226  		// as soon as we see a real dependency, we mark this as
   227  		// non-removable
   228  		return true
   229  	}
   230  
   231  	depends := g.DownEdges(v)
   232  
   233  	for _, d := range depends.List() {
   234  		if !targetedNodes.Include(d) {
   235  			log.Printf("[WARN] %s missing targeted dependency %s, removing from the graph",
   236  				dag.VertexName(v), dag.VertexName(d))
   237  			return false
   238  		}
   239  	}
   240  	return true
   241  }
   242  
   243  func (t *TargetsTransformer) nodeIsTarget(
   244  	v dag.Vertex, addrs []ResourceAddress) bool {
   245  	r, ok := v.(GraphNodeResource)
   246  	if !ok {
   247  		return false
   248  	}
   249  
   250  	addr := r.ResourceAddr()
   251  	for _, targetAddr := range addrs {
   252  		if t.IgnoreIndices {
   253  			// targetAddr is not a pointer, so we can safely mutate it without
   254  			// interfering with references elsewhere.
   255  			targetAddr.Index = -1
   256  		}
   257  		if targetAddr.Contains(addr) {
   258  			return true
   259  		}
   260  	}
   261  
   262  	return false
   263  }
   264  
   265  // RemovableIfNotTargeted is a special interface for graph nodes that
   266  // aren't directly addressable, but need to be removed from the graph when they
   267  // are not targeted. (Nodes that are not directly targeted end up in the set of
   268  // targeted nodes because something that _is_ targeted depends on them.) The
   269  // initial use case for this interface is GraphNodeConfigVariable, which was
   270  // having trouble interpolating for module variables in targeted scenarios that
   271  // filtered out the resource node being referenced.
   272  type RemovableIfNotTargeted interface {
   273  	RemoveIfNotTargeted() bool
   274  }