github.com/opentofu/opentofu@v1.7.1/internal/tofu/transform_config.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/configs"
    13  	"github.com/opentofu/opentofu/internal/dag"
    14  )
    15  
    16  // ConfigTransformer is a GraphTransformer that adds all the resources
    17  // from the configuration to the graph.
    18  //
    19  // The module used to configure this transformer must be the root module.
    20  //
    21  // Only resources are added to the graph. Variables, outputs, and
    22  // providers must be added via other transforms.
    23  //
    24  // Unlike ConfigTransformerOld, this transformer creates a graph with
    25  // all resources including module resources, rather than creating module
    26  // nodes that are then "flattened".
    27  type ConfigTransformer struct {
    28  	Concrete ConcreteResourceNodeFunc
    29  
    30  	// Module is the module to add resources from.
    31  	Config *configs.Config
    32  
    33  	// Mode will only add resources that match the given mode
    34  	ModeFilter bool
    35  	Mode       addrs.ResourceMode
    36  
    37  	// Do not apply this transformer.
    38  	skip bool
    39  
    40  	// importTargets specifies a slice of addresses that will have state
    41  	// imported for them.
    42  	importTargets []*ImportTarget
    43  
    44  	// generateConfigPathForImportTargets tells the graph where to write any
    45  	// generated config for import targets that are not contained within config.
    46  	//
    47  	// If this is empty and an import target has no config, the graph will
    48  	// simply import the state for the target and any follow-up operations will
    49  	// try to delete the imported resource unless the config is updated
    50  	// manually.
    51  	generateConfigPathForImportTargets string
    52  }
    53  
    54  func (t *ConfigTransformer) Transform(g *Graph) error {
    55  	if t.skip {
    56  		return nil
    57  	}
    58  
    59  	// If no configuration is available, we don't do anything
    60  	if t.Config == nil {
    61  		return nil
    62  	}
    63  
    64  	// Start the transformation process
    65  	return t.transform(g, t.Config, t.generateConfigPathForImportTargets)
    66  }
    67  
    68  func (t *ConfigTransformer) transform(g *Graph, config *configs.Config, generateConfigPath string) error {
    69  	// If no config, do nothing
    70  	if config == nil {
    71  		return nil
    72  	}
    73  
    74  	// Add our resources
    75  	if err := t.transformSingle(g, config, generateConfigPath); err != nil {
    76  		return err
    77  	}
    78  
    79  	// Transform all the children without generating config.
    80  	for _, c := range config.Children {
    81  		if err := t.transform(g, c, ""); err != nil {
    82  			return err
    83  		}
    84  	}
    85  
    86  	return nil
    87  }
    88  
    89  func (t *ConfigTransformer) transformSingle(g *Graph, config *configs.Config, generateConfigPath string) error {
    90  	path := config.Path
    91  	module := config.Module
    92  	log.Printf("[TRACE] ConfigTransformer: Starting for path: %v", path)
    93  
    94  	allResources := make([]*configs.Resource, 0, len(module.ManagedResources)+len(module.DataResources))
    95  	for _, r := range module.ManagedResources {
    96  		allResources = append(allResources, r)
    97  	}
    98  	for _, r := range module.DataResources {
    99  		allResources = append(allResources, r)
   100  	}
   101  
   102  	// Take a copy of the import targets, so we can edit them as we go.
   103  	// Only include import targets that are targeting the current module.
   104  	var importTargets []*ImportTarget
   105  	for _, target := range t.importTargets {
   106  		if targetModule := target.StaticAddr().Module; targetModule.Equal(config.Path) {
   107  			importTargets = append(importTargets, target)
   108  		}
   109  	}
   110  
   111  	for _, r := range allResources {
   112  		relAddr := r.Addr()
   113  
   114  		if t.ModeFilter && relAddr.Mode != t.Mode {
   115  			// Skip non-matching modes
   116  			continue
   117  		}
   118  
   119  		// If any of the import targets can apply to this node's instances,
   120  		// filter them down to the applicable addresses.
   121  		var imports []*ImportTarget
   122  		configAddr := relAddr.InModule(path)
   123  
   124  		var matchedIndices []int
   125  		for ix, i := range importTargets {
   126  			if target := i.StaticAddr(); target.Equal(configAddr) {
   127  				// This import target has been claimed by an actual resource,
   128  				// let's make a note of this to remove it from the targets.
   129  				matchedIndices = append(matchedIndices, ix)
   130  				imports = append(imports, i)
   131  			}
   132  		}
   133  
   134  		for ix := len(matchedIndices) - 1; ix >= 0; ix-- {
   135  			tIx := matchedIndices[ix]
   136  
   137  			// We do this backwards, since it means we don't have to adjust the
   138  			// later indices as we change the length of import targets.
   139  			//
   140  			// We need to do this separately, as a single resource could match
   141  			// multiple import targets.
   142  			importTargets = append(importTargets[:tIx], importTargets[tIx+1:]...)
   143  		}
   144  
   145  		abstract := &NodeAbstractResource{
   146  			Addr: addrs.ConfigResource{
   147  				Resource: relAddr,
   148  				Module:   path,
   149  			},
   150  			importTargets: imports,
   151  		}
   152  
   153  		var node dag.Vertex = abstract
   154  		if f := t.Concrete; f != nil {
   155  			node = f(abstract)
   156  		}
   157  
   158  		g.Add(node)
   159  	}
   160  
   161  	// If any import targets were not claimed by resources, then let's add them
   162  	// into the graph now.
   163  	//
   164  	// We should only reach this point if config generation is enabled, since we validate that all import targets have
   165  	// a resource in validateImportTargets when config generation is disabled
   166  	//
   167  	// We'll add the nodes that we know will fail, and catch them again later
   168  	// in the processing when we are in a position to raise a much more helpful
   169  	// error message.
   170  	for _, i := range importTargets {
   171  		if len(generateConfigPath) > 0 {
   172  			// Create a node with the resource and import target. This node will take care of the config generation
   173  			abstract := &NodeAbstractResource{
   174  				// We've already validated in validateImportTargets that the address is fully resolvable
   175  				Addr:               i.ResolvedAddr().ConfigResource(),
   176  				importTargets:      []*ImportTarget{i},
   177  				generateConfigPath: generateConfigPath,
   178  			}
   179  
   180  			var node dag.Vertex = abstract
   181  			if f := t.Concrete; f != nil {
   182  				node = f(abstract)
   183  			}
   184  
   185  			g.Add(node)
   186  		} else {
   187  			// Technically we shouldn't reach this point, as we've already validated that a resource exists
   188  			// in validateImportTargets
   189  			return importResourceWithoutConfigDiags(i.StaticAddr().String(), i.Config)
   190  		}
   191  	}
   192  
   193  	return nil
   194  }