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  }