mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
terraform: config-driven import is idempotent (#33188)
If a resource is already in state, do not attempt to import it again. Resources already in state are filtered out of the plan's import targets. A change is only considered "importing" if it is adding a new resource instance to the state.
This commit is contained in:
parent
5d7864316e
commit
2b71e9edf3
@ -292,7 +292,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)
|
||||
opts.ImportTargets = c.findImportTargets(config, prevRunState)
|
||||
plan, walkDiags := c.planWalk(config, prevRunState, opts)
|
||||
diags = diags.Append(walkDiags)
|
||||
|
||||
@ -513,14 +513,18 @@ func (c *Context) postPlanValidateMoves(config *configs.Config, stmts []refactor
|
||||
return refactoring.ValidateMoves(stmts, config, allInsts)
|
||||
}
|
||||
|
||||
func (c *Context) findImportBlocks(config *configs.Config) []*ImportTarget {
|
||||
// findImportTargets builds a list of import targets by taking the import blocks
|
||||
// in the config and filtering out any that target a resource already in state.
|
||||
func (c *Context) findImportTargets(config *configs.Config, priorState *states.State) []*ImportTarget {
|
||||
var importTargets []*ImportTarget
|
||||
for _, ic := range config.Module.Import {
|
||||
importTargets = append(importTargets, &ImportTarget{
|
||||
Addr: ic.To,
|
||||
ID: ic.ID,
|
||||
Config: ic,
|
||||
})
|
||||
if priorState.ResourceInstance(ic.To) == nil {
|
||||
importTargets = append(importTargets, &ImportTarget{
|
||||
Addr: ic.To,
|
||||
ID: ic.ID,
|
||||
Config: ic,
|
||||
})
|
||||
}
|
||||
}
|
||||
return importTargets
|
||||
}
|
||||
|
@ -4184,6 +4184,83 @@ import {
|
||||
})
|
||||
}
|
||||
|
||||
func TestContext2Plan_importResourceAlreadyInState(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"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
state := states.NewState()
|
||||
root := state.EnsureModule(addrs.RootModuleInstance)
|
||||
root.SetResourceInstanceCurrent(
|
||||
mustResourceInstanceAddr("test_object.a").Resource,
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectReady,
|
||||
AttrsJSON: []byte(`{"test_string":"foo"}`),
|
||||
},
|
||||
mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
|
||||
)
|
||||
|
||||
plan, diags := ctx.Plan(m, state, 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 != nil {
|
||||
t.Errorf("expected non-import change, got import change %+v", instPlan.Importing)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestContext2Plan_importResourceUpdate(t *testing.T) {
|
||||
addr := mustResourceInstanceAddr("test_object.a")
|
||||
m := testModuleInline(t, map[string]string{
|
||||
|
@ -252,7 +252,7 @@ func (n *NodePlannableResourceInstance) managedResourceExecute(ctx EvalContext)
|
||||
return diags
|
||||
}
|
||||
|
||||
if n.importTarget.ID != "" {
|
||||
if importing {
|
||||
change.Importing = &plans.Importing{ID: n.importTarget.ID}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user