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 }