mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-24 16:10:46 -06:00
Plannable import 3: Make import plannable (#33085)
During a plan, Terraform now checks for the presence of import blocks. For each resource in config, if an import block is present with a matching address, planning that node will now trigger an ImportResourceState and ReadResource. The resulting state is treated as the node's "refresh state", and planning proceeds as normal from there. The walkImport operation is now only used for the legacy "terraform import" CLI command. This is the only case under which the plan should produce graphNodeImportStates.
This commit is contained in:
parent
b3a49a2fa7
commit
28643516b2
@ -37,6 +37,10 @@ func (c *Changes) Empty() bool {
|
||||
if res.Action != NoOp || res.Moved() {
|
||||
return false
|
||||
}
|
||||
|
||||
if res.Importing {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for _, out := range c.Outputs {
|
||||
@ -301,9 +305,10 @@ func (rc *ResourceInstanceChange) Simplify(destroying bool) *ResourceInstanceCha
|
||||
Private: rc.Private,
|
||||
ProviderAddr: rc.ProviderAddr,
|
||||
Change: Change{
|
||||
Action: Delete,
|
||||
Before: rc.Before,
|
||||
After: cty.NullVal(rc.Before.Type()),
|
||||
Action: Delete,
|
||||
Before: rc.Before,
|
||||
After: cty.NullVal(rc.Before.Type()),
|
||||
Importing: rc.Importing,
|
||||
},
|
||||
}
|
||||
default:
|
||||
@ -313,9 +318,10 @@ func (rc *ResourceInstanceChange) Simplify(destroying bool) *ResourceInstanceCha
|
||||
Private: rc.Private,
|
||||
ProviderAddr: rc.ProviderAddr,
|
||||
Change: Change{
|
||||
Action: NoOp,
|
||||
Before: rc.Before,
|
||||
After: rc.Before,
|
||||
Action: NoOp,
|
||||
Before: rc.Before,
|
||||
After: rc.Before,
|
||||
Importing: rc.Importing,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -328,9 +334,10 @@ func (rc *ResourceInstanceChange) Simplify(destroying bool) *ResourceInstanceCha
|
||||
Private: rc.Private,
|
||||
ProviderAddr: rc.ProviderAddr,
|
||||
Change: Change{
|
||||
Action: NoOp,
|
||||
Before: rc.Before,
|
||||
After: rc.Before,
|
||||
Action: NoOp,
|
||||
Before: rc.Before,
|
||||
After: rc.Before,
|
||||
Importing: rc.Importing,
|
||||
},
|
||||
}
|
||||
case CreateThenDelete, DeleteThenCreate:
|
||||
@ -340,9 +347,10 @@ func (rc *ResourceInstanceChange) Simplify(destroying bool) *ResourceInstanceCha
|
||||
Private: rc.Private,
|
||||
ProviderAddr: rc.ProviderAddr,
|
||||
Change: Change{
|
||||
Action: Create,
|
||||
Before: cty.NullVal(rc.After.Type()),
|
||||
After: rc.After,
|
||||
Action: Create,
|
||||
Before: cty.NullVal(rc.After.Type()),
|
||||
After: rc.After,
|
||||
Importing: rc.Importing,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -548,5 +556,6 @@ func (c *Change) Encode(ty cty.Type) (*ChangeSrc, error) {
|
||||
After: afterDV,
|
||||
BeforeValMarks: beforeVM,
|
||||
AfterValMarks: afterVM,
|
||||
Importing: c.Importing,
|
||||
}, nil
|
||||
}
|
||||
|
@ -230,8 +230,9 @@ func (cs *ChangeSrc) Decode(ty cty.Type) (*Change, error) {
|
||||
}
|
||||
|
||||
return &Change{
|
||||
Action: cs.Action,
|
||||
Before: before.MarkWithPaths(cs.BeforeValMarks),
|
||||
After: after.MarkWithPaths(cs.AfterValMarks),
|
||||
Action: cs.Action,
|
||||
Before: before.MarkWithPaths(cs.BeforeValMarks),
|
||||
After: after.MarkWithPaths(cs.AfterValMarks),
|
||||
Importing: cs.Importing,
|
||||
}, nil
|
||||
}
|
||||
|
@ -70,6 +70,10 @@ type PlanOpts struct {
|
||||
// outside of Terraform), thereby hopefully replacing it with a
|
||||
// fully-functional new object.
|
||||
ForceReplace []addrs.AbsResourceInstance
|
||||
|
||||
// ImportTargets is a list of target resources to import. These resources
|
||||
// will be added to the plan graph.
|
||||
ImportTargets []*ImportTarget
|
||||
}
|
||||
|
||||
// Plan generates an execution plan by comparing the given configuration
|
||||
@ -285,6 +289,7 @@ func (c *Context) plan(config *configs.Config, prevRunState *states.State, opts
|
||||
panic(fmt.Sprintf("called Context.plan with %s", opts.Mode))
|
||||
}
|
||||
|
||||
opts.ImportTargets = c.findImportBlocks(config)
|
||||
plan, walkDiags := c.planWalk(config, prevRunState, opts)
|
||||
diags = diags.Append(walkDiags)
|
||||
|
||||
@ -505,6 +510,17 @@ func (c *Context) postPlanValidateMoves(config *configs.Config, stmts []refactor
|
||||
return refactoring.ValidateMoves(stmts, config, allInsts)
|
||||
}
|
||||
|
||||
func (c *Context) findImportBlocks(config *configs.Config) []*ImportTarget {
|
||||
var importTargets []*ImportTarget
|
||||
for _, ic := range config.Module.Import {
|
||||
importTargets = append(importTargets, &ImportTarget{
|
||||
Addr: ic.To,
|
||||
ID: ic.ID,
|
||||
})
|
||||
}
|
||||
return importTargets
|
||||
}
|
||||
|
||||
func (c *Context) planWalk(config *configs.Config, prevRunState *states.State, opts *PlanOpts) (*plans.Plan, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
log.Printf("[DEBUG] Building and walking plan graph for %s", opts.Mode)
|
||||
@ -605,6 +621,7 @@ func (c *Context) planGraph(config *configs.Config, prevRunState *states.State,
|
||||
skipRefresh: opts.SkipRefresh,
|
||||
preDestroyRefresh: opts.PreDestroyRefresh,
|
||||
Operation: walkPlan,
|
||||
ImportTargets: opts.ImportTargets,
|
||||
}).Build(addrs.RootModuleInstance)
|
||||
return graph, walkPlan, diags
|
||||
case plans.RefreshOnlyMode:
|
||||
@ -784,7 +801,7 @@ func (c *Context) driftedResources(config *configs.Config, oldState, newState *s
|
||||
// (as opposed to graphs as an implementation detail) intended only for use
|
||||
// by the "terraform graph" command when asked to render a plan-time graph.
|
||||
//
|
||||
// The result of this is intended only for rendering ot the user as a dot
|
||||
// The result of this is intended only for rendering to the user as a dot
|
||||
// graph, and so may change in future in order to make the result more useful
|
||||
// in that context, even if drifts away from the physical graph that Terraform
|
||||
// Core currently uses as an implementation detail of planning.
|
||||
|
@ -4098,3 +4098,203 @@ resource "test_object" "a" {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_importResourceBasic(t *testing.T) {
|
||||
addr := mustResourceInstanceAddr("test_object.a")
|
||||
m := testModuleInline(t, map[string]string{
|
||||
"main.tf": `
|
||||
resource "test_object" "a" {
|
||||
test_string = "foo"
|
||||
}
|
||||
|
||||
import {
|
||||
to = test_object.a
|
||||
id = "123"
|
||||
}
|
||||
`,
|
||||
})
|
||||
|
||||
p := simpleMockProvider()
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
p.ReadResourceResponse = &providers.ReadResourceResponse{
|
||||
NewState: cty.ObjectVal(map[string]cty.Value{
|
||||
"test_string": cty.StringVal("foo"),
|
||||
}),
|
||||
}
|
||||
p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
|
||||
ImportedResources: []providers.ImportedResource{
|
||||
{
|
||||
TypeName: "test_object",
|
||||
State: cty.ObjectVal(map[string]cty.Value{
|
||||
"test_string": cty.StringVal("foo"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("unexpected errors\n%s", diags.Err().Error())
|
||||
}
|
||||
|
||||
t.Run(addr.String(), func(t *testing.T) {
|
||||
instPlan := plan.Changes.ResourceInstance(addr)
|
||||
if instPlan == nil {
|
||||
t.Fatalf("no plan for %s at all", addr)
|
||||
}
|
||||
|
||||
if got, want := instPlan.Addr, addr; !got.Equal(want) {
|
||||
t.Errorf("wrong current address\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
if got, want := instPlan.PrevRunAddr, addr; !got.Equal(want) {
|
||||
t.Errorf("wrong previous run address\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
if got, want := instPlan.Action, plans.NoOp; got != want {
|
||||
t.Errorf("wrong planned action\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
|
||||
t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
if !instPlan.Importing {
|
||||
t.Errorf("expected import change, got non-import change")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestContext2Plan_importResourceUpdate(t *testing.T) {
|
||||
addr := mustResourceInstanceAddr("test_object.a")
|
||||
m := testModuleInline(t, map[string]string{
|
||||
"main.tf": `
|
||||
resource "test_object" "a" {
|
||||
test_string = "bar"
|
||||
}
|
||||
|
||||
import {
|
||||
to = test_object.a
|
||||
id = "123"
|
||||
}
|
||||
`,
|
||||
})
|
||||
|
||||
p := simpleMockProvider()
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
p.ReadResourceResponse = &providers.ReadResourceResponse{
|
||||
NewState: cty.ObjectVal(map[string]cty.Value{
|
||||
"test_string": cty.StringVal("foo"),
|
||||
}),
|
||||
}
|
||||
p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
|
||||
ImportedResources: []providers.ImportedResource{
|
||||
{
|
||||
TypeName: "test_object",
|
||||
State: cty.ObjectVal(map[string]cty.Value{
|
||||
"test_string": cty.StringVal("foo"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("unexpected errors\n%s", diags.Err().Error())
|
||||
}
|
||||
|
||||
t.Run(addr.String(), func(t *testing.T) {
|
||||
instPlan := plan.Changes.ResourceInstance(addr)
|
||||
if instPlan == nil {
|
||||
t.Fatalf("no plan for %s at all", addr)
|
||||
}
|
||||
|
||||
if got, want := instPlan.Addr, addr; !got.Equal(want) {
|
||||
t.Errorf("wrong current address\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
if got, want := instPlan.PrevRunAddr, addr; !got.Equal(want) {
|
||||
t.Errorf("wrong previous run address\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
if got, want := instPlan.Action, plans.Update; got != want {
|
||||
t.Errorf("wrong planned action\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
|
||||
t.Errorf("wrong action reason\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
if !instPlan.Importing {
|
||||
t.Errorf("expected import change, got non-import change")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestContext2Plan_importResourceReplace(t *testing.T) {
|
||||
addr := mustResourceInstanceAddr("test_object.a")
|
||||
m := testModuleInline(t, map[string]string{
|
||||
"main.tf": `
|
||||
resource "test_object" "a" {
|
||||
test_string = "bar"
|
||||
}
|
||||
|
||||
import {
|
||||
to = test_object.a
|
||||
id = "123"
|
||||
}
|
||||
`,
|
||||
})
|
||||
|
||||
p := simpleMockProvider()
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
p.ReadResourceResponse = &providers.ReadResourceResponse{
|
||||
NewState: cty.ObjectVal(map[string]cty.Value{
|
||||
"test_string": cty.StringVal("foo"),
|
||||
}),
|
||||
}
|
||||
p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
|
||||
ImportedResources: []providers.ImportedResource{
|
||||
{
|
||||
TypeName: "test_object",
|
||||
State: cty.ObjectVal(map[string]cty.Value{
|
||||
"test_string": cty.StringVal("foo"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
|
||||
Mode: plans.NormalMode,
|
||||
ForceReplace: []addrs.AbsResourceInstance{
|
||||
addr,
|
||||
},
|
||||
})
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("unexpected errors\n%s", diags.Err().Error())
|
||||
}
|
||||
|
||||
t.Run(addr.String(), func(t *testing.T) {
|
||||
instPlan := plan.Changes.ResourceInstance(addr)
|
||||
if instPlan == nil {
|
||||
t.Fatalf("no plan for %s at all", addr)
|
||||
}
|
||||
|
||||
if got, want := instPlan.Addr, addr; !got.Equal(want) {
|
||||
t.Errorf("wrong current address\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
if got, want := instPlan.PrevRunAddr, addr; !got.Equal(want) {
|
||||
t.Errorf("wrong previous run address\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
if got, want := instPlan.Action, plans.DeleteThenCreate; got != want {
|
||||
t.Errorf("wrong planned action\ngot: %s\nwant: %s", got, want)
|
||||
}
|
||||
if !instPlan.Importing {
|
||||
t.Errorf("expected import change, got non-import change")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -309,6 +309,13 @@ func (b *PlanGraphBuilder) initImport() {
|
||||
// as the new state, and users are not expecting the import process
|
||||
// to update any other instances in state.
|
||||
skipRefresh: true,
|
||||
|
||||
// If we get here, we know that we are in legacy import mode, and
|
||||
// that the user has run the import command rather than plan.
|
||||
// This flag must be propagated down to the
|
||||
// NodePlannableResourceInstance so we can ignore the new import
|
||||
// behaviour.
|
||||
legacyImportMode: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,12 +27,13 @@ type ContextGraphWalker struct {
|
||||
|
||||
// Configurable values
|
||||
Context *Context
|
||||
State *states.SyncState // Used for safe concurrent access to state
|
||||
RefreshState *states.SyncState // Used for safe concurrent access to state
|
||||
PrevRunState *states.SyncState // Used for safe concurrent access to state
|
||||
Changes *plans.ChangesSync // Used for safe concurrent writes to changes
|
||||
Checks *checks.State // Used for safe concurrent writes of checkable objects and their check results
|
||||
InstanceExpander *instances.Expander // Tracks our gradual expansion of module and resource instances
|
||||
State *states.SyncState // Used for safe concurrent access to state
|
||||
RefreshState *states.SyncState // Used for safe concurrent access to state
|
||||
PrevRunState *states.SyncState // Used for safe concurrent access to state
|
||||
Changes *plans.ChangesSync // Used for safe concurrent writes to changes
|
||||
Checks *checks.State // Used for safe concurrent writes of checkable objects and their check results
|
||||
InstanceExpander *instances.Expander // Tracks our gradual expansion of module and resource instances
|
||||
Imports []configs.Import
|
||||
MoveResults refactoring.MoveResults // Read-only record of earlier processing of move statements
|
||||
Operation walkOperation
|
||||
StopContext context.Context
|
||||
|
@ -131,10 +131,6 @@ func (n *NodeAbstractResource) ReferenceableAddrs() []addrs.Referenceable {
|
||||
return []addrs.Referenceable{n.Addr.Resource}
|
||||
}
|
||||
|
||||
func (n *NodeAbstractResource) Import(addr *ImportTarget) {
|
||||
|
||||
}
|
||||
|
||||
// GraphNodeReferencer
|
||||
func (n *NodeAbstractResource) References() []*addrs.Reference {
|
||||
// If we have a config then we prefer to use that.
|
||||
|
@ -43,6 +43,10 @@ type nodeExpandPlannableResource struct {
|
||||
// structure in the future, as we need to compare for equality and take the
|
||||
// union of multiple groups of dependencies.
|
||||
dependencies []addrs.ConfigResource
|
||||
|
||||
// legacyImportMode is set if the graph is being constructed following an
|
||||
// invocation of the legacy "terraform import" CLI command.
|
||||
legacyImportMode bool
|
||||
}
|
||||
|
||||
var (
|
||||
@ -311,13 +315,18 @@ func (n *nodeExpandPlannableResource) resourceInstanceSubgraph(ctx EvalContext,
|
||||
|
||||
// The concrete resource factory we'll use
|
||||
concreteResource := func(a *NodeAbstractResourceInstance) dag.Vertex {
|
||||
// check if this node is being imported first
|
||||
for _, importTarget := range n.importTargets {
|
||||
if importTarget.Addr.Equal(a.Addr) {
|
||||
return &graphNodeImportState{
|
||||
Addr: importTarget.Addr,
|
||||
ID: importTarget.ID,
|
||||
ResolvedProvider: n.ResolvedProvider,
|
||||
var m *NodePlannableResourceInstance
|
||||
|
||||
// If we're in legacy import mode (the import CLI command), we only need
|
||||
// to return the import node, not a plannable resource node.
|
||||
if n.legacyImportMode {
|
||||
for _, importTarget := range n.importTargets {
|
||||
if importTarget.Addr.Equal(a.Addr) {
|
||||
return &graphNodeImportState{
|
||||
Addr: importTarget.Addr,
|
||||
ID: importTarget.ID,
|
||||
ResolvedProvider: n.ResolvedProvider,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -332,7 +341,7 @@ func (n *nodeExpandPlannableResource) resourceInstanceSubgraph(ctx EvalContext,
|
||||
a.Dependencies = n.dependencies
|
||||
a.preDestroyRefresh = n.preDestroyRefresh
|
||||
|
||||
return &NodePlannableResourceInstance{
|
||||
m = &NodePlannableResourceInstance{
|
||||
NodeAbstractResourceInstance: a,
|
||||
|
||||
// By the time we're walking, we've figured out whether we need
|
||||
@ -343,6 +352,19 @@ func (n *nodeExpandPlannableResource) resourceInstanceSubgraph(ctx EvalContext,
|
||||
skipPlanChanges: n.skipPlanChanges,
|
||||
forceReplace: n.forceReplace,
|
||||
}
|
||||
|
||||
for _, importTarget := range n.importTargets {
|
||||
if importTarget.Addr.Equal(a.Addr) {
|
||||
// If we get here, we're definitely not in legacy import mode,
|
||||
// so go ahead and plan the resource changes including import.
|
||||
m.importTarget = ImportTarget{
|
||||
ID: importTarget.ID,
|
||||
Addr: importTarget.Addr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// The concrete resource factory we'll use for orphans
|
||||
|
@ -5,13 +5,14 @@ import (
|
||||
"log"
|
||||
"sort"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/instances"
|
||||
"github.com/hashicorp/terraform/internal/plans"
|
||||
"github.com/hashicorp/terraform/internal/providers"
|
||||
"github.com/hashicorp/terraform/internal/states"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// NodePlannableResourceInstance represents a _single_ resource
|
||||
@ -37,6 +38,10 @@ type NodePlannableResourceInstance struct {
|
||||
// replaceTriggeredBy stores references from replace_triggered_by which
|
||||
// triggered this instance to be replaced.
|
||||
replaceTriggeredBy []*addrs.Reference
|
||||
|
||||
// importTarget, if populated, contains the information necessary to plan
|
||||
// an import of this resource.
|
||||
importTarget ImportTarget
|
||||
}
|
||||
|
||||
var (
|
||||
@ -133,7 +138,7 @@ func (n *NodePlannableResourceInstance) managedResourceExecute(ctx EvalContext)
|
||||
checkRuleSeverity = tfdiags.Warning
|
||||
}
|
||||
|
||||
_, providerSchema, err := getProvider(ctx, n.ResolvedProvider)
|
||||
provider, providerSchema, err := getProvider(ctx, n.ResolvedProvider)
|
||||
diags = diags.Append(err)
|
||||
if diags.HasErrors() {
|
||||
return diags
|
||||
@ -144,10 +149,17 @@ func (n *NodePlannableResourceInstance) managedResourceExecute(ctx EvalContext)
|
||||
return diags
|
||||
}
|
||||
|
||||
instanceRefreshState, readDiags := n.readResourceInstanceState(ctx, addr)
|
||||
diags = diags.Append(readDiags)
|
||||
if diags.HasErrors() {
|
||||
return diags
|
||||
// If the resource is to be imported, we now ask the provider for an Import
|
||||
// and a Refresh, and save the resulting state to instanceRefreshState.
|
||||
if n.importTarget.ID != "" {
|
||||
instanceRefreshState, diags = n.importState(ctx, addr, provider)
|
||||
} else {
|
||||
var readDiags tfdiags.Diagnostics
|
||||
instanceRefreshState, readDiags = n.readResourceInstanceState(ctx, addr)
|
||||
diags = diags.Append(readDiags)
|
||||
if diags.HasErrors() {
|
||||
return diags
|
||||
}
|
||||
}
|
||||
|
||||
// We'll save a snapshot of what we just read from the state into the
|
||||
@ -228,6 +240,10 @@ func (n *NodePlannableResourceInstance) managedResourceExecute(ctx EvalContext)
|
||||
return diags
|
||||
}
|
||||
|
||||
if n.importTarget.ID != "" {
|
||||
change.Importing = true
|
||||
}
|
||||
|
||||
// FIXME: here we udpate the change to reflect the reason for
|
||||
// replacement, but we still overload forceReplace to get the correct
|
||||
// change planned.
|
||||
@ -364,6 +380,103 @@ func (n *NodePlannableResourceInstance) replaceTriggered(ctx EvalContext, repDat
|
||||
return diags
|
||||
}
|
||||
|
||||
func (n *NodePlannableResourceInstance) importState(ctx EvalContext, addr addrs.AbsResourceInstance, provider providers.Interface) (instanceRefreshState *states.ResourceInstanceObject, diags tfdiags.Diagnostics) {
|
||||
absAddr := addr.Resource.Absolute(ctx.Path())
|
||||
|
||||
diags = diags.Append(ctx.Hook(func(h Hook) (HookAction, error) {
|
||||
return h.PreImportState(absAddr, n.importTarget.ID)
|
||||
}))
|
||||
if diags.HasErrors() {
|
||||
return instanceRefreshState, diags
|
||||
}
|
||||
|
||||
resp := provider.ImportResourceState(providers.ImportResourceStateRequest{
|
||||
TypeName: addr.Resource.Resource.Type,
|
||||
ID: n.importTarget.ID,
|
||||
})
|
||||
diags = diags.Append(resp.Diagnostics)
|
||||
if diags.HasErrors() {
|
||||
return instanceRefreshState, diags
|
||||
}
|
||||
|
||||
imported := resp.ImportedResources
|
||||
|
||||
if len(imported) == 0 {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Import returned no resources",
|
||||
fmt.Sprintf("While attempting to import with ID %s, the provider"+
|
||||
"returned no instance states.",
|
||||
n.importTarget.ID,
|
||||
),
|
||||
))
|
||||
return instanceRefreshState, diags
|
||||
}
|
||||
for _, obj := range imported {
|
||||
log.Printf("[TRACE] graphNodeImportState: import %s %q produced instance object of type %s", absAddr.String(), n.importTarget.ID, obj.TypeName)
|
||||
}
|
||||
if len(imported) > 1 {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Multiple import states not supported",
|
||||
fmt.Sprintf("While attempting to import with ID %s, the provider "+
|
||||
"returned multiple resource instance states. This "+
|
||||
"is not currently supported.",
|
||||
n.importTarget.ID,
|
||||
),
|
||||
))
|
||||
return instanceRefreshState, diags
|
||||
}
|
||||
|
||||
// call post-import hook
|
||||
diags = diags.Append(ctx.Hook(func(h Hook) (HookAction, error) {
|
||||
return h.PostImportState(absAddr, imported)
|
||||
}))
|
||||
|
||||
if imported[0].TypeName == "" {
|
||||
diags = diags.Append(fmt.Errorf("import of %s didn't set type", n.importTarget.Addr.String()))
|
||||
return instanceRefreshState, diags
|
||||
}
|
||||
|
||||
importedState := imported[0].AsInstanceObject()
|
||||
|
||||
// refresh
|
||||
riNode := &NodeAbstractResourceInstance{
|
||||
Addr: n.importTarget.Addr,
|
||||
NodeAbstractResource: NodeAbstractResource{
|
||||
ResolvedProvider: n.ResolvedProvider,
|
||||
},
|
||||
}
|
||||
importedState, refreshDiags := riNode.refresh(ctx, states.NotDeposed, importedState)
|
||||
diags = diags.Append(refreshDiags)
|
||||
if diags.HasErrors() {
|
||||
return instanceRefreshState, diags
|
||||
}
|
||||
|
||||
// verify the existence of the imported resource
|
||||
if importedState.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.importTarget.Addr,
|
||||
),
|
||||
))
|
||||
return instanceRefreshState, diags
|
||||
}
|
||||
|
||||
diags = diags.Append(riNode.writeResourceInstanceState(ctx, importedState, workingState))
|
||||
instanceRefreshState = importedState
|
||||
return instanceRefreshState, diags
|
||||
}
|
||||
|
||||
// mergeDeps returns the union of 2 sets of dependencies
|
||||
func mergeDeps(a, b []addrs.ConfigResource) []addrs.ConfigResource {
|
||||
switch {
|
||||
|
Loading…
Reference in New Issue
Block a user