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