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