]>
Commit | Line | Data |
---|---|---|
1 | package terraform | |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | ||
6 | "github.com/hashicorp/terraform/addrs" | |
7 | "github.com/hashicorp/terraform/providers" | |
8 | "github.com/hashicorp/terraform/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 | } |