1package terraform 2 3import ( 4 "fmt" 5 "log" 6 7 "github.com/hashicorp/terraform/internal/addrs" 8 "github.com/hashicorp/terraform/internal/configs" 9 "github.com/hashicorp/terraform/internal/providers" 10 "github.com/hashicorp/terraform/internal/states" 11 "github.com/hashicorp/terraform/internal/tfdiags" 12) 13 14// ImportStateTransformer is a GraphTransformer that adds nodes to the 15// graph to represent the imports we want to do for resources. 16type ImportStateTransformer struct { 17 Targets []*ImportTarget 18 Config *configs.Config 19} 20 21func (t *ImportStateTransformer) Transform(g *Graph) error { 22 for _, target := range t.Targets { 23 24 // This is only likely to happen in misconfigured tests 25 if t.Config == nil { 26 return fmt.Errorf("cannot import into an empty configuration") 27 } 28 29 // Get the module config 30 modCfg := t.Config.Descendent(target.Addr.Module.Module()) 31 if modCfg == nil { 32 return fmt.Errorf("module %s not found", target.Addr.Module.Module()) 33 } 34 35 providerAddr := addrs.AbsProviderConfig{ 36 Module: target.Addr.Module.Module(), 37 } 38 39 // Try to find the resource config 40 rsCfg := modCfg.Module.ResourceByAddr(target.Addr.Resource.Resource) 41 if rsCfg != nil { 42 // Get the provider FQN for the resource from the resource configuration 43 providerAddr.Provider = rsCfg.Provider 44 45 // Get the alias from the resource's provider local config 46 providerAddr.Alias = rsCfg.ProviderConfigAddr().Alias 47 } else { 48 // Resource has no matching config, so use an implied provider 49 // based on the resource type 50 rsProviderType := target.Addr.Resource.Resource.ImpliedProvider() 51 providerAddr.Provider = modCfg.Module.ImpliedProviderForUnqualifiedType(rsProviderType) 52 } 53 54 node := &graphNodeImportState{ 55 Addr: target.Addr, 56 ID: target.ID, 57 ProviderAddr: providerAddr, 58 } 59 g.Add(node) 60 } 61 return nil 62} 63 64type graphNodeImportState struct { 65 Addr addrs.AbsResourceInstance // Addr is the resource address to import into 66 ID string // ID is the ID to import as 67 ProviderAddr addrs.AbsProviderConfig // Provider address given by the user, or implied by the resource type 68 ResolvedProvider addrs.AbsProviderConfig // provider node address after resolution 69 70 states []providers.ImportedResource 71} 72 73var ( 74 _ GraphNodeModulePath = (*graphNodeImportState)(nil) 75 _ GraphNodeExecutable = (*graphNodeImportState)(nil) 76 _ GraphNodeProviderConsumer = (*graphNodeImportState)(nil) 77 _ GraphNodeDynamicExpandable = (*graphNodeImportState)(nil) 78) 79 80func (n *graphNodeImportState) Name() string { 81 return fmt.Sprintf("%s (import id %q)", n.Addr, n.ID) 82} 83 84// GraphNodeProviderConsumer 85func (n *graphNodeImportState) ProvidedBy() (addrs.ProviderConfig, bool) { 86 // We assume that n.ProviderAddr has been properly populated here. 87 // It's the responsibility of the code creating a graphNodeImportState 88 // to populate this, possibly by calling DefaultProviderConfig() on the 89 // resource address to infer an implied provider from the resource type 90 // name. 91 return n.ProviderAddr, false 92} 93 94// GraphNodeProviderConsumer 95func (n *graphNodeImportState) Provider() addrs.Provider { 96 // We assume that n.ProviderAddr has been properly populated here. 97 // It's the responsibility of the code creating a graphNodeImportState 98 // to populate this, possibly by calling DefaultProviderConfig() on the 99 // resource address to infer an implied provider from the resource type 100 // name. 101 return n.ProviderAddr.Provider 102} 103 104// GraphNodeProviderConsumer 105func (n *graphNodeImportState) SetProvider(addr addrs.AbsProviderConfig) { 106 n.ResolvedProvider = addr 107} 108 109// GraphNodeModuleInstance 110func (n *graphNodeImportState) Path() addrs.ModuleInstance { 111 return n.Addr.Module 112} 113 114// GraphNodeModulePath 115func (n *graphNodeImportState) ModulePath() addrs.Module { 116 return n.Addr.Module.Module() 117} 118 119// GraphNodeExecutable impl. 120func (n *graphNodeImportState) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) { 121 // Reset our states 122 n.states = nil 123 124 provider, _, err := getProvider(ctx, n.ResolvedProvider) 125 diags = diags.Append(err) 126 if diags.HasErrors() { 127 return diags 128 } 129 130 // import state 131 absAddr := n.Addr.Resource.Absolute(ctx.Path()) 132 133 // Call pre-import hook 134 diags = diags.Append(ctx.Hook(func(h Hook) (HookAction, error) { 135 return h.PreImportState(absAddr, n.ID) 136 })) 137 if diags.HasErrors() { 138 return diags 139 } 140 141 resp := provider.ImportResourceState(providers.ImportResourceStateRequest{ 142 TypeName: n.Addr.Resource.Resource.Type, 143 ID: n.ID, 144 }) 145 diags = diags.Append(resp.Diagnostics) 146 if diags.HasErrors() { 147 return diags 148 } 149 150 imported := resp.ImportedResources 151 for _, obj := range imported { 152 log.Printf("[TRACE] graphNodeImportState: import %s %q produced instance object of type %s", absAddr.String(), n.ID, obj.TypeName) 153 } 154 n.states = imported 155 156 // Call post-import hook 157 diags = diags.Append(ctx.Hook(func(h Hook) (HookAction, error) { 158 return h.PostImportState(absAddr, imported) 159 })) 160 return diags 161} 162 163// GraphNodeDynamicExpandable impl. 164// 165// We use DynamicExpand as a way to generate the subgraph of refreshes 166// and state inserts we need to do for our import state. Since they're new 167// resources they don't depend on anything else and refreshes are isolated 168// so this is nearly a perfect use case for dynamic expand. 169func (n *graphNodeImportState) DynamicExpand(ctx EvalContext) (*Graph, error) { 170 var diags tfdiags.Diagnostics 171 172 g := &Graph{Path: ctx.Path()} 173 174 // nameCounter is used to de-dup names in the state. 175 nameCounter := make(map[string]int) 176 177 // Compile the list of addresses that we'll be inserting into the state. 178 // We do this ahead of time so we can verify that we aren't importing 179 // something that already exists. 180 addrs := make([]addrs.AbsResourceInstance, len(n.states)) 181 for i, state := range n.states { 182 addr := n.Addr 183 if t := state.TypeName; t != "" { 184 addr.Resource.Resource.Type = t 185 } 186 187 // Determine if we need to suffix the name to de-dup 188 key := addr.String() 189 count, ok := nameCounter[key] 190 if ok { 191 count++ 192 addr.Resource.Resource.Name += fmt.Sprintf("-%d", count) 193 } 194 nameCounter[key] = count 195 196 // Add it to our list 197 addrs[i] = addr 198 } 199 200 // Verify that all the addresses are clear 201 state := ctx.State() 202 for _, addr := range addrs { 203 existing := state.ResourceInstance(addr) 204 if existing != nil { 205 diags = diags.Append(tfdiags.Sourceless( 206 tfdiags.Error, 207 "Resource already managed by Terraform", 208 fmt.Sprintf("Terraform is already managing a remote object for %s. To import to this address you must first remove the existing object from the state.", addr), 209 )) 210 continue 211 } 212 } 213 if diags.HasErrors() { 214 // Bail out early, then. 215 return nil, diags.Err() 216 } 217 218 // For each of the states, we add a node to handle the refresh/add to state. 219 // "n.states" is populated by our own Execute with the result of 220 // ImportState. Since DynamicExpand is always called after Execute, this is 221 // safe. 222 for i, state := range n.states { 223 g.Add(&graphNodeImportStateSub{ 224 TargetAddr: addrs[i], 225 State: state, 226 ResolvedProvider: n.ResolvedProvider, 227 }) 228 } 229 230 // Root transform for a single root 231 t := &RootTransformer{} 232 if err := t.Transform(g); err != nil { 233 return nil, err 234 } 235 236 // Done! 237 return g, diags.Err() 238} 239 240// graphNodeImportStateSub is the sub-node of graphNodeImportState 241// and is part of the subgraph. This node is responsible for refreshing 242// and adding a resource to the state once it is imported. 243type graphNodeImportStateSub struct { 244 TargetAddr addrs.AbsResourceInstance 245 State providers.ImportedResource 246 ResolvedProvider addrs.AbsProviderConfig 247} 248 249var ( 250 _ GraphNodeModuleInstance = (*graphNodeImportStateSub)(nil) 251 _ GraphNodeExecutable = (*graphNodeImportStateSub)(nil) 252) 253 254func (n *graphNodeImportStateSub) Name() string { 255 return fmt.Sprintf("import %s result", n.TargetAddr) 256} 257 258func (n *graphNodeImportStateSub) Path() addrs.ModuleInstance { 259 return n.TargetAddr.Module 260} 261 262// GraphNodeExecutable impl. 263func (n *graphNodeImportStateSub) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) { 264 // If the Ephemeral type isn't set, then it is an error 265 if n.State.TypeName == "" { 266 diags = diags.Append(fmt.Errorf("import of %s didn't set type", n.TargetAddr.String())) 267 return diags 268 } 269 270 state := n.State.AsInstanceObject() 271 272 // Refresh 273 riNode := &NodeAbstractResourceInstance{ 274 Addr: n.TargetAddr, 275 NodeAbstractResource: NodeAbstractResource{ 276 ResolvedProvider: n.ResolvedProvider, 277 }, 278 } 279 state, refreshDiags := riNode.refresh(ctx, states.NotDeposed, state) 280 diags = diags.Append(refreshDiags) 281 if diags.HasErrors() { 282 return diags 283 } 284 285 // Verify the existance of the imported resource 286 if state.Value.IsNull() { 287 var diags tfdiags.Diagnostics 288 diags = diags.Append(tfdiags.Sourceless( 289 tfdiags.Error, 290 "Cannot import non-existent remote object", 291 fmt.Sprintf( 292 "While attempting to import an existing object to %q, "+ 293 "the provider detected that no object exists with the given id. "+ 294 "Only pre-existing objects can be imported; check that the id "+ 295 "is correct and that it is associated with the provider's "+ 296 "configured region or endpoint, or use \"terraform apply\" to "+ 297 "create a new remote object for this resource.", 298 n.TargetAddr, 299 ), 300 )) 301 return diags 302 } 303 304 diags = diags.Append(riNode.writeResourceInstanceState(ctx, state, workingState)) 305 return diags 306} 307