github.com/hashicorp/terraform-plugin-sdk@v1.17.2/terraform/transform_import_state.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 6 "github.com/hashicorp/terraform-plugin-sdk/internal/addrs" 7 "github.com/hashicorp/terraform-plugin-sdk/internal/providers" 8 "github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags" 9 ) 10 11 // ImportStateTransformer is a GraphTransformer that adds nodes to the 12 // graph to represent the imports we want to do for resources. 13 type ImportStateTransformer struct { 14 Targets []*ImportTarget 15 } 16 17 func (t *ImportStateTransformer) Transform(g *Graph) error { 18 for _, target := range t.Targets { 19 // The ProviderAddr may not be supplied for non-aliased providers. 20 // This will be populated if the targets come from the cli, but tests 21 // may not specify implied provider addresses. 22 providerAddr := target.ProviderAddr 23 if providerAddr.ProviderConfig.Type == "" { 24 providerAddr = target.Addr.Resource.Resource.DefaultProviderConfig().Absolute(target.Addr.Module) 25 } 26 27 node := &graphNodeImportState{ 28 Addr: target.Addr, 29 ID: target.ID, 30 ProviderAddr: providerAddr, 31 } 32 g.Add(node) 33 } 34 return nil 35 } 36 37 type graphNodeImportState struct { 38 Addr addrs.AbsResourceInstance // Addr is the resource address to import into 39 ID string // ID is the ID to import as 40 ProviderAddr addrs.AbsProviderConfig // Provider address given by the user, or implied by the resource type 41 ResolvedProvider addrs.AbsProviderConfig // provider node address after resolution 42 43 states []providers.ImportedResource 44 } 45 46 var ( 47 _ GraphNodeSubPath = (*graphNodeImportState)(nil) 48 _ GraphNodeEvalable = (*graphNodeImportState)(nil) 49 _ GraphNodeProviderConsumer = (*graphNodeImportState)(nil) 50 _ GraphNodeDynamicExpandable = (*graphNodeImportState)(nil) 51 ) 52 53 func (n *graphNodeImportState) Name() string { 54 return fmt.Sprintf("%s (import id %q)", n.Addr, n.ID) 55 } 56 57 // GraphNodeProviderConsumer 58 func (n *graphNodeImportState) ProvidedBy() (addrs.AbsProviderConfig, bool) { 59 // We assume that n.ProviderAddr has been properly populated here. 60 // It's the responsibility of the code creating a graphNodeImportState 61 // to populate this, possibly by calling DefaultProviderConfig() on the 62 // resource address to infer an implied provider from the resource type 63 // name. 64 return n.ProviderAddr, false 65 } 66 67 // GraphNodeProviderConsumer 68 func (n *graphNodeImportState) SetProvider(addr addrs.AbsProviderConfig) { 69 n.ResolvedProvider = addr 70 } 71 72 // GraphNodeSubPath 73 func (n *graphNodeImportState) Path() addrs.ModuleInstance { 74 return n.Addr.Module 75 } 76 77 // GraphNodeEvalable impl. 78 func (n *graphNodeImportState) EvalTree() EvalNode { 79 var provider providers.Interface 80 81 // Reset our states 82 n.states = nil 83 84 // Return our sequence 85 return &EvalSequence{ 86 Nodes: []EvalNode{ 87 &EvalGetProvider{ 88 Addr: n.ResolvedProvider, 89 Output: &provider, 90 }, 91 &EvalImportState{ 92 Addr: n.Addr.Resource, 93 Provider: &provider, 94 ID: n.ID, 95 Output: &n.states, 96 }, 97 }, 98 } 99 } 100 101 // GraphNodeDynamicExpandable impl. 102 // 103 // We use DynamicExpand as a way to generate the subgraph of refreshes 104 // and state inserts we need to do for our import state. Since they're new 105 // resources they don't depend on anything else and refreshes are isolated 106 // so this is nearly a perfect use case for dynamic expand. 107 func (n *graphNodeImportState) DynamicExpand(ctx EvalContext) (*Graph, error) { 108 var diags tfdiags.Diagnostics 109 110 g := &Graph{Path: ctx.Path()} 111 112 // nameCounter is used to de-dup names in the state. 113 nameCounter := make(map[string]int) 114 115 // Compile the list of addresses that we'll be inserting into the state. 116 // We do this ahead of time so we can verify that we aren't importing 117 // something that already exists. 118 addrs := make([]addrs.AbsResourceInstance, len(n.states)) 119 for i, state := range n.states { 120 addr := n.Addr 121 if t := state.TypeName; t != "" { 122 addr.Resource.Resource.Type = t 123 } 124 125 // Determine if we need to suffix the name to de-dup 126 key := addr.String() 127 count, ok := nameCounter[key] 128 if ok { 129 count++ 130 addr.Resource.Resource.Name += fmt.Sprintf("-%d", count) 131 } 132 nameCounter[key] = count 133 134 // Add it to our list 135 addrs[i] = addr 136 } 137 138 // Verify that all the addresses are clear 139 state := ctx.State() 140 for _, addr := range addrs { 141 existing := state.ResourceInstance(addr) 142 if existing != nil { 143 diags = diags.Append(tfdiags.Sourceless( 144 tfdiags.Error, 145 "Resource already managed by Terraform", 146 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), 147 )) 148 continue 149 } 150 } 151 if diags.HasErrors() { 152 // Bail out early, then. 153 return nil, diags.Err() 154 } 155 156 // For each of the states, we add a node to handle the refresh/add to state. 157 // "n.states" is populated by our own EvalTree with the result of 158 // ImportState. Since DynamicExpand is always called after EvalTree, this 159 // is safe. 160 for i, state := range n.states { 161 g.Add(&graphNodeImportStateSub{ 162 TargetAddr: addrs[i], 163 State: state, 164 ResolvedProvider: n.ResolvedProvider, 165 }) 166 } 167 168 // Root transform for a single root 169 t := &RootTransformer{} 170 if err := t.Transform(g); err != nil { 171 return nil, err 172 } 173 174 // Done! 175 return g, diags.Err() 176 } 177 178 // graphNodeImportStateSub is the sub-node of graphNodeImportState 179 // and is part of the subgraph. This node is responsible for refreshing 180 // and adding a resource to the state once it is imported. 181 type graphNodeImportStateSub struct { 182 TargetAddr addrs.AbsResourceInstance 183 State providers.ImportedResource 184 ResolvedProvider addrs.AbsProviderConfig 185 } 186 187 var ( 188 _ GraphNodeSubPath = (*graphNodeImportStateSub)(nil) 189 _ GraphNodeEvalable = (*graphNodeImportStateSub)(nil) 190 ) 191 192 func (n *graphNodeImportStateSub) Name() string { 193 return fmt.Sprintf("import %s result", n.TargetAddr) 194 } 195 196 func (n *graphNodeImportStateSub) Path() addrs.ModuleInstance { 197 return n.TargetAddr.Module 198 } 199 200 // GraphNodeEvalable impl. 201 func (n *graphNodeImportStateSub) EvalTree() EvalNode { 202 // If the Ephemeral type isn't set, then it is an error 203 if n.State.TypeName == "" { 204 err := fmt.Errorf("import of %s didn't set type", n.TargetAddr.String()) 205 return &EvalReturnError{Error: &err} 206 } 207 208 state := n.State.AsInstanceObject() 209 210 var provider providers.Interface 211 var providerSchema *ProviderSchema 212 return &EvalSequence{ 213 Nodes: []EvalNode{ 214 &EvalGetProvider{ 215 Addr: n.ResolvedProvider, 216 Output: &provider, 217 Schema: &providerSchema, 218 }, 219 &EvalRefresh{ 220 Addr: n.TargetAddr.Resource, 221 ProviderAddr: n.ResolvedProvider, 222 Provider: &provider, 223 ProviderSchema: &providerSchema, 224 State: &state, 225 Output: &state, 226 }, 227 &EvalImportStateVerify{ 228 Addr: n.TargetAddr.Resource, 229 State: &state, 230 }, 231 &EvalWriteState{ 232 Addr: n.TargetAddr.Resource, 233 ProviderAddr: n.ResolvedProvider, 234 ProviderSchema: &providerSchema, 235 State: &state, 236 }, 237 }, 238 } 239 }