diff --git a/docs/plugin-protocol/tfplugin6.proto b/docs/plugin-protocol/tfplugin6.proto index 1b2b0ed08c..66b471697b 100644 --- a/docs/plugin-protocol/tfplugin6.proto +++ b/docs/plugin-protocol/tfplugin6.proto @@ -1,9 +1,9 @@ // Copyright IBM Corp. 2014, 2026 // 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. // @@ -312,6 +312,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. @@ -643,6 +648,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/plugin6/grpc_provider.go b/internal/plugin6/grpc_provider.go index 9da603a247..2b2c2f394c 100644 --- a/internal/plugin6/grpc_provider.go +++ b/internal/plugin6/grpc_provider.go @@ -673,6 +673,7 @@ func (p *GRPCProvider) PlanResourceChange(r providers.PlanResourceChangeRequest) ProposedNewState: &proto6.DynamicValue{Msgpack: propMP}, PriorPrivate: r.PriorPrivate, ClientCapabilities: clientCapabilitiesToProto(r.ClientCapabilities), + PlannedPrivate: r.PlannedPrivate, } if metaSchema.Body != nil { @@ -2071,6 +2072,7 @@ func clientCapabilitiesToProto(c providers.ClientCapabilities) *proto6.ClientCap return &proto6.ClientCapabilities{ DeferralAllowed: c.DeferralAllowed, WriteOnlyAttributesAllowed: c.WriteOnlyAttributesAllowed, + StorePlannedPrivate: c.StorePlannedPrivate, } } diff --git a/internal/providers/provider.go b/internal/providers/provider.go index e52de56289..011294ebfc 100644 --- a/internal/providers/provider.go +++ b/internal/providers/provider.go @@ -307,6 +307,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 { @@ -556,6 +561,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 0e5e73682e..22f715e5ce 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 30f174680f..09590c253b 100644 --- a/internal/terraform/eval_context_builtin.go +++ b/internal/terraform/eval_context_builtin.go @@ -660,6 +660,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 187f8dfb59..a2bccb0bbe 100644 --- a/internal/terraform/node_resource_abstract_instance.go +++ b/internal/terraform/node_resource_abstract_instance.go @@ -835,10 +835,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 @@ -991,6 +993,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. @@ -1012,7 +1015,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