Merge pull request #37986 from hashicorp/jbardin/planned-private-for-random
Some checks failed
build / Determine intended Terraform version (push) Has been cancelled
build / Determine Go toolchain version (push) Has been cancelled
Quick Checks / Unit Tests (push) Has been cancelled
Quick Checks / Race Tests (push) Has been cancelled
Quick Checks / End-to-end Tests (push) Has been cancelled
Quick Checks / Code Consistency Checks (push) Has been cancelled
build / Generate release metadata (push) Has been cancelled
build / Build for freebsd_386 (push) Has been cancelled
build / Build for linux_386 (push) Has been cancelled
build / Build for openbsd_386 (push) Has been cancelled
build / Build for windows_386 (push) Has been cancelled
build / Build for darwin_amd64 (push) Has been cancelled
build / Build for freebsd_amd64 (push) Has been cancelled
build / Build for linux_amd64 (push) Has been cancelled
build / Build for openbsd_amd64 (push) Has been cancelled
build / Build for solaris_amd64 (push) Has been cancelled
build / Build for windows_amd64 (push) Has been cancelled
build / Build for freebsd_arm (push) Has been cancelled
build / Build for linux_arm (push) Has been cancelled
build / Build for darwin_arm64 (push) Has been cancelled
build / Build for linux_arm64 (push) Has been cancelled
build / Build for windows_arm64 (push) Has been cancelled
build / Build Docker image for linux_386 (push) Has been cancelled
build / Build Docker image for linux_amd64 (push) Has been cancelled
build / Build Docker image for linux_arm (push) Has been cancelled
build / Build Docker image for linux_arm64 (push) Has been cancelled
build / Build e2etest for linux_386 (push) Has been cancelled
build / Build e2etest for windows_386 (push) Has been cancelled
build / Build e2etest for darwin_amd64 (push) Has been cancelled
build / Build e2etest for linux_amd64 (push) Has been cancelled
build / Build e2etest for windows_amd64 (push) Has been cancelled
build / Build e2etest for linux_arm (push) Has been cancelled
build / Build e2etest for darwin_arm64 (push) Has been cancelled
build / Build e2etest for linux_arm64 (push) Has been cancelled
build / Run e2e test for linux_386 (push) Has been cancelled
build / Run e2e test for windows_386 (push) Has been cancelled
build / Run e2e test for darwin_amd64 (push) Has been cancelled
build / Run e2e test for linux_amd64 (push) Has been cancelled
build / Run e2e test for windows_amd64 (push) Has been cancelled
build / Run e2e test for linux_arm (push) Has been cancelled
build / Run e2e test for linux_arm64 (push) Has been cancelled
build / Run terraform-exec test for linux amd64 (push) Has been cancelled

Resource pre-planned private data
This commit is contained in:
James Bardin 2026-03-26 11:55:26 -04:00 committed by GitHub
commit 73c225dff4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 125 additions and 15 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -7,6 +7,7 @@ import (
"bytes"
"errors"
"fmt"
"math/rand"
"path/filepath"
"sort"
"strings"
@ -4892,3 +4893,63 @@ func TestContext2Apply_outputWithTypeContraint(t *testing.T) {
}
}
}
func TestContext2Apply_storedPrivatePlanData(t *testing.T) {
m := testModuleInline(t, map[string]string{
"main.tf": `
resource "test_resource" "foo" {
}
resource "test_resource" "bar" {
value = test_resource.foo.computed
}
`,
})
p := testProvider("test")
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&providerSchema{
ResourceTypes: map[string]*configschema.Block{
"test_resource": {
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Computed: true},
"value": {Type: cty.String, Optional: true},
"computed": {Type: cty.String, Computed: true},
},
},
},
})
// make sure we can correctly re-plan a value which was stored in the
// PlannedPrivate data from our initial plan
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
planned := req.ProposedNewState.AsValueMap()
if req.PlannedPrivate != nil {
// fetch the originally planned random string
planned["computed"] = cty.StringVal(string(req.PlannedPrivate))
} else {
// this is our first plan, so generate a new computed value
s := fmt.Sprintf("%d", rand.Int())
planned["computed"] = cty.StringVal(s)
resp.PlannedPrivate = []byte(s)
}
planned["id"] = cty.UnknownVal(cty.String)
resp.PlannedState = cty.ObjectVal(planned)
return resp
}
ctx := testContext2(t, &ContextOpts{
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
},
})
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
tfdiags.AssertNoErrors(t, diags)
// we don't need to try and determine what the correct random value was, if
// the planing was incorrect apply would fail with "Provider produced
// inconsistent final plan"
_, diags = ctx.Apply(plan, m, nil)
tfdiags.AssertNoErrors(t, diags)
}

View file

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

View file

@ -660,6 +660,7 @@ func (ctx *BuiltinEvalContext) ClientCapabilities() providers.ClientCapabilities
return providers.ClientCapabilities{
DeferralAllowed: ctx.Deferrals().DeferralAllowed(),
WriteOnlyAttributesAllowed: true,
StorePlannedPrivate: true,
}
}

View file

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

View file

@ -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.
//
@ -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"`
@ -4998,6 +5009,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
}
@ -5088,6 +5100,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"`
@ -8220,10 +8239,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" +
@ -8361,8 +8381,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" +
@ -8372,7 +8392,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" +

View file

@ -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.
//