diff --git a/.changes/v1.15/NEW FEATURES-20251217-113349.yaml b/.changes/v1.15/NEW FEATURES-20251217-113349.yaml new file mode 100644 index 000000000000..5799b36a15e8 --- /dev/null +++ b/.changes/v1.15/NEW FEATURES-20251217-113349.yaml @@ -0,0 +1,5 @@ +kind: NEW FEATURES +body: Store PlannedPrivate data for providers +time: 2025-12-17T11:33:49.911997-05:00 +custom: + Issue: "37986" diff --git a/docs/plugin-protocol/tfplugin6.proto b/docs/plugin-protocol/tfplugin6.proto index 66d7563b2efb..e68c3b5c98b1 100644 --- a/docs/plugin-protocol/tfplugin6.proto +++ b/docs/plugin-protocol/tfplugin6.proto @@ -1,9 +1,9 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -// Terraform Plugin RPC protocol version 6.10 +// Terraform Plugin RPC protocol version 6.11 // -// This file defines version 6.10 of the RPC protocol. To implement a plugin +// This file defines version 6.11 of the RPC protocol. To implement a plugin // against this protocol, copy this definition into your own codebase and // use protoc to generate stubs for your target language. // @@ -310,6 +310,11 @@ message ClientCapabilities { // The write_only_attributes_allowed capability signals that the client // is able to handle write_only attributes for managed resources. bool write_only_attributes_allowed = 2; + + // store_planned_private indicates that the client will store the private data + // returned with an initial plan, and send it back to the provider as + // PlannedPrivate data in a subsequent plan request. + bool store_planned_private = 3; } // Deferred is a message that indicates that change is deferred for a reason. @@ -641,6 +646,7 @@ message PlanResourceChange { DynamicValue provider_meta = 6; ClientCapabilities client_capabilities = 7; ResourceIdentityData prior_identity = 8; + bytes planned_private = 9; } message Response { diff --git a/internal/providers/provider.go b/internal/providers/provider.go index 793e2685786d..4662aaa10f25 100644 --- a/internal/providers/provider.go +++ b/internal/providers/provider.go @@ -298,6 +298,11 @@ type ClientCapabilities struct { // The write_only_attributes_allowed capability signals that the client // is able to handle write_only attributes for managed resources. WriteOnlyAttributesAllowed bool + + // StorePlannedPrivate indicates that the client is will store private data + // returned from PlanResourceChange, and return it with the final + // PlanResourceChange call. + StorePlannedPrivate bool } type ValidateProviderConfigRequest struct { @@ -547,6 +552,11 @@ type PlanResourceChangeRequest struct { // provider during the last apply. PriorPrivate []byte + // PlannedPrivate is the private data stored from the the last plan. + // PlannedPrivate will only be supplied in the plan immediately preceding an + // ApplyResourceChange call. + PlannedPrivate []byte + // ProviderMeta is the configuration for the provider_meta block for the // module and provider this resource belongs to. Its use is defined by // each provider, and it should not be used without coordination with diff --git a/internal/terraform/context_plan_test.go b/internal/terraform/context_plan_test.go index 8dc83b1b5675..6e95d6f5727c 100644 --- a/internal/terraform/context_plan_test.go +++ b/internal/terraform/context_plan_test.go @@ -1748,6 +1748,7 @@ func TestContext2Plan_blockNestingGroup(t *testing.T) { ClientCapabilities: providers.ClientCapabilities{ DeferralAllowed: false, WriteOnlyAttributesAllowed: true, + StorePlannedPrivate: true, }, } if !cmp.Equal(got, want, valueTrans) { diff --git a/internal/terraform/eval_context_builtin.go b/internal/terraform/eval_context_builtin.go index 2cfeb216e1f4..663415d23d30 100644 --- a/internal/terraform/eval_context_builtin.go +++ b/internal/terraform/eval_context_builtin.go @@ -637,6 +637,7 @@ func (ctx *BuiltinEvalContext) ClientCapabilities() providers.ClientCapabilities return providers.ClientCapabilities{ DeferralAllowed: ctx.Deferrals().DeferralAllowed(), WriteOnlyAttributesAllowed: true, + StorePlannedPrivate: true, } } diff --git a/internal/terraform/node_resource_abstract_instance.go b/internal/terraform/node_resource_abstract_instance.go index c5569b5491df..2f9860e87938 100644 --- a/internal/terraform/node_resource_abstract_instance.go +++ b/internal/terraform/node_resource_abstract_instance.go @@ -832,10 +832,12 @@ func (n *NodeAbstractResourceInstance) plan( if n.preDestroyRefresh { checkRuleSeverity = tfdiags.Warning } - + var plannedPrivate []byte if plannedChange != nil { // If we already planned the action, we stick to that plan createBeforeDestroy = plannedChange.Action == plans.CreateThenDelete + + plannedPrivate = plannedChange.Private } // Evaluate the configuration @@ -985,6 +987,7 @@ func (n *NodeAbstractResourceInstance) plan( ProviderMeta: metaConfigVal, ClientCapabilities: ctx.ClientCapabilities(), PriorIdentity: priorIdentity, + PlannedPrivate: plannedPrivate, }) // If we don't support deferrals, but the provider reports a deferral and does not // emit any error level diagnostics, we should emit an error. @@ -1003,7 +1006,7 @@ func (n *NodeAbstractResourceInstance) plan( } plannedNewVal := resp.PlannedState - plannedPrivate := resp.PlannedPrivate + plannedPrivate = resp.PlannedPrivate plannedIdentity := resp.PlannedIdentity // These checks are only relevant if the provider is not deferring the diff --git a/internal/tfplugin6/tfplugin6.pb.go b/internal/tfplugin6/tfplugin6.pb.go index bb76eff6f14d..da9c27e1a3b2 100644 --- a/internal/tfplugin6/tfplugin6.pb.go +++ b/internal/tfplugin6/tfplugin6.pb.go @@ -1,9 +1,9 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -// Terraform Plugin RPC protocol version 6.10 +// Terraform Plugin RPC protocol version 6.11 // -// This file defines version 6.10 of the RPC protocol. To implement a plugin +// This file defines version 6.11 of the RPC protocol. To implement a plugin // against this protocol, copy this definition into your own codebase and // use protoc to generate stubs for your target language. // @@ -1040,8 +1040,12 @@ type ClientCapabilities struct { // The write_only_attributes_allowed capability signals that the client // is able to handle write_only attributes for managed resources. WriteOnlyAttributesAllowed bool `protobuf:"varint,2,opt,name=write_only_attributes_allowed,json=writeOnlyAttributesAllowed,proto3" json:"write_only_attributes_allowed,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // store_planned_private indicates that the client will store the private data + // returned with an initial plan, and send it back to the provider as + // PlannedPrivate data in a subsequent plan request. + StorePlannedPrivate bool `protobuf:"varint,3,opt,name=store_planned_private,json=storePlannedPrivate,proto3" json:"store_planned_private,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ClientCapabilities) Reset() { @@ -1088,6 +1092,13 @@ func (x *ClientCapabilities) GetWriteOnlyAttributesAllowed() bool { return false } +func (x *ClientCapabilities) GetStorePlannedPrivate() bool { + if x != nil { + return x.StorePlannedPrivate + } + return false +} + // Deferred is a message that indicates that change is deferred for a reason. type Deferred struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -4978,6 +4989,7 @@ type PlanResourceChange_Request struct { ProviderMeta *DynamicValue `protobuf:"bytes,6,opt,name=provider_meta,json=providerMeta,proto3" json:"provider_meta,omitempty"` ClientCapabilities *ClientCapabilities `protobuf:"bytes,7,opt,name=client_capabilities,json=clientCapabilities,proto3" json:"client_capabilities,omitempty"` PriorIdentity *ResourceIdentityData `protobuf:"bytes,8,opt,name=prior_identity,json=priorIdentity,proto3" json:"prior_identity,omitempty"` + PlannedPrivate []byte `protobuf:"bytes,9,opt,name=planned_private,json=plannedPrivate,proto3" json:"planned_private,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -5068,6 +5080,13 @@ func (x *PlanResourceChange_Request) GetPriorIdentity() *ResourceIdentityData { return nil } +func (x *PlanResourceChange_Request) GetPlannedPrivate() []byte { + if x != nil { + return x.PlannedPrivate + } + return nil +} + type PlanResourceChange_Response struct { state protoimpl.MessageState `protogen:"open.v1"` PlannedState *DynamicValue `protobuf:"bytes,1,opt,name=planned_state,json=plannedState,proto3" json:"planned_state,omitempty"` @@ -8197,10 +8216,11 @@ const file_tfplugin6_proto_rawDesc = "" + "\fplan_destroy\x18\x01 \x01(\bR\vplanDestroy\x12?\n" + "\x1cget_provider_schema_optional\x18\x02 \x01(\bR\x19getProviderSchemaOptional\x12.\n" + "\x13move_resource_state\x18\x03 \x01(\bR\x11moveResourceState\x128\n" + - "\x18generate_resource_config\x18\x04 \x01(\bR\x16generateResourceConfig\"\x82\x01\n" + + "\x18generate_resource_config\x18\x04 \x01(\bR\x16generateResourceConfig\"\xb6\x01\n" + "\x12ClientCapabilities\x12)\n" + "\x10deferral_allowed\x18\x01 \x01(\bR\x0fdeferralAllowed\x12A\n" + - "\x1dwrite_only_attributes_allowed\x18\x02 \x01(\bR\x1awriteOnlyAttributesAllowed\"\xa2\x01\n" + + "\x1dwrite_only_attributes_allowed\x18\x02 \x01(\bR\x1awriteOnlyAttributesAllowed\x122\n" + + "\x15store_planned_private\x18\x03 \x01(\bR\x13storePlannedPrivate\"\xa2\x01\n" + "\bDeferred\x122\n" + "\x06reason\x18\x01 \x01(\x0e2\x1a.tfplugin6.Deferred.ReasonR\x06reason\"b\n" + "\x06Reason\x12\v\n" + @@ -8338,8 +8358,8 @@ const file_tfplugin6_proto_rawDesc = "" + "\vdiagnostics\x18\x02 \x03(\v2\x15.tfplugin6.DiagnosticR\vdiagnostics\x12\x18\n" + "\aprivate\x18\x03 \x01(\fR\aprivate\x12/\n" + "\bdeferred\x18\x04 \x01(\v2\x13.tfplugin6.DeferredR\bdeferred\x12B\n" + - "\fnew_identity\x18\x05 \x01(\v2\x1f.tfplugin6.ResourceIdentityDataR\vnewIdentity\"\x87\a\n" + - "\x12PlanResourceChange\x1a\xd3\x03\n" + + "\fnew_identity\x18\x05 \x01(\v2\x1f.tfplugin6.ResourceIdentityDataR\vnewIdentity\"\xb0\a\n" + + "\x12PlanResourceChange\x1a\xfc\x03\n" + "\aRequest\x12\x1b\n" + "\ttype_name\x18\x01 \x01(\tR\btypeName\x128\n" + "\vprior_state\x18\x02 \x01(\v2\x17.tfplugin6.DynamicValueR\n" + @@ -8349,7 +8369,8 @@ const file_tfplugin6_proto_rawDesc = "" + "\rprior_private\x18\x05 \x01(\fR\fpriorPrivate\x12<\n" + "\rprovider_meta\x18\x06 \x01(\v2\x17.tfplugin6.DynamicValueR\fproviderMeta\x12N\n" + "\x13client_capabilities\x18\a \x01(\v2\x1d.tfplugin6.ClientCapabilitiesR\x12clientCapabilities\x12F\n" + - "\x0eprior_identity\x18\b \x01(\v2\x1f.tfplugin6.ResourceIdentityDataR\rpriorIdentity\x1a\x9a\x03\n" + + "\x0eprior_identity\x18\b \x01(\v2\x1f.tfplugin6.ResourceIdentityDataR\rpriorIdentity\x12'\n" + + "\x0fplanned_private\x18\t \x01(\fR\x0eplannedPrivate\x1a\x9a\x03\n" + "\bResponse\x12<\n" + "\rplanned_state\x18\x01 \x01(\v2\x17.tfplugin6.DynamicValueR\fplannedState\x12C\n" + "\x10requires_replace\x18\x02 \x03(\v2\x18.tfplugin6.AttributePathR\x0frequiresReplace\x12'\n" + diff --git a/internal/tfplugin6/tfplugin6_grpc.pb.go b/internal/tfplugin6/tfplugin6_grpc.pb.go index 009da41f2617..9eaee8e4b671 100644 --- a/internal/tfplugin6/tfplugin6_grpc.pb.go +++ b/internal/tfplugin6/tfplugin6_grpc.pb.go @@ -1,9 +1,9 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -// Terraform Plugin RPC protocol version 6.10 +// Terraform Plugin RPC protocol version 6.11 // -// This file defines version 6.10 of the RPC protocol. To implement a plugin +// This file defines version 6.11 of the RPC protocol. To implement a plugin // against this protocol, copy this definition into your own codebase and // use protoc to generate stubs for your target language. //