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  }