diff --git a/internal/terraform/context_apply_deferred_test.go b/internal/terraform/context_apply_deferred_test.go index bc88bb8561..c947541052 100644 --- a/internal/terraform/context_apply_deferred_test.go +++ b/internal/terraform/context_apply_deferred_test.go @@ -2025,6 +2025,63 @@ output "a" { }, }, } + + importDeferredTest = deferredActionsTest{ + configs: map[string]string{ + "main.tf": ` +variable "import_id" { + type = string +} + +resource "test" "a" { + name = "a" +} + +import { + id = var.import_id + to = test.a +} +`, + }, + stages: []deferredActionsTestStage{ + { + inputs: map[string]cty.Value{ + "import_id": cty.StringVal("deferred"), // Telling the test case to defer the import + }, + wantPlanned: map[string]cty.Value{ + "a": cty.ObjectVal(map[string]cty.Value{ + "name": cty.StringVal("a"), + "upstream_names": cty.NullVal(cty.Set(cty.String)), + "output": cty.UnknownVal(cty.String), + }), + }, + wantActions: make(map[string]plans.Action), + wantDeferred: map[string]ExpectedDeferred{ + "test.a": {Reason: providers.DeferredReasonAbsentPrereq, Action: plans.NoOp}, + }, + wantApplied: make(map[string]cty.Value), + wantOutputs: make(map[string]cty.Value), + complete: false, + }, + { + inputs: map[string]cty.Value{ + "import_id": cty.StringVal("can_be_imported"), + }, + wantPlanned: map[string]cty.Value{ + "a": cty.ObjectVal(map[string]cty.Value{ + "name": cty.StringVal("a"), + "upstream_names": cty.NullVal(cty.Set(cty.String)), + "output": cty.StringVal("can_be_imported"), + }), + }, + wantActions: map[string]plans.Action{ + "test.a": plans.Update, + }, + wantDeferred: map[string]ExpectedDeferred{}, + complete: true, + }, + }, + } ) func TestContextApply_deferredActions(t *testing.T) { @@ -2050,6 +2107,7 @@ func TestContextApply_deferredActions(t *testing.T) { "plan_force_replace_resource_change": planForceReplaceResourceChange, "plan_delete_resource_change": planDeleteResourceChange, "plan_destroy_resource_change": planDestroyResourceChange, + "import_deferred": importDeferredTest, } for name, test := range tests { @@ -2345,7 +2403,7 @@ func (provider *deferredActionsProvider) Provider() providers.Interface { } }, ImportResourceStateFn: func(request providers.ImportResourceStateRequest) providers.ImportResourceStateResponse { - return providers.ImportResourceStateResponse{ + resp := providers.ImportResourceStateResponse{ ImportedResources: []providers.ImportedResource{ { TypeName: request.TypeName, @@ -2357,6 +2415,13 @@ func (provider *deferredActionsProvider) Provider() providers.Interface { }, }, } + if request.ID == "deferred" { + resp.Deferred = &providers.Deferred{ + Reason: providers.DeferredReasonAbsentPrereq, + } + } + + return resp }, } } diff --git a/internal/terraform/node_resource_plan_instance.go b/internal/terraform/node_resource_plan_instance.go index 9a7b7c12fd..3a99f7e004 100644 --- a/internal/terraform/node_resource_plan_instance.go +++ b/internal/terraform/node_resource_plan_instance.go @@ -534,6 +534,9 @@ func (n *NodePlannableResourceInstance) importState(ctx EvalContext, addr addrs. resp = provider.ImportResourceState(providers.ImportResourceStateRequest{ TypeName: addr.Resource.Resource.Type, ID: importId, + ClientCapabilities: providers.ClientCapabilities{ + DeferralAllowed: ctx.Deferrals().DeferralAllowed(), + }, }) } diags = diags.Append(resp.Diagnostics) @@ -543,7 +546,7 @@ func (n *NodePlannableResourceInstance) importState(ctx EvalContext, addr addrs. imported := resp.ImportedResources - if len(imported) == 0 { + if len(imported) == 0 && resp.Deferred == nil { diags = diags.Append(tfdiags.Sourceless( tfdiags.Error, "Import returned no resources", @@ -570,6 +573,22 @@ func (n *NodePlannableResourceInstance) importState(ctx EvalContext, addr addrs. return nil, diags } + // If the import was deferred we can't do more here + if resp.Deferred != nil { + ctx.Deferrals().ReportResourceInstanceDeferred(n.Addr, resp.Deferred.Reason, &plans.ResourceInstanceChange{ + Addr: n.Addr, + Change: plans.Change{ + Action: plans.NoOp, + Before: cty.UnknownVal(cty.DynamicPseudoType), + After: cty.UnknownVal(cty.DynamicPseudoType), + Importing: &plans.Importing{ + ID: importId, + }, + }, + }) + return nil, diags + } + // call post-import hook diags = diags.Append(ctx.Hook(func(h Hook) (HookAction, error) { return h.PostPlanImport(hookResourceID, imported) @@ -601,14 +620,15 @@ func (n *NodePlannableResourceInstance) importState(ctx EvalContext, addr addrs. }, override: n.override, } - instanceRefreshState, deferred, refreshDiags := riNode.refresh(ctx, states.NotDeposed, importedState) + instanceRefreshState, refreshDeferred, refreshDiags := riNode.refresh(ctx, states.NotDeposed, importedState) diags = diags.Append(refreshDiags) if diags.HasErrors() { return instanceRefreshState, diags } - if deferred != nil { - ctx.Deferrals().ReportResourceInstanceDeferred(n.Addr, deferred.Reason, &plans.ResourceInstanceChange{ + // report the refresh was deferred, we don't need to error since the import step succeeded + if refreshDeferred != nil { + ctx.Deferrals().ReportResourceInstanceDeferred(n.Addr, refreshDeferred.Reason, &plans.ResourceInstanceChange{ Addr: n.Addr, Change: plans.Change{ Action: plans.Read, @@ -618,7 +638,7 @@ func (n *NodePlannableResourceInstance) importState(ctx EvalContext, addr addrs. } // verify the existence of the imported resource - if instanceRefreshState.Value.IsNull() && deferred == nil { + if instanceRefreshState.Value.IsNull() && refreshDeferred == nil { var diags tfdiags.Diagnostics diags = diags.Append(tfdiags.Sourceless( tfdiags.Error,