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:
kmoe 2023-05-12 21:31:29 +01:00 committed by GitHub
parent 5d7864316e
commit 2b71e9edf3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 89 additions and 8 deletions

View File

@ -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
}

View File

@ -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{

View File

@ -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}
}