github.com/opentofu/opentofu@v1.7.1/internal/tofu/transform_targets.go (about)

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