kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/terraform/transform_targets.go (about)

     1  package terraform
     2  
     3  import (
     4  	"log"
     5  
     6  	"kubeform.dev/terraform-backend-sdk/addrs"
     7  	"kubeform.dev/terraform-backend-sdk/dag"
     8  )
     9  
    10  // GraphNodeTargetable is an interface for graph nodes to implement when they
    11  // need to be told about incoming targets. This is useful for nodes that need
    12  // to respect targets as they dynamically expand. Note that the list of targets
    13  // provided will contain every target provided, and each implementing graph
    14  // node must filter this list to targets considered relevant.
    15  type GraphNodeTargetable interface {
    16  	SetTargets([]addrs.Targetable)
    17  }
    18  
    19  // TargetsTransformer is a GraphTransformer that, when the user specifies a
    20  // list of resources to target, limits the graph to only those resources and
    21  // their dependencies.
    22  type TargetsTransformer struct {
    23  	// List of targeted resource names specified by the user
    24  	Targets []addrs.Targetable
    25  }
    26  
    27  func (t *TargetsTransformer) Transform(g *Graph) error {
    28  	if len(t.Targets) > 0 {
    29  		targetedNodes, err := t.selectTargetedNodes(g, t.Targets)
    30  		if err != nil {
    31  			return err
    32  		}
    33  
    34  		for _, v := range g.Vertices() {
    35  			if !targetedNodes.Include(v) {
    36  				log.Printf("[DEBUG] Removing %q, filtered by targeting.", dag.VertexName(v))
    37  				g.Remove(v)
    38  			}
    39  		}
    40  	}
    41  
    42  	return nil
    43  }
    44  
    45  // Returns a set of targeted nodes. A targeted node is either addressed
    46  // directly, address indirectly via its container, or it's a dependency of a
    47  // targeted node.
    48  func (t *TargetsTransformer) selectTargetedNodes(g *Graph, addrs []addrs.Targetable) (dag.Set, error) {
    49  	targetedNodes := make(dag.Set)
    50  
    51  	vertices := g.Vertices()
    52  
    53  	for _, v := range vertices {
    54  		if t.nodeIsTarget(v, addrs) {
    55  			targetedNodes.Add(v)
    56  
    57  			// We inform nodes that ask about the list of targets - helps for nodes
    58  			// that need to dynamically expand. Note that this only occurs for nodes
    59  			// that are already directly targeted.
    60  			if tn, ok := v.(GraphNodeTargetable); ok {
    61  				tn.SetTargets(addrs)
    62  			}
    63  
    64  			deps, _ := g.Ancestors(v)
    65  			for _, d := range deps {
    66  				targetedNodes.Add(d)
    67  			}
    68  		}
    69  	}
    70  
    71  	// It is expected that outputs which are only derived from targeted
    72  	// resources are also updated. While we don't include any other possible
    73  	// side effects from the targeted nodes, these are added because outputs
    74  	// cannot be targeted on their own.
    75  	// Start by finding the root module output nodes themselves
    76  	for _, v := range vertices {
    77  		// outputs are all temporary value types
    78  		tv, ok := v.(graphNodeTemporaryValue)
    79  		if !ok {
    80  			continue
    81  		}
    82  
    83  		// root module outputs indicate that while they are an output type,
    84  		// they not temporary and will return false here.
    85  		if tv.temporaryValue() {
    86  			continue
    87  		}
    88  
    89  		// If this output is descended only from targeted resources, then we
    90  		// will keep it
    91  		deps, _ := g.Ancestors(v)
    92  		found := 0
    93  		for _, d := range deps {
    94  			switch d.(type) {
    95  			case GraphNodeResourceInstance:
    96  			case GraphNodeConfigResource:
    97  			default:
    98  				continue
    99  			}
   100  
   101  			if !targetedNodes.Include(d) {
   102  				// this dependency isn't being targeted, so we can't process this
   103  				// output
   104  				found = 0
   105  				break
   106  			}
   107  
   108  			found++
   109  		}
   110  
   111  		if found > 0 {
   112  			// we found an output we can keep; add it, and all it's dependencies
   113  			targetedNodes.Add(v)
   114  			for _, d := range deps {
   115  				targetedNodes.Add(d)
   116  			}
   117  		}
   118  	}
   119  
   120  	return targetedNodes, nil
   121  }
   122  
   123  func (t *TargetsTransformer) nodeIsTarget(v dag.Vertex, targets []addrs.Targetable) bool {
   124  	var vertexAddr addrs.Targetable
   125  	switch r := v.(type) {
   126  	case GraphNodeResourceInstance:
   127  		vertexAddr = r.ResourceInstanceAddr()
   128  	case GraphNodeConfigResource:
   129  		vertexAddr = r.ResourceAddr()
   130  
   131  	default:
   132  		// Only resource and resource instance nodes can be targeted.
   133  		return false
   134  	}
   135  
   136  	for _, targetAddr := range targets {
   137  		switch vertexAddr.(type) {
   138  		case addrs.ConfigResource:
   139  			// Before expansion happens, we only have nodes that know their
   140  			// ConfigResource address.  We need to take the more specific
   141  			// target addresses and generalize them in order to compare with a
   142  			// ConfigResource.
   143  			switch target := targetAddr.(type) {
   144  			case addrs.AbsResourceInstance:
   145  				targetAddr = target.ContainingResource().Config()
   146  			case addrs.AbsResource:
   147  				targetAddr = target.Config()
   148  			case addrs.ModuleInstance:
   149  				targetAddr = target.Module()
   150  			}
   151  		}
   152  
   153  		if targetAddr.TargetContains(vertexAddr) {
   154  			return true
   155  		}
   156  	}
   157  
   158  	return false
   159  }