terraform/internal/terraform/transform_import_state.go

307 lines
9.3 KiB
Go

package terraform
import (
"fmt"
"log"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/states"
"github.com/hashicorp/terraform/internal/tfdiags"
)
// ImportStateTransformer is a GraphTransformer that adds nodes to the
// graph to represent the imports we want to do for resources.
type ImportStateTransformer struct {
Targets []*ImportTarget
Config *configs.Config
}
func (t *ImportStateTransformer) Transform(g *Graph) error {
for _, target := range t.Targets {
// This is only likely to happen in misconfigured tests
if t.Config == nil {
return fmt.Errorf("cannot import into an empty configuration")
}
// Get the module config
modCfg := t.Config.Descendent(target.Addr.Module.Module())
if modCfg == nil {
return fmt.Errorf("module %s not found", target.Addr.Module.Module())
}
providerAddr := addrs.AbsProviderConfig{
Module: target.Addr.Module.Module(),
}
// Try to find the resource config
rsCfg := modCfg.Module.ResourceByAddr(target.Addr.Resource.Resource)
if rsCfg != nil {
// Get the provider FQN for the resource from the resource configuration
providerAddr.Provider = rsCfg.Provider
// Get the alias from the resource's provider local config
providerAddr.Alias = rsCfg.ProviderConfigAddr().Alias
} else {
// Resource has no matching config, so use an implied provider
// based on the resource type
rsProviderType := target.Addr.Resource.Resource.ImpliedProvider()
providerAddr.Provider = modCfg.Module.ImpliedProviderForUnqualifiedType(rsProviderType)
}
node := &graphNodeImportState{
Addr: target.Addr,
ID: target.ID,
ProviderAddr: providerAddr,
}
g.Add(node)
}
return nil
}
type graphNodeImportState struct {
Addr addrs.AbsResourceInstance // Addr is the resource address to import into
ID string // ID is the ID to import as
ProviderAddr addrs.AbsProviderConfig // Provider address given by the user, or implied by the resource type
ResolvedProvider addrs.AbsProviderConfig // provider node address after resolution
states []providers.ImportedResource
}
var (
_ GraphNodeModulePath = (*graphNodeImportState)(nil)
_ GraphNodeExecutable = (*graphNodeImportState)(nil)
_ GraphNodeProviderConsumer = (*graphNodeImportState)(nil)
_ GraphNodeDynamicExpandable = (*graphNodeImportState)(nil)
)
func (n *graphNodeImportState) Name() string {
return fmt.Sprintf("%s (import id %q)", n.Addr, n.ID)
}
// GraphNodeProviderConsumer
func (n *graphNodeImportState) ProvidedBy() (addrs.ProviderConfig, bool) {
// We assume that n.ProviderAddr has been properly populated here.
// It's the responsibility of the code creating a graphNodeImportState
// to populate this, possibly by calling DefaultProviderConfig() on the
// resource address to infer an implied provider from the resource type
// name.
return n.ProviderAddr, false
}
// GraphNodeProviderConsumer
func (n *graphNodeImportState) Provider() addrs.Provider {
// We assume that n.ProviderAddr has been properly populated here.
// It's the responsibility of the code creating a graphNodeImportState
// to populate this, possibly by calling DefaultProviderConfig() on the
// resource address to infer an implied provider from the resource type
// name.
return n.ProviderAddr.Provider
}
// GraphNodeProviderConsumer
func (n *graphNodeImportState) SetProvider(addr addrs.AbsProviderConfig) {
n.ResolvedProvider = addr
}
// GraphNodeModuleInstance
func (n *graphNodeImportState) Path() addrs.ModuleInstance {
return n.Addr.Module
}
// GraphNodeModulePath
func (n *graphNodeImportState) ModulePath() addrs.Module {
return n.Addr.Module.Module()
}
// GraphNodeExecutable impl.
func (n *graphNodeImportState) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) {
// Reset our states
n.states = nil
provider, _, err := getProvider(ctx, n.ResolvedProvider)
diags = diags.Append(err)
if diags.HasErrors() {
return diags
}
// import state
absAddr := n.Addr.Resource.Absolute(ctx.Path())
// Call pre-import hook
diags = diags.Append(ctx.Hook(func(h Hook) (HookAction, error) {
return h.PreImportState(absAddr, n.ID)
}))
if diags.HasErrors() {
return diags
}
resp := provider.ImportResourceState(providers.ImportResourceStateRequest{
TypeName: n.Addr.Resource.Resource.Type,
ID: n.ID,
})
diags = diags.Append(resp.Diagnostics)
if diags.HasErrors() {
return diags
}
imported := resp.ImportedResources
for _, obj := range imported {
log.Printf("[TRACE] graphNodeImportState: import %s %q produced instance object of type %s", absAddr.String(), n.ID, obj.TypeName)
}
n.states = imported
// Call post-import hook
diags = diags.Append(ctx.Hook(func(h Hook) (HookAction, error) {
return h.PostImportState(absAddr, imported)
}))
return diags
}
// GraphNodeDynamicExpandable impl.
//
// We use DynamicExpand as a way to generate the subgraph of refreshes
// and state inserts we need to do for our import state. Since they're new
// resources they don't depend on anything else and refreshes are isolated
// so this is nearly a perfect use case for dynamic expand.
func (n *graphNodeImportState) DynamicExpand(ctx EvalContext) (*Graph, error) {
var diags tfdiags.Diagnostics
g := &Graph{Path: ctx.Path()}
// nameCounter is used to de-dup names in the state.
nameCounter := make(map[string]int)
// Compile the list of addresses that we'll be inserting into the state.
// We do this ahead of time so we can verify that we aren't importing
// something that already exists.
addrs := make([]addrs.AbsResourceInstance, len(n.states))
for i, state := range n.states {
addr := n.Addr
if t := state.TypeName; t != "" {
addr.Resource.Resource.Type = t
}
// Determine if we need to suffix the name to de-dup
key := addr.String()
count, ok := nameCounter[key]
if ok {
count++
addr.Resource.Resource.Name += fmt.Sprintf("-%d", count)
}
nameCounter[key] = count
// Add it to our list
addrs[i] = addr
}
// Verify that all the addresses are clear
state := ctx.State()
for _, addr := range addrs {
existing := state.ResourceInstance(addr)
if existing != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Resource already managed by Terraform",
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),
))
continue
}
}
if diags.HasErrors() {
// Bail out early, then.
return nil, diags.Err()
}
// For each of the states, we add a node to handle the refresh/add to state.
// "n.states" is populated by our own Execute with the result of
// ImportState. Since DynamicExpand is always called after Execute, this is
// safe.
for i, state := range n.states {
g.Add(&graphNodeImportStateSub{
TargetAddr: addrs[i],
State: state,
ResolvedProvider: n.ResolvedProvider,
})
}
// Root transform for a single root
t := &RootTransformer{}
if err := t.Transform(g); err != nil {
return nil, err
}
// Done!
return g, diags.Err()
}
// graphNodeImportStateSub is the sub-node of graphNodeImportState
// and is part of the subgraph. This node is responsible for refreshing
// and adding a resource to the state once it is imported.
type graphNodeImportStateSub struct {
TargetAddr addrs.AbsResourceInstance
State providers.ImportedResource
ResolvedProvider addrs.AbsProviderConfig
}
var (
_ GraphNodeModuleInstance = (*graphNodeImportStateSub)(nil)
_ GraphNodeExecutable = (*graphNodeImportStateSub)(nil)
)
func (n *graphNodeImportStateSub) Name() string {
return fmt.Sprintf("import %s result", n.TargetAddr)
}
func (n *graphNodeImportStateSub) Path() addrs.ModuleInstance {
return n.TargetAddr.Module
}
// GraphNodeExecutable impl.
func (n *graphNodeImportStateSub) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) {
// If the Ephemeral type isn't set, then it is an error
if n.State.TypeName == "" {
diags = diags.Append(fmt.Errorf("import of %s didn't set type", n.TargetAddr.String()))
return diags
}
state := n.State.AsInstanceObject()
// Refresh
riNode := &NodeAbstractResourceInstance{
Addr: n.TargetAddr,
NodeAbstractResource: NodeAbstractResource{
ResolvedProvider: n.ResolvedProvider,
},
}
state, refreshDiags := riNode.refresh(ctx, states.NotDeposed, state)
diags = diags.Append(refreshDiags)
if diags.HasErrors() {
return diags
}
// Verify the existance of the imported resource
if state.Value.IsNull() {
var diags tfdiags.Diagnostics
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Cannot import non-existent remote object",
fmt.Sprintf(
"While attempting to import an existing object to %q, "+
"the provider detected that no object exists with the given id. "+
"Only pre-existing objects can be imported; check that the id "+
"is correct and that it is associated with the provider's "+
"configured region or endpoint, or use \"terraform apply\" to "+
"create a new remote object for this resource.",
n.TargetAddr,
),
))
return diags
}
diags = diags.Append(riNode.writeResourceInstanceState(ctx, state, workingState))
return diags
}