github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-internal/terraform/transform_import_state.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  
     7  	"github.com/muratcelep/terraform/not-internal/addrs"
     8  	"github.com/muratcelep/terraform/not-internal/configs"
     9  	"github.com/muratcelep/terraform/not-internal/providers"
    10  	"github.com/muratcelep/terraform/not-internal/states"
    11  	"github.com/muratcelep/terraform/not-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.
    16  type ImportStateTransformer struct {
    17  	Targets []*ImportTarget
    18  	Config  *configs.Config
    19  }
    20  
    21  func (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  
    64  type 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  
    73  var (
    74  	_ GraphNodeModulePath        = (*graphNodeImportState)(nil)
    75  	_ GraphNodeExecutable        = (*graphNodeImportState)(nil)
    76  	_ GraphNodeProviderConsumer  = (*graphNodeImportState)(nil)
    77  	_ GraphNodeDynamicExpandable = (*graphNodeImportState)(nil)
    78  )
    79  
    80  func (n *graphNodeImportState) Name() string {
    81  	return fmt.Sprintf("%s (import id %q)", n.Addr, n.ID)
    82  }
    83  
    84  // GraphNodeProviderConsumer
    85  func (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
    95  func (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
   105  func (n *graphNodeImportState) SetProvider(addr addrs.AbsProviderConfig) {
   106  	n.ResolvedProvider = addr
   107  }
   108  
   109  // GraphNodeModuleInstance
   110  func (n *graphNodeImportState) Path() addrs.ModuleInstance {
   111  	return n.Addr.Module
   112  }
   113  
   114  // GraphNodeModulePath
   115  func (n *graphNodeImportState) ModulePath() addrs.Module {
   116  	return n.Addr.Module.Module()
   117  }
   118  
   119  // GraphNodeExecutable impl.
   120  func (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.
   169  func (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.
   243  type graphNodeImportStateSub struct {
   244  	TargetAddr       addrs.AbsResourceInstance
   245  	State            providers.ImportedResource
   246  	ResolvedProvider addrs.AbsProviderConfig
   247  }
   248  
   249  var (
   250  	_ GraphNodeModuleInstance = (*graphNodeImportStateSub)(nil)
   251  	_ GraphNodeExecutable     = (*graphNodeImportStateSub)(nil)
   252  )
   253  
   254  func (n *graphNodeImportStateSub) Name() string {
   255  	return fmt.Sprintf("import %s result", n.TargetAddr)
   256  }
   257  
   258  func (n *graphNodeImportStateSub) Path() addrs.ModuleInstance {
   259  	return n.TargetAddr.Module
   260  }
   261  
   262  // GraphNodeExecutable impl.
   263  func (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  }