diff --git a/.changelog/2570.txt b/.changelog/2570.txt new file mode 100644 index 0000000000..56dc6aa807 --- /dev/null +++ b/.changelog/2570.txt @@ -0,0 +1,3 @@ +```release-note:improvement +deferred actions: return unknown value instead of nil when requesting deferral of a planned resource change. +``` \ No newline at end of file diff --git a/.github/workflows/acceptance_test_dfa.yaml b/.github/workflows/acceptance_test_dfa.yaml index d5fb96213f..aa0b99e5d4 100644 --- a/.github/workflows/acceptance_test_dfa.yaml +++ b/.github/workflows/acceptance_test_dfa.yaml @@ -12,7 +12,7 @@ on: inputs: terraformVersion: description: Terraform version - default: 1.9.0-alpha20240516 + default: 1.10.0-alpha20240807 jobs: acceptance_tests: diff --git a/manifest/provider/import.go b/manifest/provider/import.go index cf6417c01f..c0e469c6fa 100644 --- a/manifest/provider/import.go +++ b/manifest/provider/import.go @@ -27,7 +27,16 @@ func (s *RawProviderServer) ImportResourceState(ctx context.Context, req *tfprot cp := req.ClientCapabilities if cp != nil && cp.DeferralAllowed && s.clientConfigUnknown { + v := tftypes.NewValue(tftypes.DynamicPseudoType, tftypes.UnknownValue) + dv, err := tfprotov5.NewDynamicValue(v.Type(), v) + if err != nil { + return resp, err + } // if client support it, request deferral when client configuration not fully known + resp.ImportedResources = append(resp.ImportedResources, &tfprotov5.ImportedResource{ + TypeName: req.TypeName, + State: &dv, + }) resp.Deferred = &tfprotov5.Deferred{ Reason: tfprotov5.DeferredReasonProviderConfigUnknown, } diff --git a/manifest/provider/plan.go b/manifest/provider/plan.go index 0d8cb82fe7..b025e36298 100644 --- a/manifest/provider/plan.go +++ b/manifest/provider/plan.go @@ -130,10 +130,49 @@ func isImportedFlagFromPrivate(p []byte) (f bool, d []*tfprotov5.Diagnostic) { func (s *RawProviderServer) PlanResourceChange(ctx context.Context, req *tfprotov5.PlanResourceChangeRequest) (*tfprotov5.PlanResourceChangeResponse, error) { resp := &tfprotov5.PlanResourceChangeResponse{} + rt, err := GetResourceType(req.TypeName) + if err != nil { + resp.Diagnostics = append(resp.Diagnostics, &tfprotov5.Diagnostic{ + Severity: tfprotov5.DiagnosticSeverityError, + Summary: "Failed to determine planned resource type", + Detail: err.Error(), + }) + return resp, nil + } + // Decode proposed resource state + proposedState, err := req.ProposedNewState.Unmarshal(rt) + if err != nil { + resp.Diagnostics = append(resp.Diagnostics, &tfprotov5.Diagnostic{ + Severity: tfprotov5.DiagnosticSeverityError, + Summary: "Failed to unmarshal planned resource state", + Detail: err.Error(), + }) + return resp, nil + } + s.logger.Trace("[PlanResourceChange]", "[ProposedState]", dump(proposedState)) + + proposedVal := make(map[string]tftypes.Value) + err = proposedState.As(&proposedVal) + if err != nil { + resp.Diagnostics = append(resp.Diagnostics, &tfprotov5.Diagnostic{ + Severity: tfprotov5.DiagnosticSeverityError, + Summary: "Failed to extract planned resource state from tftypes.Value", + Detail: err.Error(), + }) + return resp, nil + } + canDeferr := req.ClientCapabilities != nil && req.ClientCapabilities.DeferralAllowed if canDeferr && s.clientConfigUnknown { // if client supports it, request deferral when client configuration not fully known + proposedVal["object"] = tftypes.NewValue(tftypes.DynamicPseudoType, tftypes.UnknownValue) + newPlannedState := tftypes.NewValue(proposedState.Type(), proposedVal) + ps, err := tfprotov5.NewDynamicValue(newPlannedState.Type(), newPlannedState) + if err != nil { + return resp, err + } + resp.PlannedState = &ps resp.Deferred = &tfprotov5.Deferred{ Reason: tfprotov5.DeferredReasonProviderConfigUnknown, } @@ -164,38 +203,6 @@ func (s *RawProviderServer) PlanResourceChange(ctx context.Context, req *tfproto return resp, nil } - rt, err := GetResourceType(req.TypeName) - if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov5.Diagnostic{ - Severity: tfprotov5.DiagnosticSeverityError, - Summary: "Failed to determine planned resource type", - Detail: err.Error(), - }) - return resp, nil - } - // Decode proposed resource state - proposedState, err := req.ProposedNewState.Unmarshal(rt) - if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov5.Diagnostic{ - Severity: tfprotov5.DiagnosticSeverityError, - Summary: "Failed to unmarshal planned resource state", - Detail: err.Error(), - }) - return resp, nil - } - s.logger.Trace("[PlanResourceChange]", "[ProposedState]", dump(proposedState)) - - proposedVal := make(map[string]tftypes.Value) - err = proposedState.As(&proposedVal) - if err != nil { - resp.Diagnostics = append(resp.Diagnostics, &tfprotov5.Diagnostic{ - Severity: tfprotov5.DiagnosticSeverityError, - Summary: "Failed to extract planned resource state from tftypes.Value", - Detail: err.Error(), - }) - return resp, nil - } - computedFields := make(map[string]*tftypes.AttributePath) var atp *tftypes.AttributePath cfVal, ok := proposedVal["computed_fields"] diff --git a/manifest/provider/read.go b/manifest/provider/read.go index 41143726fc..632b6681db 100644 --- a/manifest/provider/read.go +++ b/manifest/provider/read.go @@ -23,6 +23,7 @@ func (s *RawProviderServer) ReadResource(ctx context.Context, req *tfprotov5.Rea cp := req.ClientCapabilities if cp != nil && cp.DeferralAllowed && s.clientConfigUnknown { // if client support it, request deferral when client configuration not fully known + resp.NewState = req.CurrentState resp.Deferred = &tfprotov5.Deferred{ Reason: tfprotov5.DeferredReasonProviderConfigUnknown, }