]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package terraform |
2 | ||
3 | import ( | |
4 | "fmt" | |
107c1cdb ND |
5 | |
6 | "github.com/hashicorp/terraform/addrs" | |
7 | "github.com/hashicorp/terraform/providers" | |
8 | "github.com/hashicorp/terraform/tfdiags" | |
bae9f6d2 JC |
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 { | |
bae9f6d2 | 18 | for _, target := range t.Targets { |
107c1cdb ND |
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) | |
bae9f6d2 JC |
25 | } |
26 | ||
107c1cdb ND |
27 | node := &graphNodeImportState{ |
28 | Addr: target.Addr, | |
15c0b25d | 29 | ID: target.ID, |
107c1cdb ND |
30 | ProviderAddr: providerAddr, |
31 | } | |
32 | g.Add(node) | |
bae9f6d2 | 33 | } |
bae9f6d2 JC |
34 | return nil |
35 | } | |
36 | ||
37 | type graphNodeImportState struct { | |
107c1cdb ND |
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 | |
bae9f6d2 | 42 | |
107c1cdb | 43 | states []providers.ImportedResource |
bae9f6d2 JC |
44 | } |
45 | ||
107c1cdb ND |
46 | var ( |
47 | _ GraphNodeSubPath = (*graphNodeImportState)(nil) | |
48 | _ GraphNodeEvalable = (*graphNodeImportState)(nil) | |
49 | _ GraphNodeProviderConsumer = (*graphNodeImportState)(nil) | |
50 | _ GraphNodeDynamicExpandable = (*graphNodeImportState)(nil) | |
51 | ) | |
52 | ||
bae9f6d2 | 53 | func (n *graphNodeImportState) Name() string { |
107c1cdb | 54 | return fmt.Sprintf("%s (import id %q)", n.Addr, n.ID) |
bae9f6d2 JC |
55 | } |
56 | ||
107c1cdb ND |
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 | |
15c0b25d AP |
65 | } |
66 | ||
107c1cdb ND |
67 | // GraphNodeProviderConsumer |
68 | func (n *graphNodeImportState) SetProvider(addr addrs.AbsProviderConfig) { | |
69 | n.ResolvedProvider = addr | |
bae9f6d2 JC |
70 | } |
71 | ||
72 | // GraphNodeSubPath | |
107c1cdb ND |
73 | func (n *graphNodeImportState) Path() addrs.ModuleInstance { |
74 | return n.Addr.Module | |
bae9f6d2 JC |
75 | } |
76 | ||
77 | // GraphNodeEvalable impl. | |
78 | func (n *graphNodeImportState) EvalTree() EvalNode { | |
107c1cdb | 79 | var provider providers.Interface |
bae9f6d2 JC |
80 | |
81 | // Reset our states | |
82 | n.states = nil | |
83 | ||
84 | // Return our sequence | |
85 | return &EvalSequence{ | |
86 | Nodes: []EvalNode{ | |
87 | &EvalGetProvider{ | |
107c1cdb | 88 | Addr: n.ResolvedProvider, |
bae9f6d2 JC |
89 | Output: &provider, |
90 | }, | |
91 | &EvalImportState{ | |
107c1cdb | 92 | Addr: n.Addr.Resource, |
bae9f6d2 | 93 | Provider: &provider, |
107c1cdb | 94 | ID: n.ID, |
bae9f6d2 JC |
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) { | |
107c1cdb ND |
108 | var diags tfdiags.Diagnostics |
109 | ||
bae9f6d2 JC |
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. | |
107c1cdb | 118 | addrs := make([]addrs.AbsResourceInstance, len(n.states)) |
bae9f6d2 | 119 | for i, state := range n.states { |
107c1cdb ND |
120 | addr := n.Addr |
121 | if t := state.TypeName; t != "" { | |
122 | addr.Resource.Resource.Type = t | |
bae9f6d2 JC |
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++ | |
107c1cdb | 130 | addr.Resource.Resource.Name += fmt.Sprintf("-%d", count) |
bae9f6d2 JC |
131 | } |
132 | nameCounter[key] = count | |
133 | ||
134 | // Add it to our list | |
107c1cdb | 135 | addrs[i] = addr |
bae9f6d2 JC |
136 | } |
137 | ||
138 | // Verify that all the addresses are clear | |
107c1cdb | 139 | state := ctx.State() |
bae9f6d2 | 140 | for _, addr := range addrs { |
107c1cdb ND |
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 | |
bae9f6d2 JC |
149 | } |
150 | } | |
107c1cdb ND |
151 | if diags.HasErrors() { |
152 | // Bail out early, then. | |
153 | return nil, diags.Err() | |
154 | } | |
bae9f6d2 JC |
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{ | |
107c1cdb | 162 | TargetAddr: addrs[i], |
15c0b25d | 163 | State: state, |
15c0b25d | 164 | ResolvedProvider: n.ResolvedProvider, |
bae9f6d2 JC |
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! | |
107c1cdb | 175 | return g, diags.Err() |
bae9f6d2 JC |
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 { | |
107c1cdb ND |
182 | TargetAddr addrs.AbsResourceInstance |
183 | State providers.ImportedResource | |
184 | ResolvedProvider addrs.AbsProviderConfig | |
bae9f6d2 JC |
185 | } |
186 | ||
107c1cdb ND |
187 | var ( |
188 | _ GraphNodeSubPath = (*graphNodeImportStateSub)(nil) | |
189 | _ GraphNodeEvalable = (*graphNodeImportStateSub)(nil) | |
190 | ) | |
191 | ||
bae9f6d2 | 192 | func (n *graphNodeImportStateSub) Name() string { |
107c1cdb | 193 | return fmt.Sprintf("import %s result", n.TargetAddr) |
bae9f6d2 JC |
194 | } |
195 | ||
107c1cdb ND |
196 | func (n *graphNodeImportStateSub) Path() addrs.ModuleInstance { |
197 | return n.TargetAddr.Module | |
bae9f6d2 JC |
198 | } |
199 | ||
200 | // GraphNodeEvalable impl. | |
201 | func (n *graphNodeImportStateSub) EvalTree() EvalNode { | |
202 | // If the Ephemeral type isn't set, then it is an error | |
107c1cdb ND |
203 | if n.State.TypeName == "" { |
204 | err := fmt.Errorf("import of %s didn't set type", n.TargetAddr.String()) | |
bae9f6d2 JC |
205 | return &EvalReturnError{Error: &err} |
206 | } | |
207 | ||
107c1cdb | 208 | state := n.State.AsInstanceObject() |
bae9f6d2 | 209 | |
107c1cdb ND |
210 | var provider providers.Interface |
211 | var providerSchema *ProviderSchema | |
bae9f6d2 JC |
212 | return &EvalSequence{ |
213 | Nodes: []EvalNode{ | |
214 | &EvalGetProvider{ | |
107c1cdb | 215 | Addr: n.ResolvedProvider, |
bae9f6d2 | 216 | Output: &provider, |
107c1cdb | 217 | Schema: &providerSchema, |
bae9f6d2 JC |
218 | }, |
219 | &EvalRefresh{ | |
107c1cdb ND |
220 | Addr: n.TargetAddr.Resource, |
221 | ProviderAddr: n.ResolvedProvider, | |
222 | Provider: &provider, | |
223 | ProviderSchema: &providerSchema, | |
224 | State: &state, | |
225 | Output: &state, | |
bae9f6d2 JC |
226 | }, |
227 | &EvalImportStateVerify{ | |
107c1cdb | 228 | Addr: n.TargetAddr.Resource, |
bae9f6d2 JC |
229 | State: &state, |
230 | }, | |
231 | &EvalWriteState{ | |
107c1cdb ND |
232 | Addr: n.TargetAddr.Resource, |
233 | ProviderAddr: n.ResolvedProvider, | |
234 | ProviderSchema: &providerSchema, | |
235 | State: &state, | |
bae9f6d2 JC |
236 | }, |
237 | }, | |
238 | } | |
239 | } |