From 74fd3bcebc75c4fcca072e8b8dac22881ff2a5e2 Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Wed, 13 Aug 2025 15:50:16 +0200 Subject: [PATCH 01/60] Implement ReadStateBytes + WriteStateBytes --- docs/plugin-protocol/tfplugin6.proto | 36 + .../builtin/providers/terraform/provider.go | 12 + internal/grpcwrap/provider6.go | 8 + internal/plugin/grpc_provider.go | 8 + internal/plugin6/grpc_provider.go | 135 ++ internal/plugin6/mock_proto/mock.go | 40 + internal/provider-simple-v6/provider.go | 8 + internal/provider-simple/provider.go | 8 + internal/providers/mock.go | 8 + internal/providers/provider.go | 33 + internal/providers/testing/provider_mock.go | 36 + internal/refactoring/mock_provider.go | 8 + .../internal/stackeval/stubs/errored.go | 28 + .../internal/stackeval/stubs/offline.go | 28 + .../internal/stackeval/stubs/unknown.go | 28 + internal/tfplugin6/tfplugin6.pb.go | 1558 +++++++++++------ 16 files changed, 1473 insertions(+), 509 deletions(-) diff --git a/docs/plugin-protocol/tfplugin6.proto b/docs/plugin-protocol/tfplugin6.proto index 92a565002269..4d3b8c365900 100644 --- a/docs/plugin-protocol/tfplugin6.proto +++ b/docs/plugin-protocol/tfplugin6.proto @@ -425,6 +425,11 @@ service Provider { // ConfigureStateStore configures the state store, such as S3 connection in the context of already configured provider rpc ConfigureStateStore(ConfigureStateStore.Request) returns (ConfigureStateStore.Response); + // ReadStateBytes streams byte chunks of a given state file from a state store + rpc ReadStateBytes(ReadStateBytes.Request) returns (stream ReadStateBytes.ResponseChunk); + // WriteStateBytes streams byte chunks of a given state file into a state store + rpc WriteStateBytes(stream WriteStateBytes.RequestChunk) returns (WriteStateBytes.Response); + // GetStates returns a list of all states (i.e. CE workspaces) managed by a given state store rpc GetStates(GetStates.Request) returns (GetStates.Response); // DeleteState instructs a given state store to delete a specific state (i.e. a CE workspace) @@ -939,6 +944,37 @@ message ConfigureStateStore { } } +message ReadStateBytes { + message Request { + string type_name = 1; + string state_id = 2; + } + message ResponseChunk { + bytes bytes = 1; + int64 total_length = 2; + StateRange range = 3; + repeated Diagnostic diagnostics = 4; + } +} + +message WriteStateBytes { + message RequestChunk { + string type_name = 1; + bytes bytes = 2; + string state_id = 3; + int64 total_length = 4; + StateRange range = 5; + } + message Response { + repeated Diagnostic diagnostics = 1; + } +} + +message StateRange { + int64 start = 1; + int64 end = 2; +} + message GetStates { message Request { string type_name = 1; diff --git a/internal/builtin/providers/terraform/provider.go b/internal/builtin/providers/terraform/provider.go index b96f1df3ad01..c4330cda51e9 100644 --- a/internal/builtin/providers/terraform/provider.go +++ b/internal/builtin/providers/terraform/provider.go @@ -291,6 +291,18 @@ func (p *Provider) ConfigureStateStore(req providers.ConfigureStateStoreRequest) return resp } +func (p *Provider) ReadStateBytes(req providers.ReadStateBytesRequest) providers.ReadStateBytesResponse { + var resp providers.ReadStateBytesResponse + resp.Diagnostics.Append(fmt.Errorf("unsupported state store type %q", req.TypeName)) + return resp +} + +func (p *Provider) WriteStateBytes(req providers.WriteStateBytesRequest) providers.WriteStateBytesResponse { + var resp providers.WriteStateBytesResponse + resp.Diagnostics.Append(fmt.Errorf("unsupported state store type %q", req.TypeName)) + return resp +} + func (p *Provider) GetStates(req providers.GetStatesRequest) providers.GetStatesResponse { var resp providers.GetStatesResponse resp.Diagnostics.Append(fmt.Errorf("unsupported state store type %q", req.TypeName)) diff --git a/internal/grpcwrap/provider6.go b/internal/grpcwrap/provider6.go index 64c3a62ce822..70d52124f85d 100644 --- a/internal/grpcwrap/provider6.go +++ b/internal/grpcwrap/provider6.go @@ -918,6 +918,14 @@ func (p *provider6) ConfigureStateStore(ctx context.Context, req *tfplugin6.Conf panic("not implemented") } +func (p *provider6) ReadStateBytes(req *tfplugin6.ReadStateBytes_Request, srv tfplugin6.Provider_ReadStateBytesServer) error { + panic("not implemented") +} + +func (p *provider6) WriteStateBytes(srv tfplugin6.Provider_WriteStateBytesServer) error { + panic("not implemented") +} + func (p *provider6) GetStates(ctx context.Context, req *tfplugin6.GetStates_Request) (*tfplugin6.GetStates_Response, error) { panic("not implemented") } diff --git a/internal/plugin/grpc_provider.go b/internal/plugin/grpc_provider.go index 323897406cf9..541a48d0c356 100644 --- a/internal/plugin/grpc_provider.go +++ b/internal/plugin/grpc_provider.go @@ -1418,6 +1418,14 @@ func (p *GRPCProvider) ConfigureStateStore(r providers.ConfigureStateStoreReques panic("not implemented") } +func (p *GRPCProvider) ReadStateBytes(r providers.ReadStateBytesRequest) providers.ReadStateBytesResponse { + panic("not implemented") +} + +func (p *GRPCProvider) WriteStateBytes(r providers.WriteStateBytesRequest) providers.WriteStateBytesResponse { + panic("not implemented") +} + func (p *GRPCProvider) GetStates(r providers.GetStatesRequest) providers.GetStatesResponse { panic("not implemented") } diff --git a/internal/plugin6/grpc_provider.go b/internal/plugin6/grpc_provider.go index e7c46797b465..3d8a565a4eba 100644 --- a/internal/plugin6/grpc_provider.go +++ b/internal/plugin6/grpc_provider.go @@ -4,6 +4,7 @@ package plugin6 import ( + "bytes" "context" "errors" "fmt" @@ -1478,6 +1479,140 @@ func (p *GRPCProvider) ConfigureStateStore(r providers.ConfigureStateStoreReques return resp } +func (p *GRPCProvider) ReadStateBytes(r providers.ReadStateBytesRequest) (resp providers.ReadStateBytesResponse) { + logger.Trace("GRPCProvider.v6: ReadStateBytes") + + schema := p.GetProviderSchema() + if schema.Diagnostics.HasErrors() { + resp.Diagnostics = schema.Diagnostics + return resp + } + + if _, ok := schema.StateStores[r.TypeName]; !ok { + resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unknown state store type %q", r.TypeName)) + return resp + } + + protoReq := &proto6.ReadStateBytes_Request{ + TypeName: r.TypeName, + StateId: r.StateId, + } + + // Start the streaming RPC with a context. The context will be cancelled + // when this function returns, which will stop the stream if it is still + // running. + ctx, cancel := context.WithCancel(p.ctx) + defer cancel() + + client, err := p.client.ReadStateBytes(ctx, protoReq) + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err)) + return resp + } + + var buf *bytes.Buffer + var expectedTotalLength int + for { + chunk, err := client.Recv() + if err == io.EOF { + // End of stream, we're done + break + } + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(err) + break + } + resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(chunk.Diagnostics)) + if resp.Diagnostics.HasErrors() { + // If we have errors, we stop processing and return early + break + } + + if expectedTotalLength == 0 { + expectedTotalLength = int(chunk.TotalLength) + } + logger.Trace("GRPCProvider.v6: ReadStateBytes: received chunk for range", chunk.Range) + + n, err := buf.Write(chunk.Bytes) + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(err) + break + } + logger.Trace("GRPCProvider.v6: ReadStateBytes: read bytes of a chunk", n) + } + + logger.Trace("GRPCProvider.v6: ReadStateBytes: received all chunks", buf.Len()) + if buf.Len() != expectedTotalLength { + err = fmt.Errorf("expected state file of total %d bytes, received %d bytes", + expectedTotalLength, buf.Len()) + resp.Diagnostics = resp.Diagnostics.Append(err) + return resp + } + resp.Bytes = buf.Bytes() + + return resp +} + +func (p *GRPCProvider) WriteStateBytes(r providers.WriteStateBytesRequest) (resp providers.WriteStateBytesResponse) { + logger.Trace("GRPCProvider.v6: WriteStateBytes") + + schema := p.GetProviderSchema() + if schema.Diagnostics.HasErrors() { + resp.Diagnostics = schema.Diagnostics + return resp + } + + if _, ok := schema.StateStores[r.TypeName]; !ok { + resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unknown state store type %q", r.TypeName)) + return resp + } + + // Start the streaming RPC with a context. The context will be cancelled + // when this function returns, which will stop the stream if it is still + // running. + ctx, cancel := context.WithCancel(p.ctx) + defer cancel() + + // TODO: Configurable chunk size + chunkSize := 4 * 1_000_000 // 4MB + + if len(r.Bytes) < chunkSize { + protoReq := &proto6.WriteStateBytes_RequestChunk{ + TypeName: r.TypeName, + StateId: r.StateId, + Bytes: r.Bytes, + TotalLength: int64(len(r.Bytes)), + Range: &proto6.StateRange{ + Start: 0, + End: int64(len(r.Bytes)), + }, + } + client, err := p.client.WriteStateBytes(ctx) + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err)) + return resp + } + err = client.Send(protoReq) + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err)) + return resp + } + protoResp, err := client.CloseAndRecv() + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err)) + return resp + } + resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics)) + if resp.Diagnostics.HasErrors() { + return resp + } + } + + // TODO: implement chunking for state files larger than chunkSize + + return resp +} + func (p *GRPCProvider) GetStates(r providers.GetStatesRequest) (resp providers.GetStatesResponse) { logger.Trace("GRPCProvider.v6: GetStates") diff --git a/internal/plugin6/mock_proto/mock.go b/internal/plugin6/mock_proto/mock.go index efb8c225acd2..12b0cc79a9ee 100644 --- a/internal/plugin6/mock_proto/mock.go +++ b/internal/plugin6/mock_proto/mock.go @@ -442,6 +442,26 @@ func (mr *MockProviderClientMockRecorder) ReadResource(arg0, arg1 any, arg2 ...a return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadResource", reflect.TypeOf((*MockProviderClient)(nil).ReadResource), varargs...) } +// ReadStateBytes mocks base method. +func (m *MockProviderClient) ReadStateBytes(arg0 context.Context, arg1 *tfplugin6.ReadStateBytes_Request, arg2 ...grpc.CallOption) (tfplugin6.Provider_ReadStateBytesClient, error) { + m.ctrl.T.Helper() + varargs := []any{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "ReadStateBytes", varargs...) + ret0, _ := ret[0].(tfplugin6.Provider_ReadStateBytesClient) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ReadStateBytes indicates an expected call of ReadStateBytes. +func (mr *MockProviderClientMockRecorder) ReadStateBytes(arg0, arg1 any, arg2 ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadStateBytes", reflect.TypeOf((*MockProviderClient)(nil).ReadStateBytes), varargs...) +} + // RenewEphemeralResource mocks base method. func (m *MockProviderClient) RenewEphemeralResource(arg0 context.Context, arg1 *tfplugin6.RenewEphemeralResource_Request, arg2 ...grpc.CallOption) (*tfplugin6.RenewEphemeralResource_Response, error) { m.ctrl.T.Helper() @@ -662,6 +682,26 @@ func (mr *MockProviderClientMockRecorder) ValidateStateStoreConfig(arg0, arg1 an return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateStateStoreConfig", reflect.TypeOf((*MockProviderClient)(nil).ValidateStateStoreConfig), varargs...) } +// WriteStateBytes mocks base method. +func (m *MockProviderClient) WriteStateBytes(arg0 context.Context, arg1 ...grpc.CallOption) (tfplugin6.Provider_WriteStateBytesClient, error) { + m.ctrl.T.Helper() + varargs := []any{arg0} + for _, a := range arg1 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "WriteStateBytes", varargs...) + ret0, _ := ret[0].(tfplugin6.Provider_WriteStateBytesClient) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WriteStateBytes indicates an expected call of WriteStateBytes. +func (mr *MockProviderClientMockRecorder) WriteStateBytes(arg0 any, arg1 ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{arg0}, arg1...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteStateBytes", reflect.TypeOf((*MockProviderClient)(nil).WriteStateBytes), varargs...) +} + // MockProvider_InvokeActionClient is a mock of Provider_InvokeActionClient interface. type MockProvider_InvokeActionClient struct { ctrl *gomock.Controller diff --git a/internal/provider-simple-v6/provider.go b/internal/provider-simple-v6/provider.go index 10b0e5139228..639baee86e88 100644 --- a/internal/provider-simple-v6/provider.go +++ b/internal/provider-simple-v6/provider.go @@ -311,6 +311,14 @@ func (s simple) ConfigureStateStore(req providers.ConfigureStateStoreRequest) pr panic("not implemented") } +func (s simple) ReadStateBytes(req providers.ReadStateBytesRequest) providers.ReadStateBytesResponse { + panic("not implemented") +} + +func (s simple) WriteStateBytes(req providers.WriteStateBytesRequest) providers.WriteStateBytesResponse { + panic("not implemented") +} + func (s simple) GetStates(req providers.GetStatesRequest) providers.GetStatesResponse { panic("not implemented") } diff --git a/internal/provider-simple/provider.go b/internal/provider-simple/provider.go index 2cd80fe03bf3..dd55b4408278 100644 --- a/internal/provider-simple/provider.go +++ b/internal/provider-simple/provider.go @@ -271,6 +271,14 @@ func (s simple) ConfigureStateStore(req providers.ConfigureStateStoreRequest) pr panic("not implemented") } +func (s simple) ReadStateBytes(req providers.ReadStateBytesRequest) providers.ReadStateBytesResponse { + panic("not implemented") +} + +func (s simple) WriteStateBytes(req providers.WriteStateBytesRequest) providers.WriteStateBytesResponse { + panic("not implemented") +} + func (s simple) GetStates(req providers.GetStatesRequest) providers.GetStatesResponse { // provider-simple uses protocol version 5, which does not include the RPC that maps to this method panic("not implemented") diff --git a/internal/providers/mock.go b/internal/providers/mock.go index 42829b8a80e9..cba833bf2d0f 100644 --- a/internal/providers/mock.go +++ b/internal/providers/mock.go @@ -432,6 +432,14 @@ func (m *Mock) ConfigureStateStore(req ConfigureStateStoreRequest) ConfigureStat return m.Provider.ConfigureStateStore(req) } +func (m *Mock) ReadStateBytes(req ReadStateBytesRequest) ReadStateBytesResponse { + return m.Provider.ReadStateBytes(req) +} + +func (m *Mock) WriteStateBytes(req WriteStateBytesRequest) WriteStateBytesResponse { + return m.Provider.WriteStateBytes(req) +} + func (m *Mock) GetStates(req GetStatesRequest) GetStatesResponse { return m.Provider.GetStates(req) } diff --git a/internal/providers/provider.go b/internal/providers/provider.go index face2a9c154e..cbb110178b2c 100644 --- a/internal/providers/provider.go +++ b/internal/providers/provider.go @@ -118,6 +118,11 @@ type Interface interface { // ConfigureStateStore configures the state store, such as S3 connection in the context of already configured provider ConfigureStateStore(ConfigureStateStoreRequest) ConfigureStateStoreResponse + // ReadStateBytes streams byte chunks of a given state file from a state store + ReadStateBytes(ReadStateBytesRequest) ReadStateBytesResponse + // WriteStateBytes streams byte chunks of a given state file into a state store + WriteStateBytes(WriteStateBytesRequest) WriteStateBytesResponse + // GetStates returns a list of all states (i.e. CE workspaces) managed by a given state store GetStates(GetStatesRequest) GetStatesResponse // DeleteState instructs a given state store to delete a specific state (i.e. a CE workspace) @@ -850,6 +855,34 @@ type ConfigureStateStoreResponse struct { Diagnostics tfdiags.Diagnostics } +type ReadStateBytesRequest struct { + // TypeName is the name of the state store to read state from + TypeName string + // StateId is the ID of a state file to read + StateId string +} + +type ReadStateBytesResponse struct { + // Bytes represents all received bytes of the given state file + Bytes []byte + // Diagnostics contains any warnings or errors from the method call. + Diagnostics tfdiags.Diagnostics +} + +type WriteStateBytesRequest struct { + // TypeName is the name of the state store to write state to + TypeName string + // Bytes represents all bytes of the given state file to write + Bytes []byte + // StateId is the ID of a state file to write + StateId string +} + +type WriteStateBytesResponse struct { + // Diagnostics contains any warnings or errors from the method call. + Diagnostics tfdiags.Diagnostics +} + type GetStatesRequest struct { // TypeName is the name of the state store to request the list of states from TypeName string diff --git a/internal/providers/testing/provider_mock.go b/internal/providers/testing/provider_mock.go index ca5e4e1b6a6b..c54760ee14d2 100644 --- a/internal/providers/testing/provider_mock.go +++ b/internal/providers/testing/provider_mock.go @@ -141,6 +141,16 @@ type MockProvider struct { ConfigureStateStoreRequest providers.ConfigureStateStoreRequest ConfigureStateStoreFn func(providers.ConfigureStateStoreRequest) providers.ConfigureStateStoreResponse + ReadStateBytesCalled bool + ReadStateBytesRequest providers.ReadStateBytesRequest + ReadStateBytesFn func(providers.ReadStateBytesRequest) providers.ReadStateBytesResponse + ReadStateBytesResponse providers.ReadStateBytesResponse + + WriteStateBytesCalled bool + WriteStateBytesRequest providers.WriteStateBytesRequest + WriteStateBytesFn func(providers.WriteStateBytesRequest) providers.WriteStateBytesResponse + WriteStateBytesResponse providers.WriteStateBytesResponse + GetStatesCalled bool GetStatesResponse *providers.GetStatesResponse GetStatesRequest providers.GetStatesRequest @@ -299,6 +309,32 @@ func (p *MockProvider) ValidateDataResourceConfig(r providers.ValidateDataResour return resp } +func (p *MockProvider) ReadStateBytes(r providers.ReadStateBytesRequest) (resp providers.ReadStateBytesResponse) { + p.Lock() + defer p.Unlock() + p.ReadStateBytesCalled = true + p.ReadStateBytesRequest = r + + if p.ReadStateBytesFn != nil { + return p.ReadStateBytesFn(r) + } + + return p.ReadStateBytesResponse +} + +func (p *MockProvider) WriteStateBytes(r providers.WriteStateBytesRequest) (resp providers.WriteStateBytesResponse) { + p.Lock() + defer p.Unlock() + p.WriteStateBytesCalled = true + p.WriteStateBytesRequest = r + + if p.WriteStateBytesFn != nil { + return p.WriteStateBytesFn(r) + } + + return p.WriteStateBytesResponse +} + func (p *MockProvider) ValidateEphemeralResourceConfig(r providers.ValidateEphemeralResourceConfigRequest) (resp providers.ValidateEphemeralResourceConfigResponse) { defer p.beginWrite()() diff --git a/internal/refactoring/mock_provider.go b/internal/refactoring/mock_provider.go index 31f30935b208..b0dcff50eef9 100644 --- a/internal/refactoring/mock_provider.go +++ b/internal/refactoring/mock_provider.go @@ -130,6 +130,14 @@ func (provider *mockProvider) ConfigureStateStore(req providers.ConfigureStateSt panic("not implemented in mock") } +func (provider *mockProvider) ReadStateBytes(req providers.ReadStateBytesRequest) providers.ReadStateBytesResponse { + panic("not implemented in mock") +} + +func (provider *mockProvider) WriteStateBytes(req providers.WriteStateBytesRequest) providers.WriteStateBytesResponse { + panic("not implemented in mock") +} + func (provider *mockProvider) GetStates(req providers.GetStatesRequest) providers.GetStatesResponse { panic("not implemented in mock") } diff --git a/internal/stacks/stackruntime/internal/stackeval/stubs/errored.go b/internal/stacks/stackruntime/internal/stackeval/stubs/errored.go index 24bc2c4b9f12..e2bd82fa5cac 100644 --- a/internal/stacks/stackruntime/internal/stackeval/stubs/errored.go +++ b/internal/stacks/stackruntime/internal/stackeval/stubs/errored.go @@ -271,6 +271,34 @@ func (p *erroredProvider) ConfigureStateStore(providers.ConfigureStateStoreReque } } +// ReadStateBytes implements providers.Interface. +func (p *erroredProvider) ReadStateBytes(providers.ReadStateBytesRequest) providers.ReadStateBytesResponse { + var diags tfdiags.Diagnostics + diags = diags.Append(tfdiags.AttributeValue( + tfdiags.Error, + "Provider configuration is invalid", + "Cannot read state managed by this state store because its associated provider configuration is invalid.", + nil, // nil attribute path means the overall configuration block + )) + return providers.ReadStateBytesResponse{ + Diagnostics: diags, + } +} + +// WriteStateBytes implements providers.Interface. +func (p *erroredProvider) WriteStateBytes(providers.WriteStateBytesRequest) providers.WriteStateBytesResponse { + var diags tfdiags.Diagnostics + diags = diags.Append(tfdiags.AttributeValue( + tfdiags.Error, + "Provider configuration is invalid", + "Cannot write state managed by this state store because its associated provider configuration is invalid.", + nil, // nil attribute path means the overall configuration block + )) + return providers.WriteStateBytesResponse{ + Diagnostics: diags, + } +} + // GetStates implements providers.Interface. func (p *erroredProvider) GetStates(providers.GetStatesRequest) providers.GetStatesResponse { var diags tfdiags.Diagnostics diff --git a/internal/stacks/stackruntime/internal/stackeval/stubs/offline.go b/internal/stacks/stackruntime/internal/stackeval/stubs/offline.go index 2cf4a07530a0..f54c0755b750 100644 --- a/internal/stacks/stackruntime/internal/stackeval/stubs/offline.go +++ b/internal/stacks/stackruntime/internal/stackeval/stubs/offline.go @@ -288,6 +288,34 @@ func (o *offlineProvider) ConfigureStateStore(providers.ConfigureStateStoreReque } } +// ReadStateBytes implements providers.Interface. +func (o *offlineProvider) ReadStateBytes(providers.ReadStateBytesRequest) providers.ReadStateBytesResponse { + var diags tfdiags.Diagnostics + diags = diags.Append(tfdiags.AttributeValue( + tfdiags.Error, + "Called ReadStateBytes on an unconfigured provider", + "Cannot read from state store because this provider is not configured. This is a bug in Terraform - please report it.", + nil, // nil attribute path means the overall configuration block + )) + return providers.ReadStateBytesResponse{ + Diagnostics: diags, + } +} + +// WriteStateBytes implements providers.Interface. +func (o *offlineProvider) WriteStateBytes(providers.WriteStateBytesRequest) providers.WriteStateBytesResponse { + var diags tfdiags.Diagnostics + diags = diags.Append(tfdiags.AttributeValue( + tfdiags.Error, + "Called WriteStateBytes on an unconfigured provider", + "Cannot write to state store because this provider is not configured. This is a bug in Terraform - please report it.", + nil, // nil attribute path means the overall configuration block + )) + return providers.WriteStateBytesResponse{ + Diagnostics: diags, + } +} + // GetStates implements providers.Interface. func (o *offlineProvider) GetStates(providers.GetStatesRequest) providers.GetStatesResponse { var diags tfdiags.Diagnostics diff --git a/internal/stacks/stackruntime/internal/stackeval/stubs/unknown.go b/internal/stacks/stackruntime/internal/stackeval/stubs/unknown.go index 031575c5b7ad..9a9f77cfaeb9 100644 --- a/internal/stacks/stackruntime/internal/stackeval/stubs/unknown.go +++ b/internal/stacks/stackruntime/internal/stackeval/stubs/unknown.go @@ -336,6 +336,34 @@ func (u *unknownProvider) ConfigureStateStore(providers.ConfigureStateStoreReque } } +// ReadStateBytes implements providers.Interface. +func (u *unknownProvider) ReadStateBytes(providers.ReadStateBytesRequest) providers.ReadStateBytesResponse { + var diags tfdiags.Diagnostics + diags = diags.Append(tfdiags.AttributeValue( + tfdiags.Error, + "Provider configuration is unknown", + "Cannot read from this state store because its associated provider configuration is unknown.", + nil, // nil attribute path means the overall configuration block + )) + return providers.ReadStateBytesResponse{ + Diagnostics: diags, + } +} + +// WriteStateBytes implements providers.Interface. +func (u *unknownProvider) WriteStateBytes(providers.WriteStateBytesRequest) providers.WriteStateBytesResponse { + var diags tfdiags.Diagnostics + diags = diags.Append(tfdiags.AttributeValue( + tfdiags.Error, + "Provider configuration is unknown", + "Cannot write to this state store because its associated provider configuration is unknown.", + nil, // nil attribute path means the overall configuration block + )) + return providers.WriteStateBytesResponse{ + Diagnostics: diags, + } +} + // GetStates implements providers.Interface. func (u *unknownProvider) GetStates(providers.GetStatesRequest) providers.GetStatesResponse { var diags tfdiags.Diagnostics diff --git a/internal/tfplugin6/tfplugin6.pb.go b/internal/tfplugin6/tfplugin6.pb.go index 54c9ac0590e2..555abba49689 100644 --- a/internal/tfplugin6/tfplugin6.pb.go +++ b/internal/tfplugin6/tfplugin6.pb.go @@ -2141,6 +2141,130 @@ func (*ConfigureStateStore) Descriptor() ([]byte, []int) { return file_tfplugin6_proto_rawDescGZIP(), []int{38} } +type ReadStateBytes struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ReadStateBytes) Reset() { + *x = ReadStateBytes{} + mi := &file_tfplugin6_proto_msgTypes[39] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ReadStateBytes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReadStateBytes) ProtoMessage() {} + +func (x *ReadStateBytes) ProtoReflect() protoreflect.Message { + mi := &file_tfplugin6_proto_msgTypes[39] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReadStateBytes.ProtoReflect.Descriptor instead. +func (*ReadStateBytes) Descriptor() ([]byte, []int) { + return file_tfplugin6_proto_rawDescGZIP(), []int{39} +} + +type WriteStateBytes struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WriteStateBytes) Reset() { + *x = WriteStateBytes{} + mi := &file_tfplugin6_proto_msgTypes[40] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WriteStateBytes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WriteStateBytes) ProtoMessage() {} + +func (x *WriteStateBytes) ProtoReflect() protoreflect.Message { + mi := &file_tfplugin6_proto_msgTypes[40] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WriteStateBytes.ProtoReflect.Descriptor instead. +func (*WriteStateBytes) Descriptor() ([]byte, []int) { + return file_tfplugin6_proto_rawDescGZIP(), []int{40} +} + +type StateRange struct { + state protoimpl.MessageState `protogen:"open.v1"` + Start int64 `protobuf:"varint,1,opt,name=start,proto3" json:"start,omitempty"` + End int64 `protobuf:"varint,2,opt,name=end,proto3" json:"end,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *StateRange) Reset() { + *x = StateRange{} + mi := &file_tfplugin6_proto_msgTypes[41] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StateRange) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StateRange) ProtoMessage() {} + +func (x *StateRange) ProtoReflect() protoreflect.Message { + mi := &file_tfplugin6_proto_msgTypes[41] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StateRange.ProtoReflect.Descriptor instead. +func (*StateRange) Descriptor() ([]byte, []int) { + return file_tfplugin6_proto_rawDescGZIP(), []int{41} +} + +func (x *StateRange) GetStart() int64 { + if x != nil { + return x.Start + } + return 0 +} + +func (x *StateRange) GetEnd() int64 { + if x != nil { + return x.End + } + return 0 +} + type GetStates struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -2149,7 +2273,7 @@ type GetStates struct { func (x *GetStates) Reset() { *x = GetStates{} - mi := &file_tfplugin6_proto_msgTypes[39] + mi := &file_tfplugin6_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2161,7 +2285,7 @@ func (x *GetStates) String() string { func (*GetStates) ProtoMessage() {} func (x *GetStates) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[39] + mi := &file_tfplugin6_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2174,7 +2298,7 @@ func (x *GetStates) ProtoReflect() protoreflect.Message { // Deprecated: Use GetStates.ProtoReflect.Descriptor instead. func (*GetStates) Descriptor() ([]byte, []int) { - return file_tfplugin6_proto_rawDescGZIP(), []int{39} + return file_tfplugin6_proto_rawDescGZIP(), []int{42} } type DeleteState struct { @@ -2185,7 +2309,7 @@ type DeleteState struct { func (x *DeleteState) Reset() { *x = DeleteState{} - mi := &file_tfplugin6_proto_msgTypes[40] + mi := &file_tfplugin6_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2197,7 +2321,7 @@ func (x *DeleteState) String() string { func (*DeleteState) ProtoMessage() {} func (x *DeleteState) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[40] + mi := &file_tfplugin6_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2210,7 +2334,7 @@ func (x *DeleteState) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteState.ProtoReflect.Descriptor instead. func (*DeleteState) Descriptor() ([]byte, []int) { - return file_tfplugin6_proto_rawDescGZIP(), []int{40} + return file_tfplugin6_proto_rawDescGZIP(), []int{43} } type PlanAction struct { @@ -2221,7 +2345,7 @@ type PlanAction struct { func (x *PlanAction) Reset() { *x = PlanAction{} - mi := &file_tfplugin6_proto_msgTypes[41] + mi := &file_tfplugin6_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2233,7 +2357,7 @@ func (x *PlanAction) String() string { func (*PlanAction) ProtoMessage() {} func (x *PlanAction) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[41] + mi := &file_tfplugin6_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2246,7 +2370,7 @@ func (x *PlanAction) ProtoReflect() protoreflect.Message { // Deprecated: Use PlanAction.ProtoReflect.Descriptor instead. func (*PlanAction) Descriptor() ([]byte, []int) { - return file_tfplugin6_proto_rawDescGZIP(), []int{41} + return file_tfplugin6_proto_rawDescGZIP(), []int{44} } type InvokeAction struct { @@ -2257,7 +2381,7 @@ type InvokeAction struct { func (x *InvokeAction) Reset() { *x = InvokeAction{} - mi := &file_tfplugin6_proto_msgTypes[42] + mi := &file_tfplugin6_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2269,7 +2393,7 @@ func (x *InvokeAction) String() string { func (*InvokeAction) ProtoMessage() {} func (x *InvokeAction) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[42] + mi := &file_tfplugin6_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2282,7 +2406,7 @@ func (x *InvokeAction) ProtoReflect() protoreflect.Message { // Deprecated: Use InvokeAction.ProtoReflect.Descriptor instead. func (*InvokeAction) Descriptor() ([]byte, []int) { - return file_tfplugin6_proto_rawDescGZIP(), []int{42} + return file_tfplugin6_proto_rawDescGZIP(), []int{45} } type ValidateActionConfig struct { @@ -2293,7 +2417,7 @@ type ValidateActionConfig struct { func (x *ValidateActionConfig) Reset() { *x = ValidateActionConfig{} - mi := &file_tfplugin6_proto_msgTypes[43] + mi := &file_tfplugin6_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2305,7 +2429,7 @@ func (x *ValidateActionConfig) String() string { func (*ValidateActionConfig) ProtoMessage() {} func (x *ValidateActionConfig) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[43] + mi := &file_tfplugin6_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2318,7 +2442,7 @@ func (x *ValidateActionConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use ValidateActionConfig.ProtoReflect.Descriptor instead. func (*ValidateActionConfig) Descriptor() ([]byte, []int) { - return file_tfplugin6_proto_rawDescGZIP(), []int{43} + return file_tfplugin6_proto_rawDescGZIP(), []int{46} } type LinkedResourceConfig struct { @@ -2331,7 +2455,7 @@ type LinkedResourceConfig struct { func (x *LinkedResourceConfig) Reset() { *x = LinkedResourceConfig{} - mi := &file_tfplugin6_proto_msgTypes[44] + mi := &file_tfplugin6_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2343,7 +2467,7 @@ func (x *LinkedResourceConfig) String() string { func (*LinkedResourceConfig) ProtoMessage() {} func (x *LinkedResourceConfig) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[44] + mi := &file_tfplugin6_proto_msgTypes[47] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2356,7 +2480,7 @@ func (x *LinkedResourceConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use LinkedResourceConfig.ProtoReflect.Descriptor instead. func (*LinkedResourceConfig) Descriptor() ([]byte, []int) { - return file_tfplugin6_proto_rawDescGZIP(), []int{44} + return file_tfplugin6_proto_rawDescGZIP(), []int{47} } func (x *LinkedResourceConfig) GetTypeName() string { @@ -2387,7 +2511,7 @@ type AttributePath_Step struct { func (x *AttributePath_Step) Reset() { *x = AttributePath_Step{} - mi := &file_tfplugin6_proto_msgTypes[45] + mi := &file_tfplugin6_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2399,7 +2523,7 @@ func (x *AttributePath_Step) String() string { func (*AttributePath_Step) ProtoMessage() {} func (x *AttributePath_Step) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[45] + mi := &file_tfplugin6_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2483,7 +2607,7 @@ type StopProvider_Request struct { func (x *StopProvider_Request) Reset() { *x = StopProvider_Request{} - mi := &file_tfplugin6_proto_msgTypes[46] + mi := &file_tfplugin6_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2495,7 +2619,7 @@ func (x *StopProvider_Request) String() string { func (*StopProvider_Request) ProtoMessage() {} func (x *StopProvider_Request) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[46] + mi := &file_tfplugin6_proto_msgTypes[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2520,7 +2644,7 @@ type StopProvider_Response struct { func (x *StopProvider_Response) Reset() { *x = StopProvider_Response{} - mi := &file_tfplugin6_proto_msgTypes[47] + mi := &file_tfplugin6_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2532,7 +2656,7 @@ func (x *StopProvider_Response) String() string { func (*StopProvider_Response) ProtoMessage() {} func (x *StopProvider_Response) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[47] + mi := &file_tfplugin6_proto_msgTypes[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2578,7 +2702,7 @@ type ResourceIdentitySchema_IdentityAttribute struct { func (x *ResourceIdentitySchema_IdentityAttribute) Reset() { *x = ResourceIdentitySchema_IdentityAttribute{} - mi := &file_tfplugin6_proto_msgTypes[49] + mi := &file_tfplugin6_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2590,7 +2714,7 @@ func (x *ResourceIdentitySchema_IdentityAttribute) String() string { func (*ResourceIdentitySchema_IdentityAttribute) ProtoMessage() {} func (x *ResourceIdentitySchema_IdentityAttribute) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[49] + mi := &file_tfplugin6_proto_msgTypes[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2655,7 +2779,7 @@ type ActionSchema_LinkedResource struct { func (x *ActionSchema_LinkedResource) Reset() { *x = ActionSchema_LinkedResource{} - mi := &file_tfplugin6_proto_msgTypes[50] + mi := &file_tfplugin6_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2667,7 +2791,7 @@ func (x *ActionSchema_LinkedResource) String() string { func (*ActionSchema_LinkedResource) ProtoMessage() {} func (x *ActionSchema_LinkedResource) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[50] + mi := &file_tfplugin6_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2707,7 +2831,7 @@ type ActionSchema_Unlinked struct { func (x *ActionSchema_Unlinked) Reset() { *x = ActionSchema_Unlinked{} - mi := &file_tfplugin6_proto_msgTypes[51] + mi := &file_tfplugin6_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2719,7 +2843,7 @@ func (x *ActionSchema_Unlinked) String() string { func (*ActionSchema_Unlinked) ProtoMessage() {} func (x *ActionSchema_Unlinked) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[51] + mi := &file_tfplugin6_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2748,7 +2872,7 @@ type ActionSchema_Lifecycle struct { func (x *ActionSchema_Lifecycle) Reset() { *x = ActionSchema_Lifecycle{} - mi := &file_tfplugin6_proto_msgTypes[52] + mi := &file_tfplugin6_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2760,7 +2884,7 @@ func (x *ActionSchema_Lifecycle) String() string { func (*ActionSchema_Lifecycle) ProtoMessage() {} func (x *ActionSchema_Lifecycle) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[52] + mi := &file_tfplugin6_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2800,7 +2924,7 @@ type ActionSchema_Linked struct { func (x *ActionSchema_Linked) Reset() { *x = ActionSchema_Linked{} - mi := &file_tfplugin6_proto_msgTypes[53] + mi := &file_tfplugin6_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2812,7 +2936,7 @@ func (x *ActionSchema_Linked) String() string { func (*ActionSchema_Linked) ProtoMessage() {} func (x *ActionSchema_Linked) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[53] + mi := &file_tfplugin6_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2849,7 +2973,7 @@ type Schema_Block struct { func (x *Schema_Block) Reset() { *x = Schema_Block{} - mi := &file_tfplugin6_proto_msgTypes[54] + mi := &file_tfplugin6_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2861,7 +2985,7 @@ func (x *Schema_Block) String() string { func (*Schema_Block) ProtoMessage() {} func (x *Schema_Block) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[54] + mi := &file_tfplugin6_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2938,7 +3062,7 @@ type Schema_Attribute struct { func (x *Schema_Attribute) Reset() { *x = Schema_Attribute{} - mi := &file_tfplugin6_proto_msgTypes[55] + mi := &file_tfplugin6_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2950,7 +3074,7 @@ func (x *Schema_Attribute) String() string { func (*Schema_Attribute) ProtoMessage() {} func (x *Schema_Attribute) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[55] + mi := &file_tfplugin6_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3056,7 +3180,7 @@ type Schema_NestedBlock struct { func (x *Schema_NestedBlock) Reset() { *x = Schema_NestedBlock{} - mi := &file_tfplugin6_proto_msgTypes[56] + mi := &file_tfplugin6_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3068,7 +3192,7 @@ func (x *Schema_NestedBlock) String() string { func (*Schema_NestedBlock) ProtoMessage() {} func (x *Schema_NestedBlock) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[56] + mi := &file_tfplugin6_proto_msgTypes[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3136,7 +3260,7 @@ type Schema_Object struct { func (x *Schema_Object) Reset() { *x = Schema_Object{} - mi := &file_tfplugin6_proto_msgTypes[57] + mi := &file_tfplugin6_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3148,7 +3272,7 @@ func (x *Schema_Object) String() string { func (*Schema_Object) ProtoMessage() {} func (x *Schema_Object) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[57] + mi := &file_tfplugin6_proto_msgTypes[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3219,7 +3343,7 @@ type Function_Parameter struct { func (x *Function_Parameter) Reset() { *x = Function_Parameter{} - mi := &file_tfplugin6_proto_msgTypes[58] + mi := &file_tfplugin6_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3231,7 +3355,7 @@ func (x *Function_Parameter) String() string { func (*Function_Parameter) ProtoMessage() {} func (x *Function_Parameter) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[58] + mi := &file_tfplugin6_proto_msgTypes[61] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3299,7 +3423,7 @@ type Function_Return struct { func (x *Function_Return) Reset() { *x = Function_Return{} - mi := &file_tfplugin6_proto_msgTypes[59] + mi := &file_tfplugin6_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3311,7 +3435,7 @@ func (x *Function_Return) String() string { func (*Function_Return) ProtoMessage() {} func (x *Function_Return) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[59] + mi := &file_tfplugin6_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3342,7 +3466,7 @@ type GetMetadata_Request struct { func (x *GetMetadata_Request) Reset() { *x = GetMetadata_Request{} - mi := &file_tfplugin6_proto_msgTypes[60] + mi := &file_tfplugin6_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3354,7 +3478,7 @@ func (x *GetMetadata_Request) String() string { func (*GetMetadata_Request) ProtoMessage() {} func (x *GetMetadata_Request) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[60] + mi := &file_tfplugin6_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3388,7 +3512,7 @@ type GetMetadata_Response struct { func (x *GetMetadata_Response) Reset() { *x = GetMetadata_Response{} - mi := &file_tfplugin6_proto_msgTypes[61] + mi := &file_tfplugin6_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3400,7 +3524,7 @@ func (x *GetMetadata_Response) String() string { func (*GetMetadata_Response) ProtoMessage() {} func (x *GetMetadata_Response) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[61] + mi := &file_tfplugin6_proto_msgTypes[64] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3488,7 +3612,7 @@ type GetMetadata_EphemeralMetadata struct { func (x *GetMetadata_EphemeralMetadata) Reset() { *x = GetMetadata_EphemeralMetadata{} - mi := &file_tfplugin6_proto_msgTypes[62] + mi := &file_tfplugin6_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3500,7 +3624,7 @@ func (x *GetMetadata_EphemeralMetadata) String() string { func (*GetMetadata_EphemeralMetadata) ProtoMessage() {} func (x *GetMetadata_EphemeralMetadata) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[62] + mi := &file_tfplugin6_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3533,7 +3657,7 @@ type GetMetadata_FunctionMetadata struct { func (x *GetMetadata_FunctionMetadata) Reset() { *x = GetMetadata_FunctionMetadata{} - mi := &file_tfplugin6_proto_msgTypes[63] + mi := &file_tfplugin6_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3545,7 +3669,7 @@ func (x *GetMetadata_FunctionMetadata) String() string { func (*GetMetadata_FunctionMetadata) ProtoMessage() {} func (x *GetMetadata_FunctionMetadata) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[63] + mi := &file_tfplugin6_proto_msgTypes[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3577,7 +3701,7 @@ type GetMetadata_DataSourceMetadata struct { func (x *GetMetadata_DataSourceMetadata) Reset() { *x = GetMetadata_DataSourceMetadata{} - mi := &file_tfplugin6_proto_msgTypes[64] + mi := &file_tfplugin6_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3589,7 +3713,7 @@ func (x *GetMetadata_DataSourceMetadata) String() string { func (*GetMetadata_DataSourceMetadata) ProtoMessage() {} func (x *GetMetadata_DataSourceMetadata) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[64] + mi := &file_tfplugin6_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3621,7 +3745,7 @@ type GetMetadata_ResourceMetadata struct { func (x *GetMetadata_ResourceMetadata) Reset() { *x = GetMetadata_ResourceMetadata{} - mi := &file_tfplugin6_proto_msgTypes[65] + mi := &file_tfplugin6_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3633,7 +3757,7 @@ func (x *GetMetadata_ResourceMetadata) String() string { func (*GetMetadata_ResourceMetadata) ProtoMessage() {} func (x *GetMetadata_ResourceMetadata) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[65] + mi := &file_tfplugin6_proto_msgTypes[68] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3665,7 +3789,7 @@ type GetMetadata_ListResourceMetadata struct { func (x *GetMetadata_ListResourceMetadata) Reset() { *x = GetMetadata_ListResourceMetadata{} - mi := &file_tfplugin6_proto_msgTypes[66] + mi := &file_tfplugin6_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3677,7 +3801,7 @@ func (x *GetMetadata_ListResourceMetadata) String() string { func (*GetMetadata_ListResourceMetadata) ProtoMessage() {} func (x *GetMetadata_ListResourceMetadata) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[66] + mi := &file_tfplugin6_proto_msgTypes[69] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3709,7 +3833,7 @@ type GetMetadata_StateStoreMetadata struct { func (x *GetMetadata_StateStoreMetadata) Reset() { *x = GetMetadata_StateStoreMetadata{} - mi := &file_tfplugin6_proto_msgTypes[67] + mi := &file_tfplugin6_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3721,7 +3845,7 @@ func (x *GetMetadata_StateStoreMetadata) String() string { func (*GetMetadata_StateStoreMetadata) ProtoMessage() {} func (x *GetMetadata_StateStoreMetadata) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[67] + mi := &file_tfplugin6_proto_msgTypes[70] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3753,7 +3877,7 @@ type GetMetadata_ActionMetadata struct { func (x *GetMetadata_ActionMetadata) Reset() { *x = GetMetadata_ActionMetadata{} - mi := &file_tfplugin6_proto_msgTypes[68] + mi := &file_tfplugin6_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3765,7 +3889,7 @@ func (x *GetMetadata_ActionMetadata) String() string { func (*GetMetadata_ActionMetadata) ProtoMessage() {} func (x *GetMetadata_ActionMetadata) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[68] + mi := &file_tfplugin6_proto_msgTypes[71] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3796,7 +3920,7 @@ type GetProviderSchema_Request struct { func (x *GetProviderSchema_Request) Reset() { *x = GetProviderSchema_Request{} - mi := &file_tfplugin6_proto_msgTypes[69] + mi := &file_tfplugin6_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3808,7 +3932,7 @@ func (x *GetProviderSchema_Request) String() string { func (*GetProviderSchema_Request) ProtoMessage() {} func (x *GetProviderSchema_Request) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[69] + mi := &file_tfplugin6_proto_msgTypes[72] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3843,7 +3967,7 @@ type GetProviderSchema_Response struct { func (x *GetProviderSchema_Response) Reset() { *x = GetProviderSchema_Response{} - mi := &file_tfplugin6_proto_msgTypes[70] + mi := &file_tfplugin6_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3855,7 +3979,7 @@ func (x *GetProviderSchema_Response) String() string { func (*GetProviderSchema_Response) ProtoMessage() {} func (x *GetProviderSchema_Response) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[70] + mi := &file_tfplugin6_proto_msgTypes[73] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3957,7 +4081,7 @@ type ValidateProviderConfig_Request struct { func (x *ValidateProviderConfig_Request) Reset() { *x = ValidateProviderConfig_Request{} - mi := &file_tfplugin6_proto_msgTypes[78] + mi := &file_tfplugin6_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3969,7 +4093,7 @@ func (x *ValidateProviderConfig_Request) String() string { func (*ValidateProviderConfig_Request) ProtoMessage() {} func (x *ValidateProviderConfig_Request) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[78] + mi := &file_tfplugin6_proto_msgTypes[81] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4001,7 +4125,7 @@ type ValidateProviderConfig_Response struct { func (x *ValidateProviderConfig_Response) Reset() { *x = ValidateProviderConfig_Response{} - mi := &file_tfplugin6_proto_msgTypes[79] + mi := &file_tfplugin6_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4013,7 +4137,7 @@ func (x *ValidateProviderConfig_Response) String() string { func (*ValidateProviderConfig_Response) ProtoMessage() {} func (x *ValidateProviderConfig_Response) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[79] + mi := &file_tfplugin6_proto_msgTypes[82] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4062,7 +4186,7 @@ type UpgradeResourceState_Request struct { func (x *UpgradeResourceState_Request) Reset() { *x = UpgradeResourceState_Request{} - mi := &file_tfplugin6_proto_msgTypes[80] + mi := &file_tfplugin6_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4074,7 +4198,7 @@ func (x *UpgradeResourceState_Request) String() string { func (*UpgradeResourceState_Request) ProtoMessage() {} func (x *UpgradeResourceState_Request) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[80] + mi := &file_tfplugin6_proto_msgTypes[83] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4127,7 +4251,7 @@ type UpgradeResourceState_Response struct { func (x *UpgradeResourceState_Response) Reset() { *x = UpgradeResourceState_Response{} - mi := &file_tfplugin6_proto_msgTypes[81] + mi := &file_tfplugin6_proto_msgTypes[84] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4139,7 +4263,7 @@ func (x *UpgradeResourceState_Response) String() string { func (*UpgradeResourceState_Response) ProtoMessage() {} func (x *UpgradeResourceState_Response) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[81] + mi := &file_tfplugin6_proto_msgTypes[84] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4177,7 +4301,7 @@ type GetResourceIdentitySchemas_Request struct { func (x *GetResourceIdentitySchemas_Request) Reset() { *x = GetResourceIdentitySchemas_Request{} - mi := &file_tfplugin6_proto_msgTypes[82] + mi := &file_tfplugin6_proto_msgTypes[85] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4189,7 +4313,7 @@ func (x *GetResourceIdentitySchemas_Request) String() string { func (*GetResourceIdentitySchemas_Request) ProtoMessage() {} func (x *GetResourceIdentitySchemas_Request) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[82] + mi := &file_tfplugin6_proto_msgTypes[85] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4217,7 +4341,7 @@ type GetResourceIdentitySchemas_Response struct { func (x *GetResourceIdentitySchemas_Response) Reset() { *x = GetResourceIdentitySchemas_Response{} - mi := &file_tfplugin6_proto_msgTypes[83] + mi := &file_tfplugin6_proto_msgTypes[86] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4229,7 +4353,7 @@ func (x *GetResourceIdentitySchemas_Response) String() string { func (*GetResourceIdentitySchemas_Response) ProtoMessage() {} func (x *GetResourceIdentitySchemas_Response) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[83] + mi := &file_tfplugin6_proto_msgTypes[86] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4276,7 +4400,7 @@ type UpgradeResourceIdentity_Request struct { func (x *UpgradeResourceIdentity_Request) Reset() { *x = UpgradeResourceIdentity_Request{} - mi := &file_tfplugin6_proto_msgTypes[85] + mi := &file_tfplugin6_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4288,7 +4412,7 @@ func (x *UpgradeResourceIdentity_Request) String() string { func (*UpgradeResourceIdentity_Request) ProtoMessage() {} func (x *UpgradeResourceIdentity_Request) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[85] + mi := &file_tfplugin6_proto_msgTypes[88] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4337,7 +4461,7 @@ type UpgradeResourceIdentity_Response struct { func (x *UpgradeResourceIdentity_Response) Reset() { *x = UpgradeResourceIdentity_Response{} - mi := &file_tfplugin6_proto_msgTypes[86] + mi := &file_tfplugin6_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4349,7 +4473,7 @@ func (x *UpgradeResourceIdentity_Response) String() string { func (*UpgradeResourceIdentity_Response) ProtoMessage() {} func (x *UpgradeResourceIdentity_Response) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[86] + mi := &file_tfplugin6_proto_msgTypes[89] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4390,7 +4514,7 @@ type ValidateResourceConfig_Request struct { func (x *ValidateResourceConfig_Request) Reset() { *x = ValidateResourceConfig_Request{} - mi := &file_tfplugin6_proto_msgTypes[87] + mi := &file_tfplugin6_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4402,7 +4526,7 @@ func (x *ValidateResourceConfig_Request) String() string { func (*ValidateResourceConfig_Request) ProtoMessage() {} func (x *ValidateResourceConfig_Request) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[87] + mi := &file_tfplugin6_proto_msgTypes[90] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4448,7 +4572,7 @@ type ValidateResourceConfig_Response struct { func (x *ValidateResourceConfig_Response) Reset() { *x = ValidateResourceConfig_Response{} - mi := &file_tfplugin6_proto_msgTypes[88] + mi := &file_tfplugin6_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4460,7 +4584,7 @@ func (x *ValidateResourceConfig_Response) String() string { func (*ValidateResourceConfig_Response) ProtoMessage() {} func (x *ValidateResourceConfig_Response) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[88] + mi := &file_tfplugin6_proto_msgTypes[91] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4493,7 +4617,7 @@ type ValidateDataResourceConfig_Request struct { func (x *ValidateDataResourceConfig_Request) Reset() { *x = ValidateDataResourceConfig_Request{} - mi := &file_tfplugin6_proto_msgTypes[89] + mi := &file_tfplugin6_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4505,7 +4629,7 @@ func (x *ValidateDataResourceConfig_Request) String() string { func (*ValidateDataResourceConfig_Request) ProtoMessage() {} func (x *ValidateDataResourceConfig_Request) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[89] + mi := &file_tfplugin6_proto_msgTypes[92] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4544,7 +4668,7 @@ type ValidateDataResourceConfig_Response struct { func (x *ValidateDataResourceConfig_Response) Reset() { *x = ValidateDataResourceConfig_Response{} - mi := &file_tfplugin6_proto_msgTypes[90] + mi := &file_tfplugin6_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4556,7 +4680,7 @@ func (x *ValidateDataResourceConfig_Response) String() string { func (*ValidateDataResourceConfig_Response) ProtoMessage() {} func (x *ValidateDataResourceConfig_Response) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[90] + mi := &file_tfplugin6_proto_msgTypes[93] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4589,7 +4713,7 @@ type ValidateEphemeralResourceConfig_Request struct { func (x *ValidateEphemeralResourceConfig_Request) Reset() { *x = ValidateEphemeralResourceConfig_Request{} - mi := &file_tfplugin6_proto_msgTypes[91] + mi := &file_tfplugin6_proto_msgTypes[94] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4601,7 +4725,7 @@ func (x *ValidateEphemeralResourceConfig_Request) String() string { func (*ValidateEphemeralResourceConfig_Request) ProtoMessage() {} func (x *ValidateEphemeralResourceConfig_Request) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[91] + mi := &file_tfplugin6_proto_msgTypes[94] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4640,7 +4764,7 @@ type ValidateEphemeralResourceConfig_Response struct { func (x *ValidateEphemeralResourceConfig_Response) Reset() { *x = ValidateEphemeralResourceConfig_Response{} - mi := &file_tfplugin6_proto_msgTypes[92] + mi := &file_tfplugin6_proto_msgTypes[95] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4652,7 +4776,7 @@ func (x *ValidateEphemeralResourceConfig_Response) String() string { func (*ValidateEphemeralResourceConfig_Response) ProtoMessage() {} func (x *ValidateEphemeralResourceConfig_Response) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[92] + mi := &file_tfplugin6_proto_msgTypes[95] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4686,7 +4810,7 @@ type ConfigureProvider_Request struct { func (x *ConfigureProvider_Request) Reset() { *x = ConfigureProvider_Request{} - mi := &file_tfplugin6_proto_msgTypes[93] + mi := &file_tfplugin6_proto_msgTypes[96] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4698,7 +4822,7 @@ func (x *ConfigureProvider_Request) String() string { func (*ConfigureProvider_Request) ProtoMessage() {} func (x *ConfigureProvider_Request) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[93] + mi := &file_tfplugin6_proto_msgTypes[96] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4744,7 +4868,7 @@ type ConfigureProvider_Response struct { func (x *ConfigureProvider_Response) Reset() { *x = ConfigureProvider_Response{} - mi := &file_tfplugin6_proto_msgTypes[94] + mi := &file_tfplugin6_proto_msgTypes[97] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4756,7 +4880,7 @@ func (x *ConfigureProvider_Response) String() string { func (*ConfigureProvider_Response) ProtoMessage() {} func (x *ConfigureProvider_Response) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[94] + mi := &file_tfplugin6_proto_msgTypes[97] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4801,7 +4925,7 @@ type ReadResource_Request struct { func (x *ReadResource_Request) Reset() { *x = ReadResource_Request{} - mi := &file_tfplugin6_proto_msgTypes[95] + mi := &file_tfplugin6_proto_msgTypes[98] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4813,7 +4937,7 @@ func (x *ReadResource_Request) String() string { func (*ReadResource_Request) ProtoMessage() {} func (x *ReadResource_Request) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[95] + mi := &file_tfplugin6_proto_msgTypes[98] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4886,7 +5010,7 @@ type ReadResource_Response struct { func (x *ReadResource_Response) Reset() { *x = ReadResource_Response{} - mi := &file_tfplugin6_proto_msgTypes[96] + mi := &file_tfplugin6_proto_msgTypes[99] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4898,7 +5022,7 @@ func (x *ReadResource_Response) String() string { func (*ReadResource_Response) ProtoMessage() {} func (x *ReadResource_Response) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[96] + mi := &file_tfplugin6_proto_msgTypes[99] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4965,7 +5089,7 @@ type PlanResourceChange_Request struct { func (x *PlanResourceChange_Request) Reset() { *x = PlanResourceChange_Request{} - mi := &file_tfplugin6_proto_msgTypes[97] + mi := &file_tfplugin6_proto_msgTypes[100] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4977,7 +5101,7 @@ func (x *PlanResourceChange_Request) String() string { func (*PlanResourceChange_Request) ProtoMessage() {} func (x *PlanResourceChange_Request) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[97] + mi := &file_tfplugin6_proto_msgTypes[100] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5077,7 +5201,7 @@ type PlanResourceChange_Response struct { func (x *PlanResourceChange_Response) Reset() { *x = PlanResourceChange_Response{} - mi := &file_tfplugin6_proto_msgTypes[98] + mi := &file_tfplugin6_proto_msgTypes[101] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5089,7 +5213,7 @@ func (x *PlanResourceChange_Response) String() string { func (*PlanResourceChange_Response) ProtoMessage() {} func (x *PlanResourceChange_Response) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[98] + mi := &file_tfplugin6_proto_msgTypes[101] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5169,7 +5293,7 @@ type ApplyResourceChange_Request struct { func (x *ApplyResourceChange_Request) Reset() { *x = ApplyResourceChange_Request{} - mi := &file_tfplugin6_proto_msgTypes[99] + mi := &file_tfplugin6_proto_msgTypes[102] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5181,7 +5305,7 @@ func (x *ApplyResourceChange_Request) String() string { func (*ApplyResourceChange_Request) ProtoMessage() {} func (x *ApplyResourceChange_Request) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[99] + mi := &file_tfplugin6_proto_msgTypes[102] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5270,7 +5394,7 @@ type ApplyResourceChange_Response struct { func (x *ApplyResourceChange_Response) Reset() { *x = ApplyResourceChange_Response{} - mi := &file_tfplugin6_proto_msgTypes[100] + mi := &file_tfplugin6_proto_msgTypes[103] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5282,7 +5406,7 @@ func (x *ApplyResourceChange_Response) String() string { func (*ApplyResourceChange_Response) ProtoMessage() {} func (x *ApplyResourceChange_Response) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[100] + mi := &file_tfplugin6_proto_msgTypes[103] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5345,7 +5469,7 @@ type ImportResourceState_Request struct { func (x *ImportResourceState_Request) Reset() { *x = ImportResourceState_Request{} - mi := &file_tfplugin6_proto_msgTypes[101] + mi := &file_tfplugin6_proto_msgTypes[104] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5357,7 +5481,7 @@ func (x *ImportResourceState_Request) String() string { func (*ImportResourceState_Request) ProtoMessage() {} func (x *ImportResourceState_Request) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[101] + mi := &file_tfplugin6_proto_msgTypes[104] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5413,7 +5537,7 @@ type ImportResourceState_ImportedResource struct { func (x *ImportResourceState_ImportedResource) Reset() { *x = ImportResourceState_ImportedResource{} - mi := &file_tfplugin6_proto_msgTypes[102] + mi := &file_tfplugin6_proto_msgTypes[105] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5425,7 +5549,7 @@ func (x *ImportResourceState_ImportedResource) String() string { func (*ImportResourceState_ImportedResource) ProtoMessage() {} func (x *ImportResourceState_ImportedResource) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[102] + mi := &file_tfplugin6_proto_msgTypes[105] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5482,7 +5606,7 @@ type ImportResourceState_Response struct { func (x *ImportResourceState_Response) Reset() { *x = ImportResourceState_Response{} - mi := &file_tfplugin6_proto_msgTypes[103] + mi := &file_tfplugin6_proto_msgTypes[106] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5494,7 +5618,7 @@ func (x *ImportResourceState_Response) String() string { func (*ImportResourceState_Response) ProtoMessage() {} func (x *ImportResourceState_Response) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[103] + mi := &file_tfplugin6_proto_msgTypes[106] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5561,7 +5685,7 @@ type MoveResourceState_Request struct { func (x *MoveResourceState_Request) Reset() { *x = MoveResourceState_Request{} - mi := &file_tfplugin6_proto_msgTypes[104] + mi := &file_tfplugin6_proto_msgTypes[107] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5573,7 +5697,7 @@ func (x *MoveResourceState_Request) String() string { func (*MoveResourceState_Request) ProtoMessage() {} func (x *MoveResourceState_Request) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[104] + mi := &file_tfplugin6_proto_msgTypes[107] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5660,7 +5784,7 @@ type MoveResourceState_Response struct { func (x *MoveResourceState_Response) Reset() { *x = MoveResourceState_Response{} - mi := &file_tfplugin6_proto_msgTypes[105] + mi := &file_tfplugin6_proto_msgTypes[108] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5672,7 +5796,7 @@ func (x *MoveResourceState_Response) String() string { func (*MoveResourceState_Response) ProtoMessage() {} func (x *MoveResourceState_Response) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[105] + mi := &file_tfplugin6_proto_msgTypes[108] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5728,7 +5852,7 @@ type ReadDataSource_Request struct { func (x *ReadDataSource_Request) Reset() { *x = ReadDataSource_Request{} - mi := &file_tfplugin6_proto_msgTypes[106] + mi := &file_tfplugin6_proto_msgTypes[109] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5740,7 +5864,7 @@ func (x *ReadDataSource_Request) String() string { func (*ReadDataSource_Request) ProtoMessage() {} func (x *ReadDataSource_Request) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[106] + mi := &file_tfplugin6_proto_msgTypes[109] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5797,7 +5921,7 @@ type ReadDataSource_Response struct { func (x *ReadDataSource_Response) Reset() { *x = ReadDataSource_Response{} - mi := &file_tfplugin6_proto_msgTypes[107] + mi := &file_tfplugin6_proto_msgTypes[110] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5809,7 +5933,7 @@ func (x *ReadDataSource_Response) String() string { func (*ReadDataSource_Response) ProtoMessage() {} func (x *ReadDataSource_Response) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[107] + mi := &file_tfplugin6_proto_msgTypes[110] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5857,7 +5981,7 @@ type OpenEphemeralResource_Request struct { func (x *OpenEphemeralResource_Request) Reset() { *x = OpenEphemeralResource_Request{} - mi := &file_tfplugin6_proto_msgTypes[108] + mi := &file_tfplugin6_proto_msgTypes[111] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5869,7 +5993,7 @@ func (x *OpenEphemeralResource_Request) String() string { func (*OpenEphemeralResource_Request) ProtoMessage() {} func (x *OpenEphemeralResource_Request) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[108] + mi := &file_tfplugin6_proto_msgTypes[111] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5919,7 +6043,7 @@ type OpenEphemeralResource_Response struct { func (x *OpenEphemeralResource_Response) Reset() { *x = OpenEphemeralResource_Response{} - mi := &file_tfplugin6_proto_msgTypes[109] + mi := &file_tfplugin6_proto_msgTypes[112] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5931,7 +6055,7 @@ func (x *OpenEphemeralResource_Response) String() string { func (*OpenEphemeralResource_Response) ProtoMessage() {} func (x *OpenEphemeralResource_Response) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[109] + mi := &file_tfplugin6_proto_msgTypes[112] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5992,7 +6116,7 @@ type RenewEphemeralResource_Request struct { func (x *RenewEphemeralResource_Request) Reset() { *x = RenewEphemeralResource_Request{} - mi := &file_tfplugin6_proto_msgTypes[110] + mi := &file_tfplugin6_proto_msgTypes[113] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6004,7 +6128,7 @@ func (x *RenewEphemeralResource_Request) String() string { func (*RenewEphemeralResource_Request) ProtoMessage() {} func (x *RenewEphemeralResource_Request) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[110] + mi := &file_tfplugin6_proto_msgTypes[113] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6045,7 +6169,7 @@ type RenewEphemeralResource_Response struct { func (x *RenewEphemeralResource_Response) Reset() { *x = RenewEphemeralResource_Response{} - mi := &file_tfplugin6_proto_msgTypes[111] + mi := &file_tfplugin6_proto_msgTypes[114] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6057,7 +6181,7 @@ func (x *RenewEphemeralResource_Response) String() string { func (*RenewEphemeralResource_Response) ProtoMessage() {} func (x *RenewEphemeralResource_Response) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[111] + mi := &file_tfplugin6_proto_msgTypes[114] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6104,7 +6228,7 @@ type CloseEphemeralResource_Request struct { func (x *CloseEphemeralResource_Request) Reset() { *x = CloseEphemeralResource_Request{} - mi := &file_tfplugin6_proto_msgTypes[112] + mi := &file_tfplugin6_proto_msgTypes[115] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6116,7 +6240,7 @@ func (x *CloseEphemeralResource_Request) String() string { func (*CloseEphemeralResource_Request) ProtoMessage() {} func (x *CloseEphemeralResource_Request) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[112] + mi := &file_tfplugin6_proto_msgTypes[115] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6155,7 +6279,7 @@ type CloseEphemeralResource_Response struct { func (x *CloseEphemeralResource_Response) Reset() { *x = CloseEphemeralResource_Response{} - mi := &file_tfplugin6_proto_msgTypes[113] + mi := &file_tfplugin6_proto_msgTypes[116] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6167,7 +6291,7 @@ func (x *CloseEphemeralResource_Response) String() string { func (*CloseEphemeralResource_Response) ProtoMessage() {} func (x *CloseEphemeralResource_Response) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[113] + mi := &file_tfplugin6_proto_msgTypes[116] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6198,7 +6322,7 @@ type GetFunctions_Request struct { func (x *GetFunctions_Request) Reset() { *x = GetFunctions_Request{} - mi := &file_tfplugin6_proto_msgTypes[114] + mi := &file_tfplugin6_proto_msgTypes[117] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6210,7 +6334,7 @@ func (x *GetFunctions_Request) String() string { func (*GetFunctions_Request) ProtoMessage() {} func (x *GetFunctions_Request) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[114] + mi := &file_tfplugin6_proto_msgTypes[117] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6238,7 +6362,7 @@ type GetFunctions_Response struct { func (x *GetFunctions_Response) Reset() { *x = GetFunctions_Response{} - mi := &file_tfplugin6_proto_msgTypes[115] + mi := &file_tfplugin6_proto_msgTypes[118] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6250,7 +6374,7 @@ func (x *GetFunctions_Response) String() string { func (*GetFunctions_Response) ProtoMessage() {} func (x *GetFunctions_Response) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[115] + mi := &file_tfplugin6_proto_msgTypes[118] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6290,7 +6414,7 @@ type CallFunction_Request struct { func (x *CallFunction_Request) Reset() { *x = CallFunction_Request{} - mi := &file_tfplugin6_proto_msgTypes[117] + mi := &file_tfplugin6_proto_msgTypes[120] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6302,7 +6426,7 @@ func (x *CallFunction_Request) String() string { func (*CallFunction_Request) ProtoMessage() {} func (x *CallFunction_Request) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[117] + mi := &file_tfplugin6_proto_msgTypes[120] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6342,7 +6466,7 @@ type CallFunction_Response struct { func (x *CallFunction_Response) Reset() { *x = CallFunction_Response{} - mi := &file_tfplugin6_proto_msgTypes[118] + mi := &file_tfplugin6_proto_msgTypes[121] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6354,7 +6478,7 @@ func (x *CallFunction_Response) String() string { func (*CallFunction_Response) ProtoMessage() {} func (x *CallFunction_Response) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[118] + mi := &file_tfplugin6_proto_msgTypes[121] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6402,7 +6526,7 @@ type ListResource_Request struct { func (x *ListResource_Request) Reset() { *x = ListResource_Request{} - mi := &file_tfplugin6_proto_msgTypes[119] + mi := &file_tfplugin6_proto_msgTypes[122] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6414,7 +6538,7 @@ func (x *ListResource_Request) String() string { func (*ListResource_Request) ProtoMessage() {} func (x *ListResource_Request) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[119] + mi := &file_tfplugin6_proto_msgTypes[122] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6474,7 +6598,7 @@ type ListResource_Event struct { func (x *ListResource_Event) Reset() { *x = ListResource_Event{} - mi := &file_tfplugin6_proto_msgTypes[120] + mi := &file_tfplugin6_proto_msgTypes[123] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6486,7 +6610,7 @@ func (x *ListResource_Event) String() string { func (*ListResource_Event) ProtoMessage() {} func (x *ListResource_Event) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[120] + mi := &file_tfplugin6_proto_msgTypes[123] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6542,7 +6666,7 @@ type ValidateListResourceConfig_Request struct { func (x *ValidateListResourceConfig_Request) Reset() { *x = ValidateListResourceConfig_Request{} - mi := &file_tfplugin6_proto_msgTypes[121] + mi := &file_tfplugin6_proto_msgTypes[124] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6554,7 +6678,7 @@ func (x *ValidateListResourceConfig_Request) String() string { func (*ValidateListResourceConfig_Request) ProtoMessage() {} func (x *ValidateListResourceConfig_Request) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[121] + mi := &file_tfplugin6_proto_msgTypes[124] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6607,7 +6731,7 @@ type ValidateListResourceConfig_Response struct { func (x *ValidateListResourceConfig_Response) Reset() { *x = ValidateListResourceConfig_Response{} - mi := &file_tfplugin6_proto_msgTypes[122] + mi := &file_tfplugin6_proto_msgTypes[125] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6619,7 +6743,7 @@ func (x *ValidateListResourceConfig_Response) String() string { func (*ValidateListResourceConfig_Response) ProtoMessage() {} func (x *ValidateListResourceConfig_Response) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[122] + mi := &file_tfplugin6_proto_msgTypes[125] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6635,36 +6759,228 @@ func (*ValidateListResourceConfig_Response) Descriptor() ([]byte, []int) { return file_tfplugin6_proto_rawDescGZIP(), []int{36, 1} } -func (x *ValidateListResourceConfig_Response) GetDiagnostics() []*Diagnostic { +func (x *ValidateListResourceConfig_Response) GetDiagnostics() []*Diagnostic { + if x != nil { + return x.Diagnostics + } + return nil +} + +type ValidateStateStore_Request struct { + state protoimpl.MessageState `protogen:"open.v1"` + TypeName string `protobuf:"bytes,1,opt,name=type_name,json=typeName,proto3" json:"type_name,omitempty"` + Config *DynamicValue `protobuf:"bytes,2,opt,name=config,proto3" json:"config,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ValidateStateStore_Request) Reset() { + *x = ValidateStateStore_Request{} + mi := &file_tfplugin6_proto_msgTypes[126] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ValidateStateStore_Request) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ValidateStateStore_Request) ProtoMessage() {} + +func (x *ValidateStateStore_Request) ProtoReflect() protoreflect.Message { + mi := &file_tfplugin6_proto_msgTypes[126] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ValidateStateStore_Request.ProtoReflect.Descriptor instead. +func (*ValidateStateStore_Request) Descriptor() ([]byte, []int) { + return file_tfplugin6_proto_rawDescGZIP(), []int{37, 0} +} + +func (x *ValidateStateStore_Request) GetTypeName() string { + if x != nil { + return x.TypeName + } + return "" +} + +func (x *ValidateStateStore_Request) GetConfig() *DynamicValue { + if x != nil { + return x.Config + } + return nil +} + +type ValidateStateStore_Response struct { + state protoimpl.MessageState `protogen:"open.v1"` + Diagnostics []*Diagnostic `protobuf:"bytes,1,rep,name=diagnostics,proto3" json:"diagnostics,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ValidateStateStore_Response) Reset() { + *x = ValidateStateStore_Response{} + mi := &file_tfplugin6_proto_msgTypes[127] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ValidateStateStore_Response) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ValidateStateStore_Response) ProtoMessage() {} + +func (x *ValidateStateStore_Response) ProtoReflect() protoreflect.Message { + mi := &file_tfplugin6_proto_msgTypes[127] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ValidateStateStore_Response.ProtoReflect.Descriptor instead. +func (*ValidateStateStore_Response) Descriptor() ([]byte, []int) { + return file_tfplugin6_proto_rawDescGZIP(), []int{37, 1} +} + +func (x *ValidateStateStore_Response) GetDiagnostics() []*Diagnostic { + if x != nil { + return x.Diagnostics + } + return nil +} + +type ConfigureStateStore_Request struct { + state protoimpl.MessageState `protogen:"open.v1"` + TypeName string `protobuf:"bytes,1,opt,name=type_name,json=typeName,proto3" json:"type_name,omitempty"` + Config *DynamicValue `protobuf:"bytes,2,opt,name=config,proto3" json:"config,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConfigureStateStore_Request) Reset() { + *x = ConfigureStateStore_Request{} + mi := &file_tfplugin6_proto_msgTypes[128] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ConfigureStateStore_Request) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConfigureStateStore_Request) ProtoMessage() {} + +func (x *ConfigureStateStore_Request) ProtoReflect() protoreflect.Message { + mi := &file_tfplugin6_proto_msgTypes[128] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConfigureStateStore_Request.ProtoReflect.Descriptor instead. +func (*ConfigureStateStore_Request) Descriptor() ([]byte, []int) { + return file_tfplugin6_proto_rawDescGZIP(), []int{38, 0} +} + +func (x *ConfigureStateStore_Request) GetTypeName() string { + if x != nil { + return x.TypeName + } + return "" +} + +func (x *ConfigureStateStore_Request) GetConfig() *DynamicValue { + if x != nil { + return x.Config + } + return nil +} + +type ConfigureStateStore_Response struct { + state protoimpl.MessageState `protogen:"open.v1"` + Diagnostics []*Diagnostic `protobuf:"bytes,1,rep,name=diagnostics,proto3" json:"diagnostics,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConfigureStateStore_Response) Reset() { + *x = ConfigureStateStore_Response{} + mi := &file_tfplugin6_proto_msgTypes[129] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ConfigureStateStore_Response) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConfigureStateStore_Response) ProtoMessage() {} + +func (x *ConfigureStateStore_Response) ProtoReflect() protoreflect.Message { + mi := &file_tfplugin6_proto_msgTypes[129] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConfigureStateStore_Response.ProtoReflect.Descriptor instead. +func (*ConfigureStateStore_Response) Descriptor() ([]byte, []int) { + return file_tfplugin6_proto_rawDescGZIP(), []int{38, 1} +} + +func (x *ConfigureStateStore_Response) GetDiagnostics() []*Diagnostic { if x != nil { return x.Diagnostics } return nil } -type ValidateStateStore_Request struct { +type ReadStateBytes_Request struct { state protoimpl.MessageState `protogen:"open.v1"` TypeName string `protobuf:"bytes,1,opt,name=type_name,json=typeName,proto3" json:"type_name,omitempty"` - Config *DynamicValue `protobuf:"bytes,2,opt,name=config,proto3" json:"config,omitempty"` + StateId string `protobuf:"bytes,2,opt,name=state_id,json=stateId,proto3" json:"state_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ValidateStateStore_Request) Reset() { - *x = ValidateStateStore_Request{} - mi := &file_tfplugin6_proto_msgTypes[123] +func (x *ReadStateBytes_Request) Reset() { + *x = ReadStateBytes_Request{} + mi := &file_tfplugin6_proto_msgTypes[130] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ValidateStateStore_Request) String() string { +func (x *ReadStateBytes_Request) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ValidateStateStore_Request) ProtoMessage() {} +func (*ReadStateBytes_Request) ProtoMessage() {} -func (x *ValidateStateStore_Request) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[123] +func (x *ReadStateBytes_Request) ProtoReflect() protoreflect.Message { + mi := &file_tfplugin6_proto_msgTypes[130] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6675,47 +6991,50 @@ func (x *ValidateStateStore_Request) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ValidateStateStore_Request.ProtoReflect.Descriptor instead. -func (*ValidateStateStore_Request) Descriptor() ([]byte, []int) { - return file_tfplugin6_proto_rawDescGZIP(), []int{37, 0} +// Deprecated: Use ReadStateBytes_Request.ProtoReflect.Descriptor instead. +func (*ReadStateBytes_Request) Descriptor() ([]byte, []int) { + return file_tfplugin6_proto_rawDescGZIP(), []int{39, 0} } -func (x *ValidateStateStore_Request) GetTypeName() string { +func (x *ReadStateBytes_Request) GetTypeName() string { if x != nil { return x.TypeName } return "" } -func (x *ValidateStateStore_Request) GetConfig() *DynamicValue { +func (x *ReadStateBytes_Request) GetStateId() string { if x != nil { - return x.Config + return x.StateId } - return nil + return "" } -type ValidateStateStore_Response struct { +type ReadStateBytes_ResponseChunk struct { state protoimpl.MessageState `protogen:"open.v1"` - Diagnostics []*Diagnostic `protobuf:"bytes,1,rep,name=diagnostics,proto3" json:"diagnostics,omitempty"` + Bytes []byte `protobuf:"bytes,1,opt,name=bytes,proto3" json:"bytes,omitempty"` + TotalLength int64 `protobuf:"varint,2,opt,name=total_length,json=totalLength,proto3" json:"total_length,omitempty"` + Range *StateRange `protobuf:"bytes,3,opt,name=range,proto3" json:"range,omitempty"` + Diagnostics []*Diagnostic `protobuf:"bytes,4,rep,name=diagnostics,proto3" json:"diagnostics,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ValidateStateStore_Response) Reset() { - *x = ValidateStateStore_Response{} - mi := &file_tfplugin6_proto_msgTypes[124] +func (x *ReadStateBytes_ResponseChunk) Reset() { + *x = ReadStateBytes_ResponseChunk{} + mi := &file_tfplugin6_proto_msgTypes[131] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ValidateStateStore_Response) String() string { +func (x *ReadStateBytes_ResponseChunk) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ValidateStateStore_Response) ProtoMessage() {} +func (*ReadStateBytes_ResponseChunk) ProtoMessage() {} -func (x *ValidateStateStore_Response) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[124] +func (x *ReadStateBytes_ResponseChunk) ProtoReflect() protoreflect.Message { + mi := &file_tfplugin6_proto_msgTypes[131] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6726,41 +7045,65 @@ func (x *ValidateStateStore_Response) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ValidateStateStore_Response.ProtoReflect.Descriptor instead. -func (*ValidateStateStore_Response) Descriptor() ([]byte, []int) { - return file_tfplugin6_proto_rawDescGZIP(), []int{37, 1} +// Deprecated: Use ReadStateBytes_ResponseChunk.ProtoReflect.Descriptor instead. +func (*ReadStateBytes_ResponseChunk) Descriptor() ([]byte, []int) { + return file_tfplugin6_proto_rawDescGZIP(), []int{39, 1} } -func (x *ValidateStateStore_Response) GetDiagnostics() []*Diagnostic { +func (x *ReadStateBytes_ResponseChunk) GetBytes() []byte { + if x != nil { + return x.Bytes + } + return nil +} + +func (x *ReadStateBytes_ResponseChunk) GetTotalLength() int64 { + if x != nil { + return x.TotalLength + } + return 0 +} + +func (x *ReadStateBytes_ResponseChunk) GetRange() *StateRange { + if x != nil { + return x.Range + } + return nil +} + +func (x *ReadStateBytes_ResponseChunk) GetDiagnostics() []*Diagnostic { if x != nil { return x.Diagnostics } return nil } -type ConfigureStateStore_Request struct { +type WriteStateBytes_RequestChunk struct { state protoimpl.MessageState `protogen:"open.v1"` TypeName string `protobuf:"bytes,1,opt,name=type_name,json=typeName,proto3" json:"type_name,omitempty"` - Config *DynamicValue `protobuf:"bytes,2,opt,name=config,proto3" json:"config,omitempty"` + Bytes []byte `protobuf:"bytes,2,opt,name=bytes,proto3" json:"bytes,omitempty"` + StateId string `protobuf:"bytes,3,opt,name=state_id,json=stateId,proto3" json:"state_id,omitempty"` + TotalLength int64 `protobuf:"varint,4,opt,name=total_length,json=totalLength,proto3" json:"total_length,omitempty"` + Range *StateRange `protobuf:"bytes,5,opt,name=range,proto3" json:"range,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ConfigureStateStore_Request) Reset() { - *x = ConfigureStateStore_Request{} - mi := &file_tfplugin6_proto_msgTypes[125] +func (x *WriteStateBytes_RequestChunk) Reset() { + *x = WriteStateBytes_RequestChunk{} + mi := &file_tfplugin6_proto_msgTypes[132] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ConfigureStateStore_Request) String() string { +func (x *WriteStateBytes_RequestChunk) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ConfigureStateStore_Request) ProtoMessage() {} +func (*WriteStateBytes_RequestChunk) ProtoMessage() {} -func (x *ConfigureStateStore_Request) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[125] +func (x *WriteStateBytes_RequestChunk) ProtoReflect() protoreflect.Message { + mi := &file_tfplugin6_proto_msgTypes[132] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6771,47 +7114,68 @@ func (x *ConfigureStateStore_Request) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ConfigureStateStore_Request.ProtoReflect.Descriptor instead. -func (*ConfigureStateStore_Request) Descriptor() ([]byte, []int) { - return file_tfplugin6_proto_rawDescGZIP(), []int{38, 0} +// Deprecated: Use WriteStateBytes_RequestChunk.ProtoReflect.Descriptor instead. +func (*WriteStateBytes_RequestChunk) Descriptor() ([]byte, []int) { + return file_tfplugin6_proto_rawDescGZIP(), []int{40, 0} } -func (x *ConfigureStateStore_Request) GetTypeName() string { +func (x *WriteStateBytes_RequestChunk) GetTypeName() string { if x != nil { return x.TypeName } return "" } -func (x *ConfigureStateStore_Request) GetConfig() *DynamicValue { +func (x *WriteStateBytes_RequestChunk) GetBytes() []byte { if x != nil { - return x.Config + return x.Bytes } return nil } -type ConfigureStateStore_Response struct { +func (x *WriteStateBytes_RequestChunk) GetStateId() string { + if x != nil { + return x.StateId + } + return "" +} + +func (x *WriteStateBytes_RequestChunk) GetTotalLength() int64 { + if x != nil { + return x.TotalLength + } + return 0 +} + +func (x *WriteStateBytes_RequestChunk) GetRange() *StateRange { + if x != nil { + return x.Range + } + return nil +} + +type WriteStateBytes_Response struct { state protoimpl.MessageState `protogen:"open.v1"` Diagnostics []*Diagnostic `protobuf:"bytes,1,rep,name=diagnostics,proto3" json:"diagnostics,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *ConfigureStateStore_Response) Reset() { - *x = ConfigureStateStore_Response{} - mi := &file_tfplugin6_proto_msgTypes[126] +func (x *WriteStateBytes_Response) Reset() { + *x = WriteStateBytes_Response{} + mi := &file_tfplugin6_proto_msgTypes[133] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ConfigureStateStore_Response) String() string { +func (x *WriteStateBytes_Response) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ConfigureStateStore_Response) ProtoMessage() {} +func (*WriteStateBytes_Response) ProtoMessage() {} -func (x *ConfigureStateStore_Response) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[126] +func (x *WriteStateBytes_Response) ProtoReflect() protoreflect.Message { + mi := &file_tfplugin6_proto_msgTypes[133] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6822,12 +7186,12 @@ func (x *ConfigureStateStore_Response) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ConfigureStateStore_Response.ProtoReflect.Descriptor instead. -func (*ConfigureStateStore_Response) Descriptor() ([]byte, []int) { - return file_tfplugin6_proto_rawDescGZIP(), []int{38, 1} +// Deprecated: Use WriteStateBytes_Response.ProtoReflect.Descriptor instead. +func (*WriteStateBytes_Response) Descriptor() ([]byte, []int) { + return file_tfplugin6_proto_rawDescGZIP(), []int{40, 1} } -func (x *ConfigureStateStore_Response) GetDiagnostics() []*Diagnostic { +func (x *WriteStateBytes_Response) GetDiagnostics() []*Diagnostic { if x != nil { return x.Diagnostics } @@ -6843,7 +7207,7 @@ type GetStates_Request struct { func (x *GetStates_Request) Reset() { *x = GetStates_Request{} - mi := &file_tfplugin6_proto_msgTypes[127] + mi := &file_tfplugin6_proto_msgTypes[134] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6855,7 +7219,7 @@ func (x *GetStates_Request) String() string { func (*GetStates_Request) ProtoMessage() {} func (x *GetStates_Request) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[127] + mi := &file_tfplugin6_proto_msgTypes[134] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6868,7 +7232,7 @@ func (x *GetStates_Request) ProtoReflect() protoreflect.Message { // Deprecated: Use GetStates_Request.ProtoReflect.Descriptor instead. func (*GetStates_Request) Descriptor() ([]byte, []int) { - return file_tfplugin6_proto_rawDescGZIP(), []int{39, 0} + return file_tfplugin6_proto_rawDescGZIP(), []int{42, 0} } func (x *GetStates_Request) GetTypeName() string { @@ -6888,7 +7252,7 @@ type GetStates_Response struct { func (x *GetStates_Response) Reset() { *x = GetStates_Response{} - mi := &file_tfplugin6_proto_msgTypes[128] + mi := &file_tfplugin6_proto_msgTypes[135] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6900,7 +7264,7 @@ func (x *GetStates_Response) String() string { func (*GetStates_Response) ProtoMessage() {} func (x *GetStates_Response) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[128] + mi := &file_tfplugin6_proto_msgTypes[135] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6913,7 +7277,7 @@ func (x *GetStates_Response) ProtoReflect() protoreflect.Message { // Deprecated: Use GetStates_Response.ProtoReflect.Descriptor instead. func (*GetStates_Response) Descriptor() ([]byte, []int) { - return file_tfplugin6_proto_rawDescGZIP(), []int{39, 1} + return file_tfplugin6_proto_rawDescGZIP(), []int{42, 1} } func (x *GetStates_Response) GetStateId() []string { @@ -6940,7 +7304,7 @@ type DeleteState_Request struct { func (x *DeleteState_Request) Reset() { *x = DeleteState_Request{} - mi := &file_tfplugin6_proto_msgTypes[129] + mi := &file_tfplugin6_proto_msgTypes[136] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6952,7 +7316,7 @@ func (x *DeleteState_Request) String() string { func (*DeleteState_Request) ProtoMessage() {} func (x *DeleteState_Request) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[129] + mi := &file_tfplugin6_proto_msgTypes[136] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6965,7 +7329,7 @@ func (x *DeleteState_Request) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteState_Request.ProtoReflect.Descriptor instead. func (*DeleteState_Request) Descriptor() ([]byte, []int) { - return file_tfplugin6_proto_rawDescGZIP(), []int{40, 0} + return file_tfplugin6_proto_rawDescGZIP(), []int{43, 0} } func (x *DeleteState_Request) GetTypeName() string { @@ -6991,7 +7355,7 @@ type DeleteState_Response struct { func (x *DeleteState_Response) Reset() { *x = DeleteState_Response{} - mi := &file_tfplugin6_proto_msgTypes[130] + mi := &file_tfplugin6_proto_msgTypes[137] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7003,7 +7367,7 @@ func (x *DeleteState_Response) String() string { func (*DeleteState_Response) ProtoMessage() {} func (x *DeleteState_Response) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[130] + mi := &file_tfplugin6_proto_msgTypes[137] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7016,7 +7380,7 @@ func (x *DeleteState_Response) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteState_Response.ProtoReflect.Descriptor instead. func (*DeleteState_Response) Descriptor() ([]byte, []int) { - return file_tfplugin6_proto_rawDescGZIP(), []int{40, 1} + return file_tfplugin6_proto_rawDescGZIP(), []int{43, 1} } func (x *DeleteState_Response) GetDiagnostics() []*Diagnostic { @@ -7041,7 +7405,7 @@ type PlanAction_Request struct { func (x *PlanAction_Request) Reset() { *x = PlanAction_Request{} - mi := &file_tfplugin6_proto_msgTypes[131] + mi := &file_tfplugin6_proto_msgTypes[138] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7053,7 +7417,7 @@ func (x *PlanAction_Request) String() string { func (*PlanAction_Request) ProtoMessage() {} func (x *PlanAction_Request) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[131] + mi := &file_tfplugin6_proto_msgTypes[138] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7066,7 +7430,7 @@ func (x *PlanAction_Request) ProtoReflect() protoreflect.Message { // Deprecated: Use PlanAction_Request.ProtoReflect.Descriptor instead. func (*PlanAction_Request) Descriptor() ([]byte, []int) { - return file_tfplugin6_proto_rawDescGZIP(), []int{41, 0} + return file_tfplugin6_proto_rawDescGZIP(), []int{44, 0} } func (x *PlanAction_Request) GetActionType() string { @@ -7110,7 +7474,7 @@ type PlanAction_Response struct { func (x *PlanAction_Response) Reset() { *x = PlanAction_Response{} - mi := &file_tfplugin6_proto_msgTypes[132] + mi := &file_tfplugin6_proto_msgTypes[139] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7122,7 +7486,7 @@ func (x *PlanAction_Response) String() string { func (*PlanAction_Response) ProtoMessage() {} func (x *PlanAction_Response) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[132] + mi := &file_tfplugin6_proto_msgTypes[139] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7135,7 +7499,7 @@ func (x *PlanAction_Response) ProtoReflect() protoreflect.Message { // Deprecated: Use PlanAction_Response.ProtoReflect.Descriptor instead. func (*PlanAction_Response) Descriptor() ([]byte, []int) { - return file_tfplugin6_proto_rawDescGZIP(), []int{41, 1} + return file_tfplugin6_proto_rawDescGZIP(), []int{44, 1} } func (x *PlanAction_Response) GetLinkedResources() []*PlanAction_Response_LinkedResource { @@ -7174,7 +7538,7 @@ type PlanAction_Request_LinkedResource struct { func (x *PlanAction_Request_LinkedResource) Reset() { *x = PlanAction_Request_LinkedResource{} - mi := &file_tfplugin6_proto_msgTypes[133] + mi := &file_tfplugin6_proto_msgTypes[140] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7186,7 +7550,7 @@ func (x *PlanAction_Request_LinkedResource) String() string { func (*PlanAction_Request_LinkedResource) ProtoMessage() {} func (x *PlanAction_Request_LinkedResource) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[133] + mi := &file_tfplugin6_proto_msgTypes[140] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7199,7 +7563,7 @@ func (x *PlanAction_Request_LinkedResource) ProtoReflect() protoreflect.Message // Deprecated: Use PlanAction_Request_LinkedResource.ProtoReflect.Descriptor instead. func (*PlanAction_Request_LinkedResource) Descriptor() ([]byte, []int) { - return file_tfplugin6_proto_rawDescGZIP(), []int{41, 0, 0} + return file_tfplugin6_proto_rawDescGZIP(), []int{44, 0, 0} } func (x *PlanAction_Request_LinkedResource) GetPriorState() *DynamicValue { @@ -7240,7 +7604,7 @@ type PlanAction_Response_LinkedResource struct { func (x *PlanAction_Response_LinkedResource) Reset() { *x = PlanAction_Response_LinkedResource{} - mi := &file_tfplugin6_proto_msgTypes[134] + mi := &file_tfplugin6_proto_msgTypes[141] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7252,7 +7616,7 @@ func (x *PlanAction_Response_LinkedResource) String() string { func (*PlanAction_Response_LinkedResource) ProtoMessage() {} func (x *PlanAction_Response_LinkedResource) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[134] + mi := &file_tfplugin6_proto_msgTypes[141] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7265,7 +7629,7 @@ func (x *PlanAction_Response_LinkedResource) ProtoReflect() protoreflect.Message // Deprecated: Use PlanAction_Response_LinkedResource.ProtoReflect.Descriptor instead. func (*PlanAction_Response_LinkedResource) Descriptor() ([]byte, []int) { - return file_tfplugin6_proto_rawDescGZIP(), []int{41, 1, 0} + return file_tfplugin6_proto_rawDescGZIP(), []int{44, 1, 0} } func (x *PlanAction_Response_LinkedResource) GetPlannedState() *DynamicValue { @@ -7297,7 +7661,7 @@ type InvokeAction_Request struct { func (x *InvokeAction_Request) Reset() { *x = InvokeAction_Request{} - mi := &file_tfplugin6_proto_msgTypes[135] + mi := &file_tfplugin6_proto_msgTypes[142] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7309,7 +7673,7 @@ func (x *InvokeAction_Request) String() string { func (*InvokeAction_Request) ProtoMessage() {} func (x *InvokeAction_Request) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[135] + mi := &file_tfplugin6_proto_msgTypes[142] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7322,7 +7686,7 @@ func (x *InvokeAction_Request) ProtoReflect() protoreflect.Message { // Deprecated: Use InvokeAction_Request.ProtoReflect.Descriptor instead. func (*InvokeAction_Request) Descriptor() ([]byte, []int) { - return file_tfplugin6_proto_rawDescGZIP(), []int{42, 0} + return file_tfplugin6_proto_rawDescGZIP(), []int{45, 0} } func (x *InvokeAction_Request) GetActionType() string { @@ -7366,7 +7730,7 @@ type InvokeAction_Event struct { func (x *InvokeAction_Event) Reset() { *x = InvokeAction_Event{} - mi := &file_tfplugin6_proto_msgTypes[136] + mi := &file_tfplugin6_proto_msgTypes[143] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7378,7 +7742,7 @@ func (x *InvokeAction_Event) String() string { func (*InvokeAction_Event) ProtoMessage() {} func (x *InvokeAction_Event) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[136] + mi := &file_tfplugin6_proto_msgTypes[143] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7391,7 +7755,7 @@ func (x *InvokeAction_Event) ProtoReflect() protoreflect.Message { // Deprecated: Use InvokeAction_Event.ProtoReflect.Descriptor instead. func (*InvokeAction_Event) Descriptor() ([]byte, []int) { - return file_tfplugin6_proto_rawDescGZIP(), []int{42, 1} + return file_tfplugin6_proto_rawDescGZIP(), []int{45, 1} } func (x *InvokeAction_Event) GetType() isInvokeAction_Event_Type { @@ -7449,7 +7813,7 @@ type InvokeAction_Request_LinkedResource struct { func (x *InvokeAction_Request_LinkedResource) Reset() { *x = InvokeAction_Request_LinkedResource{} - mi := &file_tfplugin6_proto_msgTypes[137] + mi := &file_tfplugin6_proto_msgTypes[144] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7461,7 +7825,7 @@ func (x *InvokeAction_Request_LinkedResource) String() string { func (*InvokeAction_Request_LinkedResource) ProtoMessage() {} func (x *InvokeAction_Request_LinkedResource) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[137] + mi := &file_tfplugin6_proto_msgTypes[144] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7474,7 +7838,7 @@ func (x *InvokeAction_Request_LinkedResource) ProtoReflect() protoreflect.Messag // Deprecated: Use InvokeAction_Request_LinkedResource.ProtoReflect.Descriptor instead. func (*InvokeAction_Request_LinkedResource) Descriptor() ([]byte, []int) { - return file_tfplugin6_proto_rawDescGZIP(), []int{42, 0, 0} + return file_tfplugin6_proto_rawDescGZIP(), []int{45, 0, 0} } func (x *InvokeAction_Request_LinkedResource) GetPriorState() *DynamicValue { @@ -7515,7 +7879,7 @@ type InvokeAction_Event_Progress struct { func (x *InvokeAction_Event_Progress) Reset() { *x = InvokeAction_Event_Progress{} - mi := &file_tfplugin6_proto_msgTypes[138] + mi := &file_tfplugin6_proto_msgTypes[145] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7527,7 +7891,7 @@ func (x *InvokeAction_Event_Progress) String() string { func (*InvokeAction_Event_Progress) ProtoMessage() {} func (x *InvokeAction_Event_Progress) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[138] + mi := &file_tfplugin6_proto_msgTypes[145] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7540,7 +7904,7 @@ func (x *InvokeAction_Event_Progress) ProtoReflect() protoreflect.Message { // Deprecated: Use InvokeAction_Event_Progress.ProtoReflect.Descriptor instead. func (*InvokeAction_Event_Progress) Descriptor() ([]byte, []int) { - return file_tfplugin6_proto_rawDescGZIP(), []int{42, 1, 0} + return file_tfplugin6_proto_rawDescGZIP(), []int{45, 1, 0} } func (x *InvokeAction_Event_Progress) GetMessage() string { @@ -7561,7 +7925,7 @@ type InvokeAction_Event_Completed struct { func (x *InvokeAction_Event_Completed) Reset() { *x = InvokeAction_Event_Completed{} - mi := &file_tfplugin6_proto_msgTypes[139] + mi := &file_tfplugin6_proto_msgTypes[146] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7573,7 +7937,7 @@ func (x *InvokeAction_Event_Completed) String() string { func (*InvokeAction_Event_Completed) ProtoMessage() {} func (x *InvokeAction_Event_Completed) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[139] + mi := &file_tfplugin6_proto_msgTypes[146] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7586,7 +7950,7 @@ func (x *InvokeAction_Event_Completed) ProtoReflect() protoreflect.Message { // Deprecated: Use InvokeAction_Event_Completed.ProtoReflect.Descriptor instead. func (*InvokeAction_Event_Completed) Descriptor() ([]byte, []int) { - return file_tfplugin6_proto_rawDescGZIP(), []int{42, 1, 1} + return file_tfplugin6_proto_rawDescGZIP(), []int{45, 1, 1} } func (x *InvokeAction_Event_Completed) GetLinkedResources() []*InvokeAction_Event_Completed_LinkedResource { @@ -7615,7 +7979,7 @@ type InvokeAction_Event_Completed_LinkedResource struct { func (x *InvokeAction_Event_Completed_LinkedResource) Reset() { *x = InvokeAction_Event_Completed_LinkedResource{} - mi := &file_tfplugin6_proto_msgTypes[140] + mi := &file_tfplugin6_proto_msgTypes[147] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7627,7 +7991,7 @@ func (x *InvokeAction_Event_Completed_LinkedResource) String() string { func (*InvokeAction_Event_Completed_LinkedResource) ProtoMessage() {} func (x *InvokeAction_Event_Completed_LinkedResource) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[140] + mi := &file_tfplugin6_proto_msgTypes[147] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7640,7 +8004,7 @@ func (x *InvokeAction_Event_Completed_LinkedResource) ProtoReflect() protoreflec // Deprecated: Use InvokeAction_Event_Completed_LinkedResource.ProtoReflect.Descriptor instead. func (*InvokeAction_Event_Completed_LinkedResource) Descriptor() ([]byte, []int) { - return file_tfplugin6_proto_rawDescGZIP(), []int{42, 1, 1, 0} + return file_tfplugin6_proto_rawDescGZIP(), []int{45, 1, 1, 0} } func (x *InvokeAction_Event_Completed_LinkedResource) GetNewState() *DynamicValue { @@ -7675,7 +8039,7 @@ type ValidateActionConfig_Request struct { func (x *ValidateActionConfig_Request) Reset() { *x = ValidateActionConfig_Request{} - mi := &file_tfplugin6_proto_msgTypes[141] + mi := &file_tfplugin6_proto_msgTypes[148] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7687,7 +8051,7 @@ func (x *ValidateActionConfig_Request) String() string { func (*ValidateActionConfig_Request) ProtoMessage() {} func (x *ValidateActionConfig_Request) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[141] + mi := &file_tfplugin6_proto_msgTypes[148] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7700,7 +8064,7 @@ func (x *ValidateActionConfig_Request) ProtoReflect() protoreflect.Message { // Deprecated: Use ValidateActionConfig_Request.ProtoReflect.Descriptor instead. func (*ValidateActionConfig_Request) Descriptor() ([]byte, []int) { - return file_tfplugin6_proto_rawDescGZIP(), []int{43, 0} + return file_tfplugin6_proto_rawDescGZIP(), []int{46, 0} } func (x *ValidateActionConfig_Request) GetTypeName() string { @@ -7733,7 +8097,7 @@ type ValidateActionConfig_Response struct { func (x *ValidateActionConfig_Response) Reset() { *x = ValidateActionConfig_Response{} - mi := &file_tfplugin6_proto_msgTypes[142] + mi := &file_tfplugin6_proto_msgTypes[149] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7745,7 +8109,7 @@ func (x *ValidateActionConfig_Response) String() string { func (*ValidateActionConfig_Response) ProtoMessage() {} func (x *ValidateActionConfig_Response) ProtoReflect() protoreflect.Message { - mi := &file_tfplugin6_proto_msgTypes[142] + mi := &file_tfplugin6_proto_msgTypes[149] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7758,7 +8122,7 @@ func (x *ValidateActionConfig_Response) ProtoReflect() protoreflect.Message { // Deprecated: Use ValidateActionConfig_Response.ProtoReflect.Descriptor instead. func (*ValidateActionConfig_Response) Descriptor() ([]byte, []int) { - return file_tfplugin6_proto_rawDescGZIP(), []int{43, 1} + return file_tfplugin6_proto_rawDescGZIP(), []int{46, 1} } func (x *ValidateActionConfig_Response) GetDiagnostics() []*Diagnostic { @@ -8223,7 +8587,29 @@ const file_tfplugin6_proto_rawDesc = "" + "\ttype_name\x18\x01 \x01(\tR\btypeName\x12/\n" + "\x06config\x18\x02 \x01(\v2\x17.tfplugin6.DynamicValueR\x06config\x1aC\n" + "\bResponse\x127\n" + - "\vdiagnostics\x18\x01 \x03(\v2\x15.tfplugin6.DiagnosticR\vdiagnostics\"\x93\x01\n" + + "\vdiagnostics\x18\x01 \x03(\v2\x15.tfplugin6.DiagnosticR\vdiagnostics\"\x84\x02\n" + + "\x0eReadStateBytes\x1aA\n" + + "\aRequest\x12\x1b\n" + + "\ttype_name\x18\x01 \x01(\tR\btypeName\x12\x19\n" + + "\bstate_id\x18\x02 \x01(\tR\astateId\x1a\xae\x01\n" + + "\rResponseChunk\x12\x14\n" + + "\x05bytes\x18\x01 \x01(\fR\x05bytes\x12!\n" + + "\ftotal_length\x18\x02 \x01(\x03R\vtotalLength\x12+\n" + + "\x05range\x18\x03 \x01(\v2\x15.tfplugin6.StateRangeR\x05range\x127\n" + + "\vdiagnostics\x18\x04 \x03(\v2\x15.tfplugin6.DiagnosticR\vdiagnostics\"\x85\x02\n" + + "\x0fWriteStateBytes\x1a\xac\x01\n" + + "\fRequestChunk\x12\x1b\n" + + "\ttype_name\x18\x01 \x01(\tR\btypeName\x12\x14\n" + + "\x05bytes\x18\x02 \x01(\fR\x05bytes\x12\x19\n" + + "\bstate_id\x18\x03 \x01(\tR\astateId\x12!\n" + + "\ftotal_length\x18\x04 \x01(\x03R\vtotalLength\x12+\n" + + "\x05range\x18\x05 \x01(\v2\x15.tfplugin6.StateRangeR\x05range\x1aC\n" + + "\bResponse\x127\n" + + "\vdiagnostics\x18\x01 \x03(\v2\x15.tfplugin6.DiagnosticR\vdiagnostics\"4\n" + + "\n" + + "StateRange\x12\x14\n" + + "\x05start\x18\x01 \x01(\x03R\x05start\x12\x10\n" + + "\x03end\x18\x02 \x01(\x03R\x03end\"\x93\x01\n" + "\tGetStates\x1a&\n" + "\aRequest\x12\x1b\n" + "\ttype_name\x18\x01 \x01(\tR\btypeName\x1a^\n" + @@ -8296,7 +8682,7 @@ const file_tfplugin6_proto_rawDesc = "" + "\n" + "StringKind\x12\t\n" + "\x05PLAIN\x10\x00\x12\f\n" + - "\bMARKDOWN\x10\x012\xb8\x18\n" + + "\bMARKDOWN\x10\x012\xfb\x19\n" + "\bProvider\x12N\n" + "\vGetMetadata\x12\x1e.tfplugin6.GetMetadata.Request\x1a\x1f.tfplugin6.GetMetadata.Response\x12`\n" + "\x11GetProviderSchema\x12$.tfplugin6.GetProviderSchema.Request\x1a%.tfplugin6.GetProviderSchema.Response\x12o\n" + @@ -8322,7 +8708,9 @@ const file_tfplugin6_proto_rawDesc = "" + "\fGetFunctions\x12\x1f.tfplugin6.GetFunctions.Request\x1a .tfplugin6.GetFunctions.Response\x12Q\n" + "\fCallFunction\x12\x1f.tfplugin6.CallFunction.Request\x1a .tfplugin6.CallFunction.Response\x12i\n" + "\x18ValidateStateStoreConfig\x12%.tfplugin6.ValidateStateStore.Request\x1a&.tfplugin6.ValidateStateStore.Response\x12f\n" + - "\x13ConfigureStateStore\x12&.tfplugin6.ConfigureStateStore.Request\x1a'.tfplugin6.ConfigureStateStore.Response\x12H\n" + + "\x13ConfigureStateStore\x12&.tfplugin6.ConfigureStateStore.Request\x1a'.tfplugin6.ConfigureStateStore.Response\x12^\n" + + "\x0eReadStateBytes\x12!.tfplugin6.ReadStateBytes.Request\x1a'.tfplugin6.ReadStateBytes.ResponseChunk0\x01\x12a\n" + + "\x0fWriteStateBytes\x12'.tfplugin6.WriteStateBytes.RequestChunk\x1a#.tfplugin6.WriteStateBytes.Response(\x01\x12H\n" + "\tGetStates\x12\x1c.tfplugin6.GetStates.Request\x1a\x1d.tfplugin6.GetStates.Response\x12N\n" + "\vDeleteState\x12\x1e.tfplugin6.DeleteState.Request\x1a\x1f.tfplugin6.DeleteState.Response\x12K\n" + "\n" + @@ -8344,7 +8732,7 @@ func file_tfplugin6_proto_rawDescGZIP() []byte { } var file_tfplugin6_proto_enumTypes = make([]protoimpl.EnumInfo, 6) -var file_tfplugin6_proto_msgTypes = make([]protoimpl.MessageInfo, 143) +var file_tfplugin6_proto_msgTypes = make([]protoimpl.MessageInfo, 150) var file_tfplugin6_proto_goTypes = []any{ (StringKind)(0), // 0: tfplugin6.StringKind (Diagnostic_Severity)(0), // 1: tfplugin6.Diagnostic.Severity @@ -8391,160 +8779,167 @@ var file_tfplugin6_proto_goTypes = []any{ (*ValidateListResourceConfig)(nil), // 42: tfplugin6.ValidateListResourceConfig (*ValidateStateStore)(nil), // 43: tfplugin6.ValidateStateStore (*ConfigureStateStore)(nil), // 44: tfplugin6.ConfigureStateStore - (*GetStates)(nil), // 45: tfplugin6.GetStates - (*DeleteState)(nil), // 46: tfplugin6.DeleteState - (*PlanAction)(nil), // 47: tfplugin6.PlanAction - (*InvokeAction)(nil), // 48: tfplugin6.InvokeAction - (*ValidateActionConfig)(nil), // 49: tfplugin6.ValidateActionConfig - (*LinkedResourceConfig)(nil), // 50: tfplugin6.LinkedResourceConfig - (*AttributePath_Step)(nil), // 51: tfplugin6.AttributePath.Step - (*StopProvider_Request)(nil), // 52: tfplugin6.StopProvider.Request - (*StopProvider_Response)(nil), // 53: tfplugin6.StopProvider.Response - nil, // 54: tfplugin6.RawState.FlatmapEntry - (*ResourceIdentitySchema_IdentityAttribute)(nil), // 55: tfplugin6.ResourceIdentitySchema.IdentityAttribute - (*ActionSchema_LinkedResource)(nil), // 56: tfplugin6.ActionSchema.LinkedResource - (*ActionSchema_Unlinked)(nil), // 57: tfplugin6.ActionSchema.Unlinked - (*ActionSchema_Lifecycle)(nil), // 58: tfplugin6.ActionSchema.Lifecycle - (*ActionSchema_Linked)(nil), // 59: tfplugin6.ActionSchema.Linked - (*Schema_Block)(nil), // 60: tfplugin6.Schema.Block - (*Schema_Attribute)(nil), // 61: tfplugin6.Schema.Attribute - (*Schema_NestedBlock)(nil), // 62: tfplugin6.Schema.NestedBlock - (*Schema_Object)(nil), // 63: tfplugin6.Schema.Object - (*Function_Parameter)(nil), // 64: tfplugin6.Function.Parameter - (*Function_Return)(nil), // 65: tfplugin6.Function.Return - (*GetMetadata_Request)(nil), // 66: tfplugin6.GetMetadata.Request - (*GetMetadata_Response)(nil), // 67: tfplugin6.GetMetadata.Response - (*GetMetadata_EphemeralMetadata)(nil), // 68: tfplugin6.GetMetadata.EphemeralMetadata - (*GetMetadata_FunctionMetadata)(nil), // 69: tfplugin6.GetMetadata.FunctionMetadata - (*GetMetadata_DataSourceMetadata)(nil), // 70: tfplugin6.GetMetadata.DataSourceMetadata - (*GetMetadata_ResourceMetadata)(nil), // 71: tfplugin6.GetMetadata.ResourceMetadata - (*GetMetadata_ListResourceMetadata)(nil), // 72: tfplugin6.GetMetadata.ListResourceMetadata - (*GetMetadata_StateStoreMetadata)(nil), // 73: tfplugin6.GetMetadata.StateStoreMetadata - (*GetMetadata_ActionMetadata)(nil), // 74: tfplugin6.GetMetadata.ActionMetadata - (*GetProviderSchema_Request)(nil), // 75: tfplugin6.GetProviderSchema.Request - (*GetProviderSchema_Response)(nil), // 76: tfplugin6.GetProviderSchema.Response - nil, // 77: tfplugin6.GetProviderSchema.Response.ResourceSchemasEntry - nil, // 78: tfplugin6.GetProviderSchema.Response.DataSourceSchemasEntry - nil, // 79: tfplugin6.GetProviderSchema.Response.FunctionsEntry - nil, // 80: tfplugin6.GetProviderSchema.Response.EphemeralResourceSchemasEntry - nil, // 81: tfplugin6.GetProviderSchema.Response.ListResourceSchemasEntry - nil, // 82: tfplugin6.GetProviderSchema.Response.StateStoreSchemasEntry - nil, // 83: tfplugin6.GetProviderSchema.Response.ActionSchemasEntry - (*ValidateProviderConfig_Request)(nil), // 84: tfplugin6.ValidateProviderConfig.Request - (*ValidateProviderConfig_Response)(nil), // 85: tfplugin6.ValidateProviderConfig.Response - (*UpgradeResourceState_Request)(nil), // 86: tfplugin6.UpgradeResourceState.Request - (*UpgradeResourceState_Response)(nil), // 87: tfplugin6.UpgradeResourceState.Response - (*GetResourceIdentitySchemas_Request)(nil), // 88: tfplugin6.GetResourceIdentitySchemas.Request - (*GetResourceIdentitySchemas_Response)(nil), // 89: tfplugin6.GetResourceIdentitySchemas.Response - nil, // 90: tfplugin6.GetResourceIdentitySchemas.Response.IdentitySchemasEntry - (*UpgradeResourceIdentity_Request)(nil), // 91: tfplugin6.UpgradeResourceIdentity.Request - (*UpgradeResourceIdentity_Response)(nil), // 92: tfplugin6.UpgradeResourceIdentity.Response - (*ValidateResourceConfig_Request)(nil), // 93: tfplugin6.ValidateResourceConfig.Request - (*ValidateResourceConfig_Response)(nil), // 94: tfplugin6.ValidateResourceConfig.Response - (*ValidateDataResourceConfig_Request)(nil), // 95: tfplugin6.ValidateDataResourceConfig.Request - (*ValidateDataResourceConfig_Response)(nil), // 96: tfplugin6.ValidateDataResourceConfig.Response - (*ValidateEphemeralResourceConfig_Request)(nil), // 97: tfplugin6.ValidateEphemeralResourceConfig.Request - (*ValidateEphemeralResourceConfig_Response)(nil), // 98: tfplugin6.ValidateEphemeralResourceConfig.Response - (*ConfigureProvider_Request)(nil), // 99: tfplugin6.ConfigureProvider.Request - (*ConfigureProvider_Response)(nil), // 100: tfplugin6.ConfigureProvider.Response - (*ReadResource_Request)(nil), // 101: tfplugin6.ReadResource.Request - (*ReadResource_Response)(nil), // 102: tfplugin6.ReadResource.Response - (*PlanResourceChange_Request)(nil), // 103: tfplugin6.PlanResourceChange.Request - (*PlanResourceChange_Response)(nil), // 104: tfplugin6.PlanResourceChange.Response - (*ApplyResourceChange_Request)(nil), // 105: tfplugin6.ApplyResourceChange.Request - (*ApplyResourceChange_Response)(nil), // 106: tfplugin6.ApplyResourceChange.Response - (*ImportResourceState_Request)(nil), // 107: tfplugin6.ImportResourceState.Request - (*ImportResourceState_ImportedResource)(nil), // 108: tfplugin6.ImportResourceState.ImportedResource - (*ImportResourceState_Response)(nil), // 109: tfplugin6.ImportResourceState.Response - (*MoveResourceState_Request)(nil), // 110: tfplugin6.MoveResourceState.Request - (*MoveResourceState_Response)(nil), // 111: tfplugin6.MoveResourceState.Response - (*ReadDataSource_Request)(nil), // 112: tfplugin6.ReadDataSource.Request - (*ReadDataSource_Response)(nil), // 113: tfplugin6.ReadDataSource.Response - (*OpenEphemeralResource_Request)(nil), // 114: tfplugin6.OpenEphemeralResource.Request - (*OpenEphemeralResource_Response)(nil), // 115: tfplugin6.OpenEphemeralResource.Response - (*RenewEphemeralResource_Request)(nil), // 116: tfplugin6.RenewEphemeralResource.Request - (*RenewEphemeralResource_Response)(nil), // 117: tfplugin6.RenewEphemeralResource.Response - (*CloseEphemeralResource_Request)(nil), // 118: tfplugin6.CloseEphemeralResource.Request - (*CloseEphemeralResource_Response)(nil), // 119: tfplugin6.CloseEphemeralResource.Response - (*GetFunctions_Request)(nil), // 120: tfplugin6.GetFunctions.Request - (*GetFunctions_Response)(nil), // 121: tfplugin6.GetFunctions.Response - nil, // 122: tfplugin6.GetFunctions.Response.FunctionsEntry - (*CallFunction_Request)(nil), // 123: tfplugin6.CallFunction.Request - (*CallFunction_Response)(nil), // 124: tfplugin6.CallFunction.Response - (*ListResource_Request)(nil), // 125: tfplugin6.ListResource.Request - (*ListResource_Event)(nil), // 126: tfplugin6.ListResource.Event - (*ValidateListResourceConfig_Request)(nil), // 127: tfplugin6.ValidateListResourceConfig.Request - (*ValidateListResourceConfig_Response)(nil), // 128: tfplugin6.ValidateListResourceConfig.Response - (*ValidateStateStore_Request)(nil), // 129: tfplugin6.ValidateStateStore.Request - (*ValidateStateStore_Response)(nil), // 130: tfplugin6.ValidateStateStore.Response - (*ConfigureStateStore_Request)(nil), // 131: tfplugin6.ConfigureStateStore.Request - (*ConfigureStateStore_Response)(nil), // 132: tfplugin6.ConfigureStateStore.Response - (*GetStates_Request)(nil), // 133: tfplugin6.GetStates.Request - (*GetStates_Response)(nil), // 134: tfplugin6.GetStates.Response - (*DeleteState_Request)(nil), // 135: tfplugin6.DeleteState.Request - (*DeleteState_Response)(nil), // 136: tfplugin6.DeleteState.Response - (*PlanAction_Request)(nil), // 137: tfplugin6.PlanAction.Request - (*PlanAction_Response)(nil), // 138: tfplugin6.PlanAction.Response - (*PlanAction_Request_LinkedResource)(nil), // 139: tfplugin6.PlanAction.Request.LinkedResource - (*PlanAction_Response_LinkedResource)(nil), // 140: tfplugin6.PlanAction.Response.LinkedResource - (*InvokeAction_Request)(nil), // 141: tfplugin6.InvokeAction.Request - (*InvokeAction_Event)(nil), // 142: tfplugin6.InvokeAction.Event - (*InvokeAction_Request_LinkedResource)(nil), // 143: tfplugin6.InvokeAction.Request.LinkedResource - (*InvokeAction_Event_Progress)(nil), // 144: tfplugin6.InvokeAction.Event.Progress - (*InvokeAction_Event_Completed)(nil), // 145: tfplugin6.InvokeAction.Event.Completed - (*InvokeAction_Event_Completed_LinkedResource)(nil), // 146: tfplugin6.InvokeAction.Event.Completed.LinkedResource - (*ValidateActionConfig_Request)(nil), // 147: tfplugin6.ValidateActionConfig.Request - (*ValidateActionConfig_Response)(nil), // 148: tfplugin6.ValidateActionConfig.Response - (*timestamppb.Timestamp)(nil), // 149: google.protobuf.Timestamp + (*ReadStateBytes)(nil), // 45: tfplugin6.ReadStateBytes + (*WriteStateBytes)(nil), // 46: tfplugin6.WriteStateBytes + (*StateRange)(nil), // 47: tfplugin6.StateRange + (*GetStates)(nil), // 48: tfplugin6.GetStates + (*DeleteState)(nil), // 49: tfplugin6.DeleteState + (*PlanAction)(nil), // 50: tfplugin6.PlanAction + (*InvokeAction)(nil), // 51: tfplugin6.InvokeAction + (*ValidateActionConfig)(nil), // 52: tfplugin6.ValidateActionConfig + (*LinkedResourceConfig)(nil), // 53: tfplugin6.LinkedResourceConfig + (*AttributePath_Step)(nil), // 54: tfplugin6.AttributePath.Step + (*StopProvider_Request)(nil), // 55: tfplugin6.StopProvider.Request + (*StopProvider_Response)(nil), // 56: tfplugin6.StopProvider.Response + nil, // 57: tfplugin6.RawState.FlatmapEntry + (*ResourceIdentitySchema_IdentityAttribute)(nil), // 58: tfplugin6.ResourceIdentitySchema.IdentityAttribute + (*ActionSchema_LinkedResource)(nil), // 59: tfplugin6.ActionSchema.LinkedResource + (*ActionSchema_Unlinked)(nil), // 60: tfplugin6.ActionSchema.Unlinked + (*ActionSchema_Lifecycle)(nil), // 61: tfplugin6.ActionSchema.Lifecycle + (*ActionSchema_Linked)(nil), // 62: tfplugin6.ActionSchema.Linked + (*Schema_Block)(nil), // 63: tfplugin6.Schema.Block + (*Schema_Attribute)(nil), // 64: tfplugin6.Schema.Attribute + (*Schema_NestedBlock)(nil), // 65: tfplugin6.Schema.NestedBlock + (*Schema_Object)(nil), // 66: tfplugin6.Schema.Object + (*Function_Parameter)(nil), // 67: tfplugin6.Function.Parameter + (*Function_Return)(nil), // 68: tfplugin6.Function.Return + (*GetMetadata_Request)(nil), // 69: tfplugin6.GetMetadata.Request + (*GetMetadata_Response)(nil), // 70: tfplugin6.GetMetadata.Response + (*GetMetadata_EphemeralMetadata)(nil), // 71: tfplugin6.GetMetadata.EphemeralMetadata + (*GetMetadata_FunctionMetadata)(nil), // 72: tfplugin6.GetMetadata.FunctionMetadata + (*GetMetadata_DataSourceMetadata)(nil), // 73: tfplugin6.GetMetadata.DataSourceMetadata + (*GetMetadata_ResourceMetadata)(nil), // 74: tfplugin6.GetMetadata.ResourceMetadata + (*GetMetadata_ListResourceMetadata)(nil), // 75: tfplugin6.GetMetadata.ListResourceMetadata + (*GetMetadata_StateStoreMetadata)(nil), // 76: tfplugin6.GetMetadata.StateStoreMetadata + (*GetMetadata_ActionMetadata)(nil), // 77: tfplugin6.GetMetadata.ActionMetadata + (*GetProviderSchema_Request)(nil), // 78: tfplugin6.GetProviderSchema.Request + (*GetProviderSchema_Response)(nil), // 79: tfplugin6.GetProviderSchema.Response + nil, // 80: tfplugin6.GetProviderSchema.Response.ResourceSchemasEntry + nil, // 81: tfplugin6.GetProviderSchema.Response.DataSourceSchemasEntry + nil, // 82: tfplugin6.GetProviderSchema.Response.FunctionsEntry + nil, // 83: tfplugin6.GetProviderSchema.Response.EphemeralResourceSchemasEntry + nil, // 84: tfplugin6.GetProviderSchema.Response.ListResourceSchemasEntry + nil, // 85: tfplugin6.GetProviderSchema.Response.StateStoreSchemasEntry + nil, // 86: tfplugin6.GetProviderSchema.Response.ActionSchemasEntry + (*ValidateProviderConfig_Request)(nil), // 87: tfplugin6.ValidateProviderConfig.Request + (*ValidateProviderConfig_Response)(nil), // 88: tfplugin6.ValidateProviderConfig.Response + (*UpgradeResourceState_Request)(nil), // 89: tfplugin6.UpgradeResourceState.Request + (*UpgradeResourceState_Response)(nil), // 90: tfplugin6.UpgradeResourceState.Response + (*GetResourceIdentitySchemas_Request)(nil), // 91: tfplugin6.GetResourceIdentitySchemas.Request + (*GetResourceIdentitySchemas_Response)(nil), // 92: tfplugin6.GetResourceIdentitySchemas.Response + nil, // 93: tfplugin6.GetResourceIdentitySchemas.Response.IdentitySchemasEntry + (*UpgradeResourceIdentity_Request)(nil), // 94: tfplugin6.UpgradeResourceIdentity.Request + (*UpgradeResourceIdentity_Response)(nil), // 95: tfplugin6.UpgradeResourceIdentity.Response + (*ValidateResourceConfig_Request)(nil), // 96: tfplugin6.ValidateResourceConfig.Request + (*ValidateResourceConfig_Response)(nil), // 97: tfplugin6.ValidateResourceConfig.Response + (*ValidateDataResourceConfig_Request)(nil), // 98: tfplugin6.ValidateDataResourceConfig.Request + (*ValidateDataResourceConfig_Response)(nil), // 99: tfplugin6.ValidateDataResourceConfig.Response + (*ValidateEphemeralResourceConfig_Request)(nil), // 100: tfplugin6.ValidateEphemeralResourceConfig.Request + (*ValidateEphemeralResourceConfig_Response)(nil), // 101: tfplugin6.ValidateEphemeralResourceConfig.Response + (*ConfigureProvider_Request)(nil), // 102: tfplugin6.ConfigureProvider.Request + (*ConfigureProvider_Response)(nil), // 103: tfplugin6.ConfigureProvider.Response + (*ReadResource_Request)(nil), // 104: tfplugin6.ReadResource.Request + (*ReadResource_Response)(nil), // 105: tfplugin6.ReadResource.Response + (*PlanResourceChange_Request)(nil), // 106: tfplugin6.PlanResourceChange.Request + (*PlanResourceChange_Response)(nil), // 107: tfplugin6.PlanResourceChange.Response + (*ApplyResourceChange_Request)(nil), // 108: tfplugin6.ApplyResourceChange.Request + (*ApplyResourceChange_Response)(nil), // 109: tfplugin6.ApplyResourceChange.Response + (*ImportResourceState_Request)(nil), // 110: tfplugin6.ImportResourceState.Request + (*ImportResourceState_ImportedResource)(nil), // 111: tfplugin6.ImportResourceState.ImportedResource + (*ImportResourceState_Response)(nil), // 112: tfplugin6.ImportResourceState.Response + (*MoveResourceState_Request)(nil), // 113: tfplugin6.MoveResourceState.Request + (*MoveResourceState_Response)(nil), // 114: tfplugin6.MoveResourceState.Response + (*ReadDataSource_Request)(nil), // 115: tfplugin6.ReadDataSource.Request + (*ReadDataSource_Response)(nil), // 116: tfplugin6.ReadDataSource.Response + (*OpenEphemeralResource_Request)(nil), // 117: tfplugin6.OpenEphemeralResource.Request + (*OpenEphemeralResource_Response)(nil), // 118: tfplugin6.OpenEphemeralResource.Response + (*RenewEphemeralResource_Request)(nil), // 119: tfplugin6.RenewEphemeralResource.Request + (*RenewEphemeralResource_Response)(nil), // 120: tfplugin6.RenewEphemeralResource.Response + (*CloseEphemeralResource_Request)(nil), // 121: tfplugin6.CloseEphemeralResource.Request + (*CloseEphemeralResource_Response)(nil), // 122: tfplugin6.CloseEphemeralResource.Response + (*GetFunctions_Request)(nil), // 123: tfplugin6.GetFunctions.Request + (*GetFunctions_Response)(nil), // 124: tfplugin6.GetFunctions.Response + nil, // 125: tfplugin6.GetFunctions.Response.FunctionsEntry + (*CallFunction_Request)(nil), // 126: tfplugin6.CallFunction.Request + (*CallFunction_Response)(nil), // 127: tfplugin6.CallFunction.Response + (*ListResource_Request)(nil), // 128: tfplugin6.ListResource.Request + (*ListResource_Event)(nil), // 129: tfplugin6.ListResource.Event + (*ValidateListResourceConfig_Request)(nil), // 130: tfplugin6.ValidateListResourceConfig.Request + (*ValidateListResourceConfig_Response)(nil), // 131: tfplugin6.ValidateListResourceConfig.Response + (*ValidateStateStore_Request)(nil), // 132: tfplugin6.ValidateStateStore.Request + (*ValidateStateStore_Response)(nil), // 133: tfplugin6.ValidateStateStore.Response + (*ConfigureStateStore_Request)(nil), // 134: tfplugin6.ConfigureStateStore.Request + (*ConfigureStateStore_Response)(nil), // 135: tfplugin6.ConfigureStateStore.Response + (*ReadStateBytes_Request)(nil), // 136: tfplugin6.ReadStateBytes.Request + (*ReadStateBytes_ResponseChunk)(nil), // 137: tfplugin6.ReadStateBytes.ResponseChunk + (*WriteStateBytes_RequestChunk)(nil), // 138: tfplugin6.WriteStateBytes.RequestChunk + (*WriteStateBytes_Response)(nil), // 139: tfplugin6.WriteStateBytes.Response + (*GetStates_Request)(nil), // 140: tfplugin6.GetStates.Request + (*GetStates_Response)(nil), // 141: tfplugin6.GetStates.Response + (*DeleteState_Request)(nil), // 142: tfplugin6.DeleteState.Request + (*DeleteState_Response)(nil), // 143: tfplugin6.DeleteState.Response + (*PlanAction_Request)(nil), // 144: tfplugin6.PlanAction.Request + (*PlanAction_Response)(nil), // 145: tfplugin6.PlanAction.Response + (*PlanAction_Request_LinkedResource)(nil), // 146: tfplugin6.PlanAction.Request.LinkedResource + (*PlanAction_Response_LinkedResource)(nil), // 147: tfplugin6.PlanAction.Response.LinkedResource + (*InvokeAction_Request)(nil), // 148: tfplugin6.InvokeAction.Request + (*InvokeAction_Event)(nil), // 149: tfplugin6.InvokeAction.Event + (*InvokeAction_Request_LinkedResource)(nil), // 150: tfplugin6.InvokeAction.Request.LinkedResource + (*InvokeAction_Event_Progress)(nil), // 151: tfplugin6.InvokeAction.Event.Progress + (*InvokeAction_Event_Completed)(nil), // 152: tfplugin6.InvokeAction.Event.Completed + (*InvokeAction_Event_Completed_LinkedResource)(nil), // 153: tfplugin6.InvokeAction.Event.Completed.LinkedResource + (*ValidateActionConfig_Request)(nil), // 154: tfplugin6.ValidateActionConfig.Request + (*ValidateActionConfig_Response)(nil), // 155: tfplugin6.ValidateActionConfig.Response + (*timestamppb.Timestamp)(nil), // 156: google.protobuf.Timestamp } var file_tfplugin6_proto_depIdxs = []int32{ 1, // 0: tfplugin6.Diagnostic.severity:type_name -> tfplugin6.Diagnostic.Severity 9, // 1: tfplugin6.Diagnostic.attribute:type_name -> tfplugin6.AttributePath - 51, // 2: tfplugin6.AttributePath.steps:type_name -> tfplugin6.AttributePath.Step - 54, // 3: tfplugin6.RawState.flatmap:type_name -> tfplugin6.RawState.FlatmapEntry - 55, // 4: tfplugin6.ResourceIdentitySchema.identity_attributes:type_name -> tfplugin6.ResourceIdentitySchema.IdentityAttribute + 54, // 2: tfplugin6.AttributePath.steps:type_name -> tfplugin6.AttributePath.Step + 57, // 3: tfplugin6.RawState.flatmap:type_name -> tfplugin6.RawState.FlatmapEntry + 58, // 4: tfplugin6.ResourceIdentitySchema.identity_attributes:type_name -> tfplugin6.ResourceIdentitySchema.IdentityAttribute 6, // 5: tfplugin6.ResourceIdentityData.identity_data:type_name -> tfplugin6.DynamicValue 15, // 6: tfplugin6.ActionSchema.schema:type_name -> tfplugin6.Schema - 57, // 7: tfplugin6.ActionSchema.unlinked:type_name -> tfplugin6.ActionSchema.Unlinked - 58, // 8: tfplugin6.ActionSchema.lifecycle:type_name -> tfplugin6.ActionSchema.Lifecycle - 59, // 9: tfplugin6.ActionSchema.linked:type_name -> tfplugin6.ActionSchema.Linked - 60, // 10: tfplugin6.Schema.block:type_name -> tfplugin6.Schema.Block - 64, // 11: tfplugin6.Function.parameters:type_name -> tfplugin6.Function.Parameter - 64, // 12: tfplugin6.Function.variadic_parameter:type_name -> tfplugin6.Function.Parameter - 65, // 13: tfplugin6.Function.return:type_name -> tfplugin6.Function.Return + 60, // 7: tfplugin6.ActionSchema.unlinked:type_name -> tfplugin6.ActionSchema.Unlinked + 61, // 8: tfplugin6.ActionSchema.lifecycle:type_name -> tfplugin6.ActionSchema.Lifecycle + 62, // 9: tfplugin6.ActionSchema.linked:type_name -> tfplugin6.ActionSchema.Linked + 63, // 10: tfplugin6.Schema.block:type_name -> tfplugin6.Schema.Block + 67, // 11: tfplugin6.Function.parameters:type_name -> tfplugin6.Function.Parameter + 67, // 12: tfplugin6.Function.variadic_parameter:type_name -> tfplugin6.Function.Parameter + 68, // 13: tfplugin6.Function.return:type_name -> tfplugin6.Function.Return 0, // 14: tfplugin6.Function.description_kind:type_name -> tfplugin6.StringKind 5, // 15: tfplugin6.Deferred.reason:type_name -> tfplugin6.Deferred.Reason 6, // 16: tfplugin6.LinkedResourceConfig.config:type_name -> tfplugin6.DynamicValue 2, // 17: tfplugin6.ActionSchema.Lifecycle.executes:type_name -> tfplugin6.ActionSchema.Lifecycle.ExecutionOrder - 56, // 18: tfplugin6.ActionSchema.Lifecycle.linked_resource:type_name -> tfplugin6.ActionSchema.LinkedResource - 56, // 19: tfplugin6.ActionSchema.Linked.linked_resources:type_name -> tfplugin6.ActionSchema.LinkedResource - 61, // 20: tfplugin6.Schema.Block.attributes:type_name -> tfplugin6.Schema.Attribute - 62, // 21: tfplugin6.Schema.Block.block_types:type_name -> tfplugin6.Schema.NestedBlock + 59, // 18: tfplugin6.ActionSchema.Lifecycle.linked_resource:type_name -> tfplugin6.ActionSchema.LinkedResource + 59, // 19: tfplugin6.ActionSchema.Linked.linked_resources:type_name -> tfplugin6.ActionSchema.LinkedResource + 64, // 20: tfplugin6.Schema.Block.attributes:type_name -> tfplugin6.Schema.Attribute + 65, // 21: tfplugin6.Schema.Block.block_types:type_name -> tfplugin6.Schema.NestedBlock 0, // 22: tfplugin6.Schema.Block.description_kind:type_name -> tfplugin6.StringKind - 63, // 23: tfplugin6.Schema.Attribute.nested_type:type_name -> tfplugin6.Schema.Object + 66, // 23: tfplugin6.Schema.Attribute.nested_type:type_name -> tfplugin6.Schema.Object 0, // 24: tfplugin6.Schema.Attribute.description_kind:type_name -> tfplugin6.StringKind - 60, // 25: tfplugin6.Schema.NestedBlock.block:type_name -> tfplugin6.Schema.Block + 63, // 25: tfplugin6.Schema.NestedBlock.block:type_name -> tfplugin6.Schema.Block 3, // 26: tfplugin6.Schema.NestedBlock.nesting:type_name -> tfplugin6.Schema.NestedBlock.NestingMode - 61, // 27: tfplugin6.Schema.Object.attributes:type_name -> tfplugin6.Schema.Attribute + 64, // 27: tfplugin6.Schema.Object.attributes:type_name -> tfplugin6.Schema.Attribute 4, // 28: tfplugin6.Schema.Object.nesting:type_name -> tfplugin6.Schema.Object.NestingMode 0, // 29: tfplugin6.Function.Parameter.description_kind:type_name -> tfplugin6.StringKind 17, // 30: tfplugin6.GetMetadata.Response.server_capabilities:type_name -> tfplugin6.ServerCapabilities 7, // 31: tfplugin6.GetMetadata.Response.diagnostics:type_name -> tfplugin6.Diagnostic - 70, // 32: tfplugin6.GetMetadata.Response.data_sources:type_name -> tfplugin6.GetMetadata.DataSourceMetadata - 71, // 33: tfplugin6.GetMetadata.Response.resources:type_name -> tfplugin6.GetMetadata.ResourceMetadata - 69, // 34: tfplugin6.GetMetadata.Response.functions:type_name -> tfplugin6.GetMetadata.FunctionMetadata - 68, // 35: tfplugin6.GetMetadata.Response.ephemeral_resources:type_name -> tfplugin6.GetMetadata.EphemeralMetadata - 72, // 36: tfplugin6.GetMetadata.Response.list_resources:type_name -> tfplugin6.GetMetadata.ListResourceMetadata - 73, // 37: tfplugin6.GetMetadata.Response.state_stores:type_name -> tfplugin6.GetMetadata.StateStoreMetadata - 74, // 38: tfplugin6.GetMetadata.Response.actions:type_name -> tfplugin6.GetMetadata.ActionMetadata + 73, // 32: tfplugin6.GetMetadata.Response.data_sources:type_name -> tfplugin6.GetMetadata.DataSourceMetadata + 74, // 33: tfplugin6.GetMetadata.Response.resources:type_name -> tfplugin6.GetMetadata.ResourceMetadata + 72, // 34: tfplugin6.GetMetadata.Response.functions:type_name -> tfplugin6.GetMetadata.FunctionMetadata + 71, // 35: tfplugin6.GetMetadata.Response.ephemeral_resources:type_name -> tfplugin6.GetMetadata.EphemeralMetadata + 75, // 36: tfplugin6.GetMetadata.Response.list_resources:type_name -> tfplugin6.GetMetadata.ListResourceMetadata + 76, // 37: tfplugin6.GetMetadata.Response.state_stores:type_name -> tfplugin6.GetMetadata.StateStoreMetadata + 77, // 38: tfplugin6.GetMetadata.Response.actions:type_name -> tfplugin6.GetMetadata.ActionMetadata 15, // 39: tfplugin6.GetProviderSchema.Response.provider:type_name -> tfplugin6.Schema - 77, // 40: tfplugin6.GetProviderSchema.Response.resource_schemas:type_name -> tfplugin6.GetProviderSchema.Response.ResourceSchemasEntry - 78, // 41: tfplugin6.GetProviderSchema.Response.data_source_schemas:type_name -> tfplugin6.GetProviderSchema.Response.DataSourceSchemasEntry - 79, // 42: tfplugin6.GetProviderSchema.Response.functions:type_name -> tfplugin6.GetProviderSchema.Response.FunctionsEntry - 80, // 43: tfplugin6.GetProviderSchema.Response.ephemeral_resource_schemas:type_name -> tfplugin6.GetProviderSchema.Response.EphemeralResourceSchemasEntry - 81, // 44: tfplugin6.GetProviderSchema.Response.list_resource_schemas:type_name -> tfplugin6.GetProviderSchema.Response.ListResourceSchemasEntry - 82, // 45: tfplugin6.GetProviderSchema.Response.state_store_schemas:type_name -> tfplugin6.GetProviderSchema.Response.StateStoreSchemasEntry - 83, // 46: tfplugin6.GetProviderSchema.Response.action_schemas:type_name -> tfplugin6.GetProviderSchema.Response.ActionSchemasEntry + 80, // 40: tfplugin6.GetProviderSchema.Response.resource_schemas:type_name -> tfplugin6.GetProviderSchema.Response.ResourceSchemasEntry + 81, // 41: tfplugin6.GetProviderSchema.Response.data_source_schemas:type_name -> tfplugin6.GetProviderSchema.Response.DataSourceSchemasEntry + 82, // 42: tfplugin6.GetProviderSchema.Response.functions:type_name -> tfplugin6.GetProviderSchema.Response.FunctionsEntry + 83, // 43: tfplugin6.GetProviderSchema.Response.ephemeral_resource_schemas:type_name -> tfplugin6.GetProviderSchema.Response.EphemeralResourceSchemasEntry + 84, // 44: tfplugin6.GetProviderSchema.Response.list_resource_schemas:type_name -> tfplugin6.GetProviderSchema.Response.ListResourceSchemasEntry + 85, // 45: tfplugin6.GetProviderSchema.Response.state_store_schemas:type_name -> tfplugin6.GetProviderSchema.Response.StateStoreSchemasEntry + 86, // 46: tfplugin6.GetProviderSchema.Response.action_schemas:type_name -> tfplugin6.GetProviderSchema.Response.ActionSchemasEntry 7, // 47: tfplugin6.GetProviderSchema.Response.diagnostics:type_name -> tfplugin6.Diagnostic 15, // 48: tfplugin6.GetProviderSchema.Response.provider_meta:type_name -> tfplugin6.Schema 17, // 49: tfplugin6.GetProviderSchema.Response.server_capabilities:type_name -> tfplugin6.ServerCapabilities @@ -8560,7 +8955,7 @@ var file_tfplugin6_proto_depIdxs = []int32{ 11, // 59: tfplugin6.UpgradeResourceState.Request.raw_state:type_name -> tfplugin6.RawState 6, // 60: tfplugin6.UpgradeResourceState.Response.upgraded_state:type_name -> tfplugin6.DynamicValue 7, // 61: tfplugin6.UpgradeResourceState.Response.diagnostics:type_name -> tfplugin6.Diagnostic - 90, // 62: tfplugin6.GetResourceIdentitySchemas.Response.identity_schemas:type_name -> tfplugin6.GetResourceIdentitySchemas.Response.IdentitySchemasEntry + 93, // 62: tfplugin6.GetResourceIdentitySchemas.Response.identity_schemas:type_name -> tfplugin6.GetResourceIdentitySchemas.Response.IdentitySchemasEntry 7, // 63: tfplugin6.GetResourceIdentitySchemas.Response.diagnostics:type_name -> tfplugin6.Diagnostic 12, // 64: tfplugin6.GetResourceIdentitySchemas.Response.IdentitySchemasEntry.value:type_name -> tfplugin6.ResourceIdentitySchema 11, // 65: tfplugin6.UpgradeResourceIdentity.Request.raw_identity:type_name -> tfplugin6.RawState @@ -8607,7 +9002,7 @@ var file_tfplugin6_proto_depIdxs = []int32{ 13, // 106: tfplugin6.ImportResourceState.Request.identity:type_name -> tfplugin6.ResourceIdentityData 6, // 107: tfplugin6.ImportResourceState.ImportedResource.state:type_name -> tfplugin6.DynamicValue 13, // 108: tfplugin6.ImportResourceState.ImportedResource.identity:type_name -> tfplugin6.ResourceIdentityData - 108, // 109: tfplugin6.ImportResourceState.Response.imported_resources:type_name -> tfplugin6.ImportResourceState.ImportedResource + 111, // 109: tfplugin6.ImportResourceState.Response.imported_resources:type_name -> tfplugin6.ImportResourceState.ImportedResource 7, // 110: tfplugin6.ImportResourceState.Response.diagnostics:type_name -> tfplugin6.Diagnostic 19, // 111: tfplugin6.ImportResourceState.Response.deferred:type_name -> tfplugin6.Deferred 11, // 112: tfplugin6.MoveResourceState.Request.source_state:type_name -> tfplugin6.RawState @@ -8624,13 +9019,13 @@ var file_tfplugin6_proto_depIdxs = []int32{ 6, // 123: tfplugin6.OpenEphemeralResource.Request.config:type_name -> tfplugin6.DynamicValue 18, // 124: tfplugin6.OpenEphemeralResource.Request.client_capabilities:type_name -> tfplugin6.ClientCapabilities 7, // 125: tfplugin6.OpenEphemeralResource.Response.diagnostics:type_name -> tfplugin6.Diagnostic - 149, // 126: tfplugin6.OpenEphemeralResource.Response.renew_at:type_name -> google.protobuf.Timestamp + 156, // 126: tfplugin6.OpenEphemeralResource.Response.renew_at:type_name -> google.protobuf.Timestamp 6, // 127: tfplugin6.OpenEphemeralResource.Response.result:type_name -> tfplugin6.DynamicValue 19, // 128: tfplugin6.OpenEphemeralResource.Response.deferred:type_name -> tfplugin6.Deferred 7, // 129: tfplugin6.RenewEphemeralResource.Response.diagnostics:type_name -> tfplugin6.Diagnostic - 149, // 130: tfplugin6.RenewEphemeralResource.Response.renew_at:type_name -> google.protobuf.Timestamp + 156, // 130: tfplugin6.RenewEphemeralResource.Response.renew_at:type_name -> google.protobuf.Timestamp 7, // 131: tfplugin6.CloseEphemeralResource.Response.diagnostics:type_name -> tfplugin6.Diagnostic - 122, // 132: tfplugin6.GetFunctions.Response.functions:type_name -> tfplugin6.GetFunctions.Response.FunctionsEntry + 125, // 132: tfplugin6.GetFunctions.Response.functions:type_name -> tfplugin6.GetFunctions.Response.FunctionsEntry 7, // 133: tfplugin6.GetFunctions.Response.diagnostics:type_name -> tfplugin6.Diagnostic 16, // 134: tfplugin6.GetFunctions.Response.FunctionsEntry.value:type_name -> tfplugin6.Function 6, // 135: tfplugin6.CallFunction.Request.arguments:type_name -> tfplugin6.DynamicValue @@ -8648,103 +9043,111 @@ var file_tfplugin6_proto_depIdxs = []int32{ 7, // 147: tfplugin6.ValidateStateStore.Response.diagnostics:type_name -> tfplugin6.Diagnostic 6, // 148: tfplugin6.ConfigureStateStore.Request.config:type_name -> tfplugin6.DynamicValue 7, // 149: tfplugin6.ConfigureStateStore.Response.diagnostics:type_name -> tfplugin6.Diagnostic - 7, // 150: tfplugin6.GetStates.Response.diagnostics:type_name -> tfplugin6.Diagnostic - 7, // 151: tfplugin6.DeleteState.Response.diagnostics:type_name -> tfplugin6.Diagnostic - 139, // 152: tfplugin6.PlanAction.Request.linked_resources:type_name -> tfplugin6.PlanAction.Request.LinkedResource - 6, // 153: tfplugin6.PlanAction.Request.config:type_name -> tfplugin6.DynamicValue - 18, // 154: tfplugin6.PlanAction.Request.client_capabilities:type_name -> tfplugin6.ClientCapabilities - 140, // 155: tfplugin6.PlanAction.Response.linked_resources:type_name -> tfplugin6.PlanAction.Response.LinkedResource - 7, // 156: tfplugin6.PlanAction.Response.diagnostics:type_name -> tfplugin6.Diagnostic - 19, // 157: tfplugin6.PlanAction.Response.deferred:type_name -> tfplugin6.Deferred - 6, // 158: tfplugin6.PlanAction.Request.LinkedResource.prior_state:type_name -> tfplugin6.DynamicValue - 6, // 159: tfplugin6.PlanAction.Request.LinkedResource.planned_state:type_name -> tfplugin6.DynamicValue - 6, // 160: tfplugin6.PlanAction.Request.LinkedResource.config:type_name -> tfplugin6.DynamicValue - 13, // 161: tfplugin6.PlanAction.Request.LinkedResource.prior_identity:type_name -> tfplugin6.ResourceIdentityData - 6, // 162: tfplugin6.PlanAction.Response.LinkedResource.planned_state:type_name -> tfplugin6.DynamicValue - 13, // 163: tfplugin6.PlanAction.Response.LinkedResource.planned_identity:type_name -> tfplugin6.ResourceIdentityData - 143, // 164: tfplugin6.InvokeAction.Request.linked_resources:type_name -> tfplugin6.InvokeAction.Request.LinkedResource - 6, // 165: tfplugin6.InvokeAction.Request.config:type_name -> tfplugin6.DynamicValue - 18, // 166: tfplugin6.InvokeAction.Request.client_capabilities:type_name -> tfplugin6.ClientCapabilities - 144, // 167: tfplugin6.InvokeAction.Event.progress:type_name -> tfplugin6.InvokeAction.Event.Progress - 145, // 168: tfplugin6.InvokeAction.Event.completed:type_name -> tfplugin6.InvokeAction.Event.Completed - 6, // 169: tfplugin6.InvokeAction.Request.LinkedResource.prior_state:type_name -> tfplugin6.DynamicValue - 6, // 170: tfplugin6.InvokeAction.Request.LinkedResource.planned_state:type_name -> tfplugin6.DynamicValue - 6, // 171: tfplugin6.InvokeAction.Request.LinkedResource.config:type_name -> tfplugin6.DynamicValue - 13, // 172: tfplugin6.InvokeAction.Request.LinkedResource.planned_identity:type_name -> tfplugin6.ResourceIdentityData - 146, // 173: tfplugin6.InvokeAction.Event.Completed.linked_resources:type_name -> tfplugin6.InvokeAction.Event.Completed.LinkedResource - 7, // 174: tfplugin6.InvokeAction.Event.Completed.diagnostics:type_name -> tfplugin6.Diagnostic - 6, // 175: tfplugin6.InvokeAction.Event.Completed.LinkedResource.new_state:type_name -> tfplugin6.DynamicValue - 13, // 176: tfplugin6.InvokeAction.Event.Completed.LinkedResource.new_identity:type_name -> tfplugin6.ResourceIdentityData - 6, // 177: tfplugin6.ValidateActionConfig.Request.config:type_name -> tfplugin6.DynamicValue - 50, // 178: tfplugin6.ValidateActionConfig.Request.linked_resources:type_name -> tfplugin6.LinkedResourceConfig - 7, // 179: tfplugin6.ValidateActionConfig.Response.diagnostics:type_name -> tfplugin6.Diagnostic - 66, // 180: tfplugin6.Provider.GetMetadata:input_type -> tfplugin6.GetMetadata.Request - 75, // 181: tfplugin6.Provider.GetProviderSchema:input_type -> tfplugin6.GetProviderSchema.Request - 84, // 182: tfplugin6.Provider.ValidateProviderConfig:input_type -> tfplugin6.ValidateProviderConfig.Request - 93, // 183: tfplugin6.Provider.ValidateResourceConfig:input_type -> tfplugin6.ValidateResourceConfig.Request - 95, // 184: tfplugin6.Provider.ValidateDataResourceConfig:input_type -> tfplugin6.ValidateDataResourceConfig.Request - 86, // 185: tfplugin6.Provider.UpgradeResourceState:input_type -> tfplugin6.UpgradeResourceState.Request - 88, // 186: tfplugin6.Provider.GetResourceIdentitySchemas:input_type -> tfplugin6.GetResourceIdentitySchemas.Request - 91, // 187: tfplugin6.Provider.UpgradeResourceIdentity:input_type -> tfplugin6.UpgradeResourceIdentity.Request - 99, // 188: tfplugin6.Provider.ConfigureProvider:input_type -> tfplugin6.ConfigureProvider.Request - 101, // 189: tfplugin6.Provider.ReadResource:input_type -> tfplugin6.ReadResource.Request - 103, // 190: tfplugin6.Provider.PlanResourceChange:input_type -> tfplugin6.PlanResourceChange.Request - 105, // 191: tfplugin6.Provider.ApplyResourceChange:input_type -> tfplugin6.ApplyResourceChange.Request - 107, // 192: tfplugin6.Provider.ImportResourceState:input_type -> tfplugin6.ImportResourceState.Request - 110, // 193: tfplugin6.Provider.MoveResourceState:input_type -> tfplugin6.MoveResourceState.Request - 112, // 194: tfplugin6.Provider.ReadDataSource:input_type -> tfplugin6.ReadDataSource.Request - 97, // 195: tfplugin6.Provider.ValidateEphemeralResourceConfig:input_type -> tfplugin6.ValidateEphemeralResourceConfig.Request - 114, // 196: tfplugin6.Provider.OpenEphemeralResource:input_type -> tfplugin6.OpenEphemeralResource.Request - 116, // 197: tfplugin6.Provider.RenewEphemeralResource:input_type -> tfplugin6.RenewEphemeralResource.Request - 118, // 198: tfplugin6.Provider.CloseEphemeralResource:input_type -> tfplugin6.CloseEphemeralResource.Request - 125, // 199: tfplugin6.Provider.ListResource:input_type -> tfplugin6.ListResource.Request - 127, // 200: tfplugin6.Provider.ValidateListResourceConfig:input_type -> tfplugin6.ValidateListResourceConfig.Request - 120, // 201: tfplugin6.Provider.GetFunctions:input_type -> tfplugin6.GetFunctions.Request - 123, // 202: tfplugin6.Provider.CallFunction:input_type -> tfplugin6.CallFunction.Request - 129, // 203: tfplugin6.Provider.ValidateStateStoreConfig:input_type -> tfplugin6.ValidateStateStore.Request - 131, // 204: tfplugin6.Provider.ConfigureStateStore:input_type -> tfplugin6.ConfigureStateStore.Request - 133, // 205: tfplugin6.Provider.GetStates:input_type -> tfplugin6.GetStates.Request - 135, // 206: tfplugin6.Provider.DeleteState:input_type -> tfplugin6.DeleteState.Request - 137, // 207: tfplugin6.Provider.PlanAction:input_type -> tfplugin6.PlanAction.Request - 141, // 208: tfplugin6.Provider.InvokeAction:input_type -> tfplugin6.InvokeAction.Request - 147, // 209: tfplugin6.Provider.ValidateActionConfig:input_type -> tfplugin6.ValidateActionConfig.Request - 52, // 210: tfplugin6.Provider.StopProvider:input_type -> tfplugin6.StopProvider.Request - 67, // 211: tfplugin6.Provider.GetMetadata:output_type -> tfplugin6.GetMetadata.Response - 76, // 212: tfplugin6.Provider.GetProviderSchema:output_type -> tfplugin6.GetProviderSchema.Response - 85, // 213: tfplugin6.Provider.ValidateProviderConfig:output_type -> tfplugin6.ValidateProviderConfig.Response - 94, // 214: tfplugin6.Provider.ValidateResourceConfig:output_type -> tfplugin6.ValidateResourceConfig.Response - 96, // 215: tfplugin6.Provider.ValidateDataResourceConfig:output_type -> tfplugin6.ValidateDataResourceConfig.Response - 87, // 216: tfplugin6.Provider.UpgradeResourceState:output_type -> tfplugin6.UpgradeResourceState.Response - 89, // 217: tfplugin6.Provider.GetResourceIdentitySchemas:output_type -> tfplugin6.GetResourceIdentitySchemas.Response - 92, // 218: tfplugin6.Provider.UpgradeResourceIdentity:output_type -> tfplugin6.UpgradeResourceIdentity.Response - 100, // 219: tfplugin6.Provider.ConfigureProvider:output_type -> tfplugin6.ConfigureProvider.Response - 102, // 220: tfplugin6.Provider.ReadResource:output_type -> tfplugin6.ReadResource.Response - 104, // 221: tfplugin6.Provider.PlanResourceChange:output_type -> tfplugin6.PlanResourceChange.Response - 106, // 222: tfplugin6.Provider.ApplyResourceChange:output_type -> tfplugin6.ApplyResourceChange.Response - 109, // 223: tfplugin6.Provider.ImportResourceState:output_type -> tfplugin6.ImportResourceState.Response - 111, // 224: tfplugin6.Provider.MoveResourceState:output_type -> tfplugin6.MoveResourceState.Response - 113, // 225: tfplugin6.Provider.ReadDataSource:output_type -> tfplugin6.ReadDataSource.Response - 98, // 226: tfplugin6.Provider.ValidateEphemeralResourceConfig:output_type -> tfplugin6.ValidateEphemeralResourceConfig.Response - 115, // 227: tfplugin6.Provider.OpenEphemeralResource:output_type -> tfplugin6.OpenEphemeralResource.Response - 117, // 228: tfplugin6.Provider.RenewEphemeralResource:output_type -> tfplugin6.RenewEphemeralResource.Response - 119, // 229: tfplugin6.Provider.CloseEphemeralResource:output_type -> tfplugin6.CloseEphemeralResource.Response - 126, // 230: tfplugin6.Provider.ListResource:output_type -> tfplugin6.ListResource.Event - 128, // 231: tfplugin6.Provider.ValidateListResourceConfig:output_type -> tfplugin6.ValidateListResourceConfig.Response - 121, // 232: tfplugin6.Provider.GetFunctions:output_type -> tfplugin6.GetFunctions.Response - 124, // 233: tfplugin6.Provider.CallFunction:output_type -> tfplugin6.CallFunction.Response - 130, // 234: tfplugin6.Provider.ValidateStateStoreConfig:output_type -> tfplugin6.ValidateStateStore.Response - 132, // 235: tfplugin6.Provider.ConfigureStateStore:output_type -> tfplugin6.ConfigureStateStore.Response - 134, // 236: tfplugin6.Provider.GetStates:output_type -> tfplugin6.GetStates.Response - 136, // 237: tfplugin6.Provider.DeleteState:output_type -> tfplugin6.DeleteState.Response - 138, // 238: tfplugin6.Provider.PlanAction:output_type -> tfplugin6.PlanAction.Response - 142, // 239: tfplugin6.Provider.InvokeAction:output_type -> tfplugin6.InvokeAction.Event - 148, // 240: tfplugin6.Provider.ValidateActionConfig:output_type -> tfplugin6.ValidateActionConfig.Response - 53, // 241: tfplugin6.Provider.StopProvider:output_type -> tfplugin6.StopProvider.Response - 211, // [211:242] is the sub-list for method output_type - 180, // [180:211] is the sub-list for method input_type - 180, // [180:180] is the sub-list for extension type_name - 180, // [180:180] is the sub-list for extension extendee - 0, // [0:180] is the sub-list for field type_name + 47, // 150: tfplugin6.ReadStateBytes.ResponseChunk.range:type_name -> tfplugin6.StateRange + 7, // 151: tfplugin6.ReadStateBytes.ResponseChunk.diagnostics:type_name -> tfplugin6.Diagnostic + 47, // 152: tfplugin6.WriteStateBytes.RequestChunk.range:type_name -> tfplugin6.StateRange + 7, // 153: tfplugin6.WriteStateBytes.Response.diagnostics:type_name -> tfplugin6.Diagnostic + 7, // 154: tfplugin6.GetStates.Response.diagnostics:type_name -> tfplugin6.Diagnostic + 7, // 155: tfplugin6.DeleteState.Response.diagnostics:type_name -> tfplugin6.Diagnostic + 146, // 156: tfplugin6.PlanAction.Request.linked_resources:type_name -> tfplugin6.PlanAction.Request.LinkedResource + 6, // 157: tfplugin6.PlanAction.Request.config:type_name -> tfplugin6.DynamicValue + 18, // 158: tfplugin6.PlanAction.Request.client_capabilities:type_name -> tfplugin6.ClientCapabilities + 147, // 159: tfplugin6.PlanAction.Response.linked_resources:type_name -> tfplugin6.PlanAction.Response.LinkedResource + 7, // 160: tfplugin6.PlanAction.Response.diagnostics:type_name -> tfplugin6.Diagnostic + 19, // 161: tfplugin6.PlanAction.Response.deferred:type_name -> tfplugin6.Deferred + 6, // 162: tfplugin6.PlanAction.Request.LinkedResource.prior_state:type_name -> tfplugin6.DynamicValue + 6, // 163: tfplugin6.PlanAction.Request.LinkedResource.planned_state:type_name -> tfplugin6.DynamicValue + 6, // 164: tfplugin6.PlanAction.Request.LinkedResource.config:type_name -> tfplugin6.DynamicValue + 13, // 165: tfplugin6.PlanAction.Request.LinkedResource.prior_identity:type_name -> tfplugin6.ResourceIdentityData + 6, // 166: tfplugin6.PlanAction.Response.LinkedResource.planned_state:type_name -> tfplugin6.DynamicValue + 13, // 167: tfplugin6.PlanAction.Response.LinkedResource.planned_identity:type_name -> tfplugin6.ResourceIdentityData + 150, // 168: tfplugin6.InvokeAction.Request.linked_resources:type_name -> tfplugin6.InvokeAction.Request.LinkedResource + 6, // 169: tfplugin6.InvokeAction.Request.config:type_name -> tfplugin6.DynamicValue + 18, // 170: tfplugin6.InvokeAction.Request.client_capabilities:type_name -> tfplugin6.ClientCapabilities + 151, // 171: tfplugin6.InvokeAction.Event.progress:type_name -> tfplugin6.InvokeAction.Event.Progress + 152, // 172: tfplugin6.InvokeAction.Event.completed:type_name -> tfplugin6.InvokeAction.Event.Completed + 6, // 173: tfplugin6.InvokeAction.Request.LinkedResource.prior_state:type_name -> tfplugin6.DynamicValue + 6, // 174: tfplugin6.InvokeAction.Request.LinkedResource.planned_state:type_name -> tfplugin6.DynamicValue + 6, // 175: tfplugin6.InvokeAction.Request.LinkedResource.config:type_name -> tfplugin6.DynamicValue + 13, // 176: tfplugin6.InvokeAction.Request.LinkedResource.planned_identity:type_name -> tfplugin6.ResourceIdentityData + 153, // 177: tfplugin6.InvokeAction.Event.Completed.linked_resources:type_name -> tfplugin6.InvokeAction.Event.Completed.LinkedResource + 7, // 178: tfplugin6.InvokeAction.Event.Completed.diagnostics:type_name -> tfplugin6.Diagnostic + 6, // 179: tfplugin6.InvokeAction.Event.Completed.LinkedResource.new_state:type_name -> tfplugin6.DynamicValue + 13, // 180: tfplugin6.InvokeAction.Event.Completed.LinkedResource.new_identity:type_name -> tfplugin6.ResourceIdentityData + 6, // 181: tfplugin6.ValidateActionConfig.Request.config:type_name -> tfplugin6.DynamicValue + 53, // 182: tfplugin6.ValidateActionConfig.Request.linked_resources:type_name -> tfplugin6.LinkedResourceConfig + 7, // 183: tfplugin6.ValidateActionConfig.Response.diagnostics:type_name -> tfplugin6.Diagnostic + 69, // 184: tfplugin6.Provider.GetMetadata:input_type -> tfplugin6.GetMetadata.Request + 78, // 185: tfplugin6.Provider.GetProviderSchema:input_type -> tfplugin6.GetProviderSchema.Request + 87, // 186: tfplugin6.Provider.ValidateProviderConfig:input_type -> tfplugin6.ValidateProviderConfig.Request + 96, // 187: tfplugin6.Provider.ValidateResourceConfig:input_type -> tfplugin6.ValidateResourceConfig.Request + 98, // 188: tfplugin6.Provider.ValidateDataResourceConfig:input_type -> tfplugin6.ValidateDataResourceConfig.Request + 89, // 189: tfplugin6.Provider.UpgradeResourceState:input_type -> tfplugin6.UpgradeResourceState.Request + 91, // 190: tfplugin6.Provider.GetResourceIdentitySchemas:input_type -> tfplugin6.GetResourceIdentitySchemas.Request + 94, // 191: tfplugin6.Provider.UpgradeResourceIdentity:input_type -> tfplugin6.UpgradeResourceIdentity.Request + 102, // 192: tfplugin6.Provider.ConfigureProvider:input_type -> tfplugin6.ConfigureProvider.Request + 104, // 193: tfplugin6.Provider.ReadResource:input_type -> tfplugin6.ReadResource.Request + 106, // 194: tfplugin6.Provider.PlanResourceChange:input_type -> tfplugin6.PlanResourceChange.Request + 108, // 195: tfplugin6.Provider.ApplyResourceChange:input_type -> tfplugin6.ApplyResourceChange.Request + 110, // 196: tfplugin6.Provider.ImportResourceState:input_type -> tfplugin6.ImportResourceState.Request + 113, // 197: tfplugin6.Provider.MoveResourceState:input_type -> tfplugin6.MoveResourceState.Request + 115, // 198: tfplugin6.Provider.ReadDataSource:input_type -> tfplugin6.ReadDataSource.Request + 100, // 199: tfplugin6.Provider.ValidateEphemeralResourceConfig:input_type -> tfplugin6.ValidateEphemeralResourceConfig.Request + 117, // 200: tfplugin6.Provider.OpenEphemeralResource:input_type -> tfplugin6.OpenEphemeralResource.Request + 119, // 201: tfplugin6.Provider.RenewEphemeralResource:input_type -> tfplugin6.RenewEphemeralResource.Request + 121, // 202: tfplugin6.Provider.CloseEphemeralResource:input_type -> tfplugin6.CloseEphemeralResource.Request + 128, // 203: tfplugin6.Provider.ListResource:input_type -> tfplugin6.ListResource.Request + 130, // 204: tfplugin6.Provider.ValidateListResourceConfig:input_type -> tfplugin6.ValidateListResourceConfig.Request + 123, // 205: tfplugin6.Provider.GetFunctions:input_type -> tfplugin6.GetFunctions.Request + 126, // 206: tfplugin6.Provider.CallFunction:input_type -> tfplugin6.CallFunction.Request + 132, // 207: tfplugin6.Provider.ValidateStateStoreConfig:input_type -> tfplugin6.ValidateStateStore.Request + 134, // 208: tfplugin6.Provider.ConfigureStateStore:input_type -> tfplugin6.ConfigureStateStore.Request + 136, // 209: tfplugin6.Provider.ReadStateBytes:input_type -> tfplugin6.ReadStateBytes.Request + 138, // 210: tfplugin6.Provider.WriteStateBytes:input_type -> tfplugin6.WriteStateBytes.RequestChunk + 140, // 211: tfplugin6.Provider.GetStates:input_type -> tfplugin6.GetStates.Request + 142, // 212: tfplugin6.Provider.DeleteState:input_type -> tfplugin6.DeleteState.Request + 144, // 213: tfplugin6.Provider.PlanAction:input_type -> tfplugin6.PlanAction.Request + 148, // 214: tfplugin6.Provider.InvokeAction:input_type -> tfplugin6.InvokeAction.Request + 154, // 215: tfplugin6.Provider.ValidateActionConfig:input_type -> tfplugin6.ValidateActionConfig.Request + 55, // 216: tfplugin6.Provider.StopProvider:input_type -> tfplugin6.StopProvider.Request + 70, // 217: tfplugin6.Provider.GetMetadata:output_type -> tfplugin6.GetMetadata.Response + 79, // 218: tfplugin6.Provider.GetProviderSchema:output_type -> tfplugin6.GetProviderSchema.Response + 88, // 219: tfplugin6.Provider.ValidateProviderConfig:output_type -> tfplugin6.ValidateProviderConfig.Response + 97, // 220: tfplugin6.Provider.ValidateResourceConfig:output_type -> tfplugin6.ValidateResourceConfig.Response + 99, // 221: tfplugin6.Provider.ValidateDataResourceConfig:output_type -> tfplugin6.ValidateDataResourceConfig.Response + 90, // 222: tfplugin6.Provider.UpgradeResourceState:output_type -> tfplugin6.UpgradeResourceState.Response + 92, // 223: tfplugin6.Provider.GetResourceIdentitySchemas:output_type -> tfplugin6.GetResourceIdentitySchemas.Response + 95, // 224: tfplugin6.Provider.UpgradeResourceIdentity:output_type -> tfplugin6.UpgradeResourceIdentity.Response + 103, // 225: tfplugin6.Provider.ConfigureProvider:output_type -> tfplugin6.ConfigureProvider.Response + 105, // 226: tfplugin6.Provider.ReadResource:output_type -> tfplugin6.ReadResource.Response + 107, // 227: tfplugin6.Provider.PlanResourceChange:output_type -> tfplugin6.PlanResourceChange.Response + 109, // 228: tfplugin6.Provider.ApplyResourceChange:output_type -> tfplugin6.ApplyResourceChange.Response + 112, // 229: tfplugin6.Provider.ImportResourceState:output_type -> tfplugin6.ImportResourceState.Response + 114, // 230: tfplugin6.Provider.MoveResourceState:output_type -> tfplugin6.MoveResourceState.Response + 116, // 231: tfplugin6.Provider.ReadDataSource:output_type -> tfplugin6.ReadDataSource.Response + 101, // 232: tfplugin6.Provider.ValidateEphemeralResourceConfig:output_type -> tfplugin6.ValidateEphemeralResourceConfig.Response + 118, // 233: tfplugin6.Provider.OpenEphemeralResource:output_type -> tfplugin6.OpenEphemeralResource.Response + 120, // 234: tfplugin6.Provider.RenewEphemeralResource:output_type -> tfplugin6.RenewEphemeralResource.Response + 122, // 235: tfplugin6.Provider.CloseEphemeralResource:output_type -> tfplugin6.CloseEphemeralResource.Response + 129, // 236: tfplugin6.Provider.ListResource:output_type -> tfplugin6.ListResource.Event + 131, // 237: tfplugin6.Provider.ValidateListResourceConfig:output_type -> tfplugin6.ValidateListResourceConfig.Response + 124, // 238: tfplugin6.Provider.GetFunctions:output_type -> tfplugin6.GetFunctions.Response + 127, // 239: tfplugin6.Provider.CallFunction:output_type -> tfplugin6.CallFunction.Response + 133, // 240: tfplugin6.Provider.ValidateStateStoreConfig:output_type -> tfplugin6.ValidateStateStore.Response + 135, // 241: tfplugin6.Provider.ConfigureStateStore:output_type -> tfplugin6.ConfigureStateStore.Response + 137, // 242: tfplugin6.Provider.ReadStateBytes:output_type -> tfplugin6.ReadStateBytes.ResponseChunk + 139, // 243: tfplugin6.Provider.WriteStateBytes:output_type -> tfplugin6.WriteStateBytes.Response + 141, // 244: tfplugin6.Provider.GetStates:output_type -> tfplugin6.GetStates.Response + 143, // 245: tfplugin6.Provider.DeleteState:output_type -> tfplugin6.DeleteState.Response + 145, // 246: tfplugin6.Provider.PlanAction:output_type -> tfplugin6.PlanAction.Response + 149, // 247: tfplugin6.Provider.InvokeAction:output_type -> tfplugin6.InvokeAction.Event + 155, // 248: tfplugin6.Provider.ValidateActionConfig:output_type -> tfplugin6.ValidateActionConfig.Response + 56, // 249: tfplugin6.Provider.StopProvider:output_type -> tfplugin6.StopProvider.Response + 217, // [217:250] is the sub-list for method output_type + 184, // [184:217] is the sub-list for method input_type + 184, // [184:184] is the sub-list for extension type_name + 184, // [184:184] is the sub-list for extension extendee + 0, // [0:184] is the sub-list for field type_name } func init() { file_tfplugin6_proto_init() } @@ -8758,17 +9161,17 @@ func file_tfplugin6_proto_init() { (*ActionSchema_Lifecycle_)(nil), (*ActionSchema_Linked_)(nil), } - file_tfplugin6_proto_msgTypes[45].OneofWrappers = []any{ + file_tfplugin6_proto_msgTypes[48].OneofWrappers = []any{ (*AttributePath_Step_AttributeName)(nil), (*AttributePath_Step_ElementKeyString)(nil), (*AttributePath_Step_ElementKeyInt)(nil), } - file_tfplugin6_proto_msgTypes[109].OneofWrappers = []any{} - file_tfplugin6_proto_msgTypes[110].OneofWrappers = []any{} - file_tfplugin6_proto_msgTypes[111].OneofWrappers = []any{} file_tfplugin6_proto_msgTypes[112].OneofWrappers = []any{} - file_tfplugin6_proto_msgTypes[120].OneofWrappers = []any{} - file_tfplugin6_proto_msgTypes[136].OneofWrappers = []any{ + file_tfplugin6_proto_msgTypes[113].OneofWrappers = []any{} + file_tfplugin6_proto_msgTypes[114].OneofWrappers = []any{} + file_tfplugin6_proto_msgTypes[115].OneofWrappers = []any{} + file_tfplugin6_proto_msgTypes[123].OneofWrappers = []any{} + file_tfplugin6_proto_msgTypes[143].OneofWrappers = []any{ (*InvokeAction_Event_Progress_)(nil), (*InvokeAction_Event_Completed_)(nil), } @@ -8778,7 +9181,7 @@ func file_tfplugin6_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_tfplugin6_proto_rawDesc), len(file_tfplugin6_proto_rawDesc)), NumEnums: 6, - NumMessages: 143, + NumMessages: 150, NumExtensions: 0, NumServices: 1, }, @@ -8848,6 +9251,10 @@ type ProviderClient interface { ValidateStateStoreConfig(ctx context.Context, in *ValidateStateStore_Request, opts ...grpc.CallOption) (*ValidateStateStore_Response, error) // ConfigureStateStore configures the state store, such as S3 connection in the context of already configured provider ConfigureStateStore(ctx context.Context, in *ConfigureStateStore_Request, opts ...grpc.CallOption) (*ConfigureStateStore_Response, error) + // ReadStateBytes streams byte chunks of a given state file from a state store + ReadStateBytes(ctx context.Context, in *ReadStateBytes_Request, opts ...grpc.CallOption) (Provider_ReadStateBytesClient, error) + // WriteStateBytes streams byte chunks of a given state file into a state store + WriteStateBytes(ctx context.Context, opts ...grpc.CallOption) (Provider_WriteStateBytesClient, error) // GetStates returns a list of all states (i.e. CE workspaces) managed by a given state store GetStates(ctx context.Context, in *GetStates_Request, opts ...grpc.CallOption) (*GetStates_Response, error) // DeleteState instructs a given state store to delete a specific state (i.e. a CE workspace) @@ -9116,6 +9523,72 @@ func (c *providerClient) ConfigureStateStore(ctx context.Context, in *ConfigureS return out, nil } +func (c *providerClient) ReadStateBytes(ctx context.Context, in *ReadStateBytes_Request, opts ...grpc.CallOption) (Provider_ReadStateBytesClient, error) { + stream, err := c.cc.NewStream(ctx, &_Provider_serviceDesc.Streams[1], "/tfplugin6.Provider/ReadStateBytes", opts...) + if err != nil { + return nil, err + } + x := &providerReadStateBytesClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type Provider_ReadStateBytesClient interface { + Recv() (*ReadStateBytes_ResponseChunk, error) + grpc.ClientStream +} + +type providerReadStateBytesClient struct { + grpc.ClientStream +} + +func (x *providerReadStateBytesClient) Recv() (*ReadStateBytes_ResponseChunk, error) { + m := new(ReadStateBytes_ResponseChunk) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *providerClient) WriteStateBytes(ctx context.Context, opts ...grpc.CallOption) (Provider_WriteStateBytesClient, error) { + stream, err := c.cc.NewStream(ctx, &_Provider_serviceDesc.Streams[2], "/tfplugin6.Provider/WriteStateBytes", opts...) + if err != nil { + return nil, err + } + x := &providerWriteStateBytesClient{stream} + return x, nil +} + +type Provider_WriteStateBytesClient interface { + Send(*WriteStateBytes_RequestChunk) error + CloseAndRecv() (*WriteStateBytes_Response, error) + grpc.ClientStream +} + +type providerWriteStateBytesClient struct { + grpc.ClientStream +} + +func (x *providerWriteStateBytesClient) Send(m *WriteStateBytes_RequestChunk) error { + return x.ClientStream.SendMsg(m) +} + +func (x *providerWriteStateBytesClient) CloseAndRecv() (*WriteStateBytes_Response, error) { + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + m := new(WriteStateBytes_Response) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + func (c *providerClient) GetStates(ctx context.Context, in *GetStates_Request, opts ...grpc.CallOption) (*GetStates_Response, error) { out := new(GetStates_Response) err := c.cc.Invoke(ctx, "/tfplugin6.Provider/GetStates", in, out, opts...) @@ -9144,7 +9617,7 @@ func (c *providerClient) PlanAction(ctx context.Context, in *PlanAction_Request, } func (c *providerClient) InvokeAction(ctx context.Context, in *InvokeAction_Request, opts ...grpc.CallOption) (Provider_InvokeActionClient, error) { - stream, err := c.cc.NewStream(ctx, &_Provider_serviceDesc.Streams[1], "/tfplugin6.Provider/InvokeAction", opts...) + stream, err := c.cc.NewStream(ctx, &_Provider_serviceDesc.Streams[3], "/tfplugin6.Provider/InvokeAction", opts...) if err != nil { return nil, err } @@ -9239,6 +9712,10 @@ type ProviderServer interface { ValidateStateStoreConfig(context.Context, *ValidateStateStore_Request) (*ValidateStateStore_Response, error) // ConfigureStateStore configures the state store, such as S3 connection in the context of already configured provider ConfigureStateStore(context.Context, *ConfigureStateStore_Request) (*ConfigureStateStore_Response, error) + // ReadStateBytes streams byte chunks of a given state file from a state store + ReadStateBytes(*ReadStateBytes_Request, Provider_ReadStateBytesServer) error + // WriteStateBytes streams byte chunks of a given state file into a state store + WriteStateBytes(Provider_WriteStateBytesServer) error // GetStates returns a list of all states (i.e. CE workspaces) managed by a given state store GetStates(context.Context, *GetStates_Request) (*GetStates_Response, error) // DeleteState instructs a given state store to delete a specific state (i.e. a CE workspace) @@ -9330,6 +9807,12 @@ func (*UnimplementedProviderServer) ValidateStateStoreConfig(context.Context, *V func (*UnimplementedProviderServer) ConfigureStateStore(context.Context, *ConfigureStateStore_Request) (*ConfigureStateStore_Response, error) { return nil, status.Errorf(codes.Unimplemented, "method ConfigureStateStore not implemented") } +func (*UnimplementedProviderServer) ReadStateBytes(*ReadStateBytes_Request, Provider_ReadStateBytesServer) error { + return status.Errorf(codes.Unimplemented, "method ReadStateBytes not implemented") +} +func (*UnimplementedProviderServer) WriteStateBytes(Provider_WriteStateBytesServer) error { + return status.Errorf(codes.Unimplemented, "method WriteStateBytes not implemented") +} func (*UnimplementedProviderServer) GetStates(context.Context, *GetStates_Request) (*GetStates_Response, error) { return nil, status.Errorf(codes.Unimplemented, "method GetStates not implemented") } @@ -9806,6 +10289,53 @@ func _Provider_ConfigureStateStore_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } +func _Provider_ReadStateBytes_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(ReadStateBytes_Request) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(ProviderServer).ReadStateBytes(m, &providerReadStateBytesServer{stream}) +} + +type Provider_ReadStateBytesServer interface { + Send(*ReadStateBytes_ResponseChunk) error + grpc.ServerStream +} + +type providerReadStateBytesServer struct { + grpc.ServerStream +} + +func (x *providerReadStateBytesServer) Send(m *ReadStateBytes_ResponseChunk) error { + return x.ServerStream.SendMsg(m) +} + +func _Provider_WriteStateBytes_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(ProviderServer).WriteStateBytes(&providerWriteStateBytesServer{stream}) +} + +type Provider_WriteStateBytesServer interface { + SendAndClose(*WriteStateBytes_Response) error + Recv() (*WriteStateBytes_RequestChunk, error) + grpc.ServerStream +} + +type providerWriteStateBytesServer struct { + grpc.ServerStream +} + +func (x *providerWriteStateBytesServer) SendAndClose(m *WriteStateBytes_Response) error { + return x.ServerStream.SendMsg(m) +} + +func (x *providerWriteStateBytesServer) Recv() (*WriteStateBytes_RequestChunk, error) { + m := new(WriteStateBytes_RequestChunk) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + func _Provider_GetStates_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetStates_Request) if err := dec(in); err != nil { @@ -10044,6 +10574,16 @@ var _Provider_serviceDesc = grpc.ServiceDesc{ Handler: _Provider_ListResource_Handler, ServerStreams: true, }, + { + StreamName: "ReadStateBytes", + Handler: _Provider_ReadStateBytes_Handler, + ServerStreams: true, + }, + { + StreamName: "WriteStateBytes", + Handler: _Provider_WriteStateBytes_Handler, + ClientStreams: true, + }, { StreamName: "InvokeAction", Handler: _Provider_InvokeAction_Handler, From bd2039ecb3000772a4402127479317525145d8b7 Mon Sep 17 00:00:00 2001 From: Sarah French <15078782+SarahFrench@users.noreply.github.com> Date: Tue, 26 Aug 2025 10:54:32 +0100 Subject: [PATCH 02/60] [WIP] - Testing ReadStateBytes and WriteStateBytes (#37464) * Fix nil pointer error * Add WIP test for ReadStateBytes * Move test mock to separate testing file * Update mock to send unexpected EOF when there's a problem returning data and it's not a true EOF * Add test case for when length != expected length * Add test for when trying to read state from a store type that doesn't exist * Change symbol names to lowercase * Add ability to force a diagnostic to be returned from `mockReadStateBytesClient`'s `Recv` method * Add test showing error diagnostics raised by the ReadStateBytes client are returned * Add missing header * Simplify mock by using an embedded type * Rename `mockOpts` to `mockReadStateBytesOpts` * Update existing tests to assert what arguments are passed to the RPC method call * Add mock WriteStateBytesClient which uses `go.uber.org/mock/gomock` to enable assertions about calls to Send * Add a test for WriteStateBytes that makes assertions about calls to the Send method * Update test case to explicitly test writing data smaller than the chunk size * Implement chunking in WriteStateBytes, add test case to assert expected chunking behaviour * Add generated mock for Provider_WriteStateBytesClient in protocol v6 * Update tests to use new `MockProvider_WriteStateBytesClient`, remove handwritten mock * Update code comments in test * Add tests for diagnostics and errors returned during WriteStateBytes * Add generated mock for Provider_ReadStateBytesClient in protocol v6, replace old mock * Add test case for grpc errors in ReadStateBytes, fix how error is returned * Typo in comment * Add missing warning test, rename some test cases --- internal/plugin6/grpc_provider.go | 59 ++- internal/plugin6/grpc_provider_test.go | 600 ++++++++++++++++++++++++ internal/plugin6/mock_proto/generate.go | 2 +- internal/plugin6/mock_proto/mock.go | 264 ++++++++++- 4 files changed, 899 insertions(+), 26 deletions(-) diff --git a/internal/plugin6/grpc_provider.go b/internal/plugin6/grpc_provider.go index 3d8a565a4eba..61c8083e7690 100644 --- a/internal/plugin6/grpc_provider.go +++ b/internal/plugin6/grpc_provider.go @@ -1510,7 +1510,7 @@ func (p *GRPCProvider) ReadStateBytes(r providers.ReadStateBytesRequest) (resp p return resp } - var buf *bytes.Buffer + buf := &bytes.Buffer{} var expectedTotalLength int for { chunk, err := client.Recv() @@ -1519,7 +1519,7 @@ func (p *GRPCProvider) ReadStateBytes(r providers.ReadStateBytesRequest) (resp p break } if err != nil { - resp.Diagnostics = resp.Diagnostics.Append(err) + resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err)) break } resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(chunk.Diagnostics)) @@ -1576,39 +1576,52 @@ func (p *GRPCProvider) WriteStateBytes(r providers.WriteStateBytesRequest) (resp // TODO: Configurable chunk size chunkSize := 4 * 1_000_000 // 4MB - if len(r.Bytes) < chunkSize { + client, err := p.client.WriteStateBytes(ctx) + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err)) + return resp + } + + buf := bytes.NewBuffer(r.Bytes) + var totalLength int64 = int64(len(r.Bytes)) + var totalBytesProcessed int + for { + chunk := buf.Next(chunkSize) + + if len(chunk) == 0 { + // The previous iteration read the last of the data. Now we finish up. + protoResp, err := client.CloseAndRecv() + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err)) + return resp + } + resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics)) + if resp.Diagnostics.HasErrors() { + return resp + } + break + } + + // There is more data to write protoReq := &proto6.WriteStateBytes_RequestChunk{ TypeName: r.TypeName, StateId: r.StateId, - Bytes: r.Bytes, - TotalLength: int64(len(r.Bytes)), + Bytes: chunk, + TotalLength: totalLength, Range: &proto6.StateRange{ - Start: 0, - End: int64(len(r.Bytes)), + Start: int64(totalBytesProcessed), + End: int64(totalBytesProcessed + len(chunk)), }, } - client, err := p.client.WriteStateBytes(ctx) - if err != nil { - resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err)) - return resp - } err = client.Send(protoReq) if err != nil { resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err)) return resp } - protoResp, err := client.CloseAndRecv() - if err != nil { - resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err)) - return resp - } - resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics)) - if resp.Diagnostics.HasErrors() { - return resp - } - } - // TODO: implement chunking for state files larger than chunkSize + // Track progress before next iteration + totalBytesProcessed += len(chunk) + } return resp } diff --git a/internal/plugin6/grpc_provider_test.go b/internal/plugin6/grpc_provider_test.go index cc5a4251cea2..2271b61dc73b 100644 --- a/internal/plugin6/grpc_provider_test.go +++ b/internal/plugin6/grpc_provider_test.go @@ -6,6 +6,7 @@ package plugin6 import ( "bytes" "context" + "errors" "fmt" "io" "testing" @@ -21,6 +22,7 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" "github.com/hashicorp/terraform/internal/addrs" + "github.com/hashicorp/terraform/internal/backend" "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/configs/hcl2shim" "github.com/hashicorp/terraform/internal/plans" @@ -61,6 +63,16 @@ func mockProviderClient(t *testing.T) *mockproto.MockProviderClient { return client } +func mockReadStateBytesClient(t *testing.T) *mockproto.MockProvider_ReadStateBytesClient { + ctrl := gomock.NewController(t) + return mockproto.NewMockProvider_ReadStateBytesClient(ctrl) +} + +func mockWriteStateBytesClient(t *testing.T) *mockproto.MockProvider_WriteStateBytesClient { + ctrl := gomock.NewController(t) + return mockproto.NewMockProvider_WriteStateBytesClient(ctrl) +} + func checkDiags(t *testing.T, d tfdiags.Diagnostics) { t.Helper() if d.HasErrors() { @@ -3481,3 +3493,591 @@ func TestGRPCProvider_DeleteState(t *testing.T) { } }) } + +func TestGRPCProvider_ReadStateBytes(t *testing.T) { + t.Run("can process multiple chunks", func(t *testing.T) { + client := mockProviderClient(t) + p := &GRPCProvider{ + client: client, + ctx: context.Background(), + } + + // Call to ReadStateBytes + // > Assert the arguments received + // > Define the returned mock client + expectedReq := &proto.ReadStateBytes_Request{ + TypeName: "mock_store", + StateId: backend.DefaultStateName, + } + mockReadBytesClient := mockReadStateBytesClient(t) + client.EXPECT().ReadStateBytes( + gomock.Any(), + gomock.Eq(expectedReq), + ).Return(mockReadBytesClient, nil) + + // Define what will be returned by each call to Recv + chunks := []string{"hello", "world"} + totalLength := len(chunks[0]) + len(chunks[1]) + mockResp := map[int]struct { + resp *proto.ReadStateBytes_ResponseChunk + err error + }{ + 0: { + resp: &proto.ReadStateBytes_ResponseChunk{ + Bytes: []byte(chunks[0]), + TotalLength: int64(totalLength), + Range: &proto.StateRange{ + Start: 0, + End: int64(len(chunks[0])), + }, + }, + err: nil, + }, + 1: { + resp: &proto.ReadStateBytes_ResponseChunk{ + Bytes: []byte(chunks[1]), + TotalLength: int64(totalLength), + Range: &proto.StateRange{ + Start: int64(len(chunks[0])), + End: int64(len(chunks[1])), + }, + }, + err: nil, + }, + 2: { + resp: nil, + err: io.EOF, + }, + } + var count int + mockReadBytesClient.EXPECT().Recv().DoAndReturn(func() (*proto.ReadStateBytes_ResponseChunk, error) { + ret := mockResp[count] + count++ + return ret.resp, ret.err + }).Times(3) + + // Act + request := providers.ReadStateBytesRequest{ + TypeName: expectedReq.TypeName, + StateId: expectedReq.StateId, + } + resp := p.ReadStateBytes(request) + + // Assert returned values + checkDiags(t, resp.Diagnostics) + if string(resp.Bytes) != "helloworld" { + t.Fatalf("expected data to be %q, got: %q", "helloworld", string(resp.Bytes)) + } + }) + + t.Run("an error diagnostic is returned when final length does not match expectations", func(t *testing.T) { + client := mockProviderClient(t) + p := &GRPCProvider{ + client: client, + ctx: context.Background(), + } + + // Call to ReadStateBytes + // > Assert the arguments received + // > Define the returned mock client + mockReadBytesClient := mockReadStateBytesClient(t) + expectedReq := &proto.ReadStateBytes_Request{ + TypeName: "mock_store", + StateId: backend.DefaultStateName, + } + client.EXPECT().ReadStateBytes( + gomock.Any(), + gomock.Eq(expectedReq), + ).Return(mockReadBytesClient, nil) + + // Define what will be returned by each call to Recv + chunks := []string{"hello", "world"} + var incorrectLength int64 = 999 + correctLength := len(chunks[0]) + len(chunks[1]) + mockResp := map[int]struct { + resp *proto.ReadStateBytes_ResponseChunk + err error + }{ + 0: { + resp: &proto.ReadStateBytes_ResponseChunk{ + Bytes: []byte(chunks[0]), + TotalLength: incorrectLength, + Range: &proto.StateRange{ + Start: 0, + End: int64(len(chunks[0])), + }, + }, + err: nil, + }, + 1: { + resp: &proto.ReadStateBytes_ResponseChunk{ + Bytes: []byte(chunks[1]), + TotalLength: incorrectLength, + Range: &proto.StateRange{ + Start: int64(len(chunks[0])), + End: int64(len(chunks[1])), + }, + }, + err: nil, + }, + 2: { + resp: nil, + err: io.EOF, + }, + } + var count int + mockReadBytesClient.EXPECT().Recv().DoAndReturn(func() (*proto.ReadStateBytes_ResponseChunk, error) { + ret := mockResp[count] + count++ + return ret.resp, ret.err + }).Times(3) + + // Act + request := providers.ReadStateBytesRequest{ + TypeName: expectedReq.TypeName, + StateId: expectedReq.StateId, + } + resp := p.ReadStateBytes(request) + + // Assert returned values + checkDiagsHasError(t, resp.Diagnostics) + expectedErr := fmt.Sprintf("expected state file of total %d bytes, received %d bytes", incorrectLength, correctLength) + if resp.Diagnostics.Err().Error() != expectedErr { + t.Fatalf("expected error diagnostic %q, but got: %q", expectedErr, resp.Diagnostics.Err()) + } + if len(resp.Bytes) != 0 { + t.Fatalf("expected data to be omitted in error condition, but got: %q", string(resp.Bytes)) + } + }) + + t.Run("an error diagnostic is returned when store type does not exist", func(t *testing.T) { + client := mockProviderClient(t) + p := &GRPCProvider{ + client: client, + ctx: context.Background(), + } + + // In this scenario the method returns before the call to the + // ReadStateBytes RPC, so no mocking needed + + badStoreType := "doesnt_exist" + request := providers.ReadStateBytesRequest{ + TypeName: badStoreType, + StateId: backend.DefaultStateName, + } + + // Act + resp := p.ReadStateBytes(request) + + // Assert returned values + checkDiagsHasError(t, resp.Diagnostics) + expectedErr := fmt.Sprintf("unknown state store type %q", badStoreType) + if resp.Diagnostics.Err().Error() != expectedErr { + t.Fatalf("expected error diagnostic %q, but got: %q", expectedErr, resp.Diagnostics.Err()) + } + if len(resp.Bytes) != 0 { + t.Fatalf("expected data to be omitted in error condition, but got: %q", string(resp.Bytes)) + } + }) + + t.Run("error diagnostics from the provider are returned", func(t *testing.T) { + client := mockProviderClient(t) + p := &GRPCProvider{ + client: client, + ctx: context.Background(), + } + + // Call to ReadStateBytes + // > Assert the arguments received + // > Define the returned mock client + mockReadBytesClient := mockReadStateBytesClient(t) + + expectedReq := &proto.ReadStateBytes_Request{ + TypeName: "mock_store", + StateId: backend.DefaultStateName, + } + client.EXPECT().ReadStateBytes( + gomock.Any(), + gomock.Eq(expectedReq), + ).Return(mockReadBytesClient, nil) + + // Define what will be returned by each call to Recv + mockReadBytesClient.EXPECT().Recv().Return(&proto.ReadStateBytes_ResponseChunk{ + Diagnostics: []*proto.Diagnostic{ + &proto.Diagnostic{ + Severity: proto.Diagnostic_ERROR, + Summary: "Error from test", + Detail: "This error is forced by the test case", + }, + }, + }, nil) + + // Act + request := providers.ReadStateBytesRequest{ + TypeName: expectedReq.TypeName, + StateId: expectedReq.StateId, + } + resp := p.ReadStateBytes(request) + + // Assert returned values + checkDiagsHasError(t, resp.Diagnostics) + expectedErr := "Error from test: This error is forced by the test case" + if resp.Diagnostics.Err().Error() != expectedErr { + t.Fatalf("expected error diagnostic %q, but got: %q", expectedErr, resp.Diagnostics.Err()) + } + if len(resp.Bytes) != 0 { + t.Fatalf("expected data to be omitted in error condition, but got: %q", string(resp.Bytes)) + } + }) + + t.Run("warning diagnostics from the provider are returned", func(t *testing.T) { + client := mockProviderClient(t) + p := &GRPCProvider{ + client: client, + ctx: context.Background(), + } + + // Call to ReadStateBytes + // > Assert the arguments received + // > Define the returned mock client + mockReadBytesClient := mockReadStateBytesClient(t) + + expectedReq := &proto.ReadStateBytes_Request{ + TypeName: "mock_store", + StateId: backend.DefaultStateName, + } + client.EXPECT().ReadStateBytes( + gomock.Any(), + gomock.Eq(expectedReq), + ).Return(mockReadBytesClient, nil) + + // Define what will be returned by each call to Recv + mockReadBytesClient.EXPECT().Recv().Return(&proto.ReadStateBytes_ResponseChunk{ + Diagnostics: []*proto.Diagnostic{ + &proto.Diagnostic{ + Severity: proto.Diagnostic_WARNING, + Summary: "Warning from test", + Detail: "This warning is forced by the test case", + }, + }, + }, nil) + + // Act + request := providers.ReadStateBytesRequest{ + TypeName: expectedReq.TypeName, + StateId: expectedReq.StateId, + } + resp := p.ReadStateBytes(request) + + // Assert returned values + checkDiags(t, resp.Diagnostics) + expectedWarn := "Warning from test: This warning is forced by the test case" + if resp.Diagnostics.ErrWithWarnings().Error() != expectedWarn { + t.Fatalf("expected warning diagnostic %q, but got: %q", expectedWarn, resp.Diagnostics.ErrWithWarnings().Error()) + } + if len(resp.Bytes) != 0 { + t.Fatalf("expected data to be omitted in error condition, but got: %q", string(resp.Bytes)) + } + }) + + t.Run("when reading data, grpc errors are surfaced via diagnostics", func(t *testing.T) { + client := mockProviderClient(t) + p := &GRPCProvider{ + client: client, + ctx: context.Background(), + } + + // Call to ReadStateBytes + // > Assert the arguments received + // > Define the returned mock client + mockClient := mockReadStateBytesClient(t) + expectedReq := &proto.ReadStateBytes_Request{ + TypeName: "mock_store", + StateId: backend.DefaultStateName, + } + client.EXPECT().ReadStateBytes( + gomock.Any(), + gomock.Eq(expectedReq), + ).Return(mockClient, nil) + + mockError := errors.New("grpc error forced in test") + mockClient.EXPECT().Recv().Return(&proto.ReadStateBytes_ResponseChunk{}, mockError) + + // Act + request := providers.ReadStateBytesRequest{ + TypeName: expectedReq.TypeName, + StateId: expectedReq.StateId, + } + resp := p.ReadStateBytes(request) + + // Assert returned values + checkDiagsHasError(t, resp.Diagnostics) + wantErr := fmt.Sprintf("Plugin error: The plugin returned an unexpected error from plugin6.(*GRPCProvider).ReadStateBytes: %s", mockError) + if resp.Diagnostics.Err().Error() != wantErr { + t.Fatalf("expected error diagnostic %q, but got: %q", wantErr, resp.Diagnostics.Err()) + } + if len(resp.Bytes) != 0 { + t.Fatalf("expected data to be omitted in error condition, but got: %q", string(resp.Bytes)) + } + }) +} + +func TestGRPCProvider_WriteStateBytes(t *testing.T) { + t.Run("data smaller than the chunk size is sent in one write action", func(t *testing.T) { + // Less than 4MB + data := []byte("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod" + + "tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud" + + " exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor" + + " in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint" + + " occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.") + + client := mockProviderClient(t) + p := &GRPCProvider{ + client: client, + ctx: context.Background(), + } + + // Assert there will be a call to WriteStateBytes + // & make it return the mock client + mockWriteClient := mockWriteStateBytesClient(t) + client.EXPECT().WriteStateBytes( + gomock.Any(), + gomock.Any(), + ).Return(mockWriteClient, nil) + + // Spy on arguments passed to the Send method of the client + // + // We expect 1 call to Send as the total data + // is less than the chunk size + expectedReq := &proto.WriteStateBytes_RequestChunk{ + TypeName: "mock_store", + StateId: backend.DefaultStateName, + Bytes: data, + TotalLength: int64(len(data)), + Range: &proto.StateRange{ + Start: 0, + End: int64(len(data)), + }, + } + mockWriteClient.EXPECT().Send(gomock.Eq(expectedReq)).Times(1).Return(nil) + mockWriteClient.EXPECT().CloseAndRecv().Times(1).Return(&proto.WriteStateBytes_Response{}, nil) + + // Act + request := providers.WriteStateBytesRequest{ + TypeName: "mock_store", + StateId: backend.DefaultStateName, + Bytes: data, + } + resp := p.WriteStateBytes(request) + + // Assert returned values + checkDiags(t, resp.Diagnostics) + }) + + t.Run("data larger than the chunk size is sent in multiple write actions", func(t *testing.T) { + // Make a buffer that can contain 10 bytes more than the 4MB chunk size + chunkSize := 4 * 1_000_000 + dataBuff := bytes.NewBuffer(make([]byte, 0, chunkSize+10)) + dataBuffCopy := bytes.NewBuffer(make([]byte, 0, chunkSize+10)) + for i := 0; i < (chunkSize + 10); i++ { + dataBuff.WriteByte(63) // We're making 4MB + 10 bytes of question marks because why not + dataBuffCopy.WriteByte(63) // Used to make assertions + } + data := dataBuff.Bytes() + dataFirstChunk := dataBuffCopy.Next(chunkSize) // First write will have a full chunk + dataSecondChunk := dataBuffCopy.Next(chunkSize) // This will be the extra 10 bytes + + client := mockProviderClient(t) + p := &GRPCProvider{ + client: client, + ctx: context.Background(), + } + + // Assert there will be a call to WriteStateBytes + // & make it return the mock client + mockWriteClient := mockWriteStateBytesClient(t) + client.EXPECT().WriteStateBytes( + gomock.Any(), + gomock.Any(), + ).Return(mockWriteClient, nil) + + // Spy on arguments passed to the Send method because data + // is written via separate chunks and separate calls to Send. + // + // We expect 2 calls to Send as the total data + // is 10 bytes larger than the chunk size + req1 := &proto.WriteStateBytes_RequestChunk{ + TypeName: "mock_store", + StateId: backend.DefaultStateName, + Bytes: dataFirstChunk, + TotalLength: int64(len(data)), + Range: &proto.StateRange{ + Start: 0, + End: int64(chunkSize), + }, + } + req2 := &proto.WriteStateBytes_RequestChunk{ + TypeName: "mock_store", + StateId: backend.DefaultStateName, + Bytes: dataSecondChunk, + TotalLength: int64(len(data)), + Range: &proto.StateRange{ + Start: int64(chunkSize), + End: int64(chunkSize + 10), + }, + } + mockWriteClient.EXPECT().Send(gomock.AnyOf(req1, req2)).Times(2).Return(nil) + mockWriteClient.EXPECT().CloseAndRecv().Times(1).Return(&proto.WriteStateBytes_Response{}, nil) + + // Act + request := providers.WriteStateBytesRequest{ + TypeName: "mock_store", + StateId: backend.DefaultStateName, + Bytes: data, + } + resp := p.WriteStateBytes(request) + + // Assert returned values + checkDiags(t, resp.Diagnostics) + }) + + t.Run("when writing data, grpc errors are surfaced via diagnostics", func(t *testing.T) { + client := mockProviderClient(t) + p := &GRPCProvider{ + client: client, + ctx: context.Background(), + } + + // Assert there will be a call to WriteStateBytes + // & make it return the mock client + mockWriteClient := mockWriteStateBytesClient(t) + client.EXPECT().WriteStateBytes( + gomock.Any(), + gomock.Any(), + ).Return(mockWriteClient, nil) + + mockError := errors.New("grpc error forced in test") + mockWriteClient.EXPECT().Send(gomock.Any()).Return(mockError) + + // Act + request := providers.WriteStateBytesRequest{ + TypeName: "mock_store", + StateId: backend.DefaultStateName, + Bytes: []byte("helloworld"), + } + resp := p.WriteStateBytes(request) + + // Assert returned values + checkDiagsHasError(t, resp.Diagnostics) + wantErr := fmt.Sprintf("Plugin error: The plugin returned an unexpected error from plugin6.(*GRPCProvider).WriteStateBytes: %s", mockError) + if resp.Diagnostics.Err().Error() != wantErr { + t.Fatalf("unexpected error, wanted %q, got: %s", wantErr, resp.Diagnostics.Err()) + } + }) + + t.Run("error diagnostics from the provider when closing the connection are returned", func(t *testing.T) { + client := mockProviderClient(t) + p := &GRPCProvider{ + client: client, + ctx: context.Background(), + } + + // Assert there will be a call to WriteStateBytes + // & make it return the mock client + mockWriteClient := mockWriteStateBytesClient(t) + client.EXPECT().WriteStateBytes( + gomock.Any(), + gomock.Any(), + ).Return(mockWriteClient, nil) + + data := []byte("helloworld") + mockReq := &proto.WriteStateBytes_RequestChunk{ + TypeName: "mock_store", + StateId: backend.DefaultStateName, + Bytes: data, + TotalLength: int64(len(data)), + Range: &proto.StateRange{ + Start: 0, + End: int64(len(data)), + }, + } + mockResp := &proto.WriteStateBytes_Response{ + Diagnostics: []*proto.Diagnostic{ + { + Severity: proto.Diagnostic_ERROR, + Summary: "Error from test mock", + Detail: "This error is returned from the test mock", + }, + }, + } + mockWriteClient.EXPECT().Send(gomock.Eq(mockReq)).Times(1).Return(nil) + mockWriteClient.EXPECT().CloseAndRecv().Times(1).Return(mockResp, nil) + + // Act + request := providers.WriteStateBytesRequest{ + TypeName: "mock_store", + StateId: backend.DefaultStateName, + Bytes: data, + } + resp := p.WriteStateBytes(request) + + // Assert returned values + checkDiagsHasError(t, resp.Diagnostics) + if resp.Diagnostics.Err().Error() != "Error from test mock: This error is returned from the test mock" { + t.Fatal() + } + }) + + t.Run("warning diagnostics from the provider when closing the connection are returned", func(t *testing.T) { + client := mockProviderClient(t) + p := &GRPCProvider{ + client: client, + ctx: context.Background(), + } + + // Assert there will be a call to WriteStateBytes + // & make it return the mock client + mockWriteClient := mockWriteStateBytesClient(t) + client.EXPECT().WriteStateBytes( + gomock.Any(), + gomock.Any(), + ).Return(mockWriteClient, nil) + + data := []byte("helloworld") + mockReq := &proto.WriteStateBytes_RequestChunk{ + TypeName: "mock_store", + StateId: backend.DefaultStateName, + Bytes: data, + TotalLength: int64(len(data)), + Range: &proto.StateRange{ + Start: 0, + End: int64(len(data)), + }, + } + mockResp := &proto.WriteStateBytes_Response{ + Diagnostics: []*proto.Diagnostic{ + { + Severity: proto.Diagnostic_WARNING, + Summary: "Warning from test mock", + Detail: "This warning is returned from the test mock", + }, + }, + } + mockWriteClient.EXPECT().Send(gomock.Eq(mockReq)).Times(1).Return(nil) + mockWriteClient.EXPECT().CloseAndRecv().Times(1).Return(mockResp, nil) + + // Act + request := providers.WriteStateBytesRequest{ + TypeName: "mock_store", + StateId: backend.DefaultStateName, + Bytes: data, + } + resp := p.WriteStateBytes(request) + + // Assert returned values + checkDiags(t, resp.Diagnostics) + if resp.Diagnostics.ErrWithWarnings().Error() != "Warning from test mock: This warning is returned from the test mock" { + t.Fatal() + } + }) +} diff --git a/internal/plugin6/mock_proto/generate.go b/internal/plugin6/mock_proto/generate.go index 36cc550081ce..4d7f823b94ae 100644 --- a/internal/plugin6/mock_proto/generate.go +++ b/internal/plugin6/mock_proto/generate.go @@ -1,6 +1,6 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 -//go:generate go tool go.uber.org/mock/mockgen -destination mock.go github.com/hashicorp/terraform/internal/tfplugin6 ProviderClient,Provider_InvokeActionClient +//go:generate go tool go.uber.org/mock/mockgen -destination mock.go github.com/hashicorp/terraform/internal/tfplugin6 ProviderClient,Provider_InvokeActionClient,Provider_ReadStateBytesClient,Provider_WriteStateBytesClient package mock_tfplugin6 diff --git a/internal/plugin6/mock_proto/mock.go b/internal/plugin6/mock_proto/mock.go index 12b0cc79a9ee..b4c9250177a7 100644 --- a/internal/plugin6/mock_proto/mock.go +++ b/internal/plugin6/mock_proto/mock.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/hashicorp/terraform/internal/tfplugin6 (interfaces: ProviderClient,Provider_InvokeActionClient) +// Source: github.com/hashicorp/terraform/internal/tfplugin6 (interfaces: ProviderClient,Provider_InvokeActionClient,Provider_ReadStateBytesClient,Provider_WriteStateBytesClient) // // Generated by this command: // -// mockgen -destination mock.go github.com/hashicorp/terraform/internal/tfplugin6 ProviderClient,Provider_InvokeActionClient +// mockgen -destination mock.go github.com/hashicorp/terraform/internal/tfplugin6 ProviderClient,Provider_InvokeActionClient,Provider_ReadStateBytesClient,Provider_WriteStateBytesClient // // Package mock_tfplugin6 is a generated GoMock package. @@ -824,3 +824,263 @@ func (mr *MockProvider_InvokeActionClientMockRecorder) Trailer() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Trailer", reflect.TypeOf((*MockProvider_InvokeActionClient)(nil).Trailer)) } + +// MockProvider_ReadStateBytesClient is a mock of Provider_ReadStateBytesClient interface. +type MockProvider_ReadStateBytesClient struct { + ctrl *gomock.Controller + recorder *MockProvider_ReadStateBytesClientMockRecorder +} + +// MockProvider_ReadStateBytesClientMockRecorder is the mock recorder for MockProvider_ReadStateBytesClient. +type MockProvider_ReadStateBytesClientMockRecorder struct { + mock *MockProvider_ReadStateBytesClient +} + +// NewMockProvider_ReadStateBytesClient creates a new mock instance. +func NewMockProvider_ReadStateBytesClient(ctrl *gomock.Controller) *MockProvider_ReadStateBytesClient { + mock := &MockProvider_ReadStateBytesClient{ctrl: ctrl} + mock.recorder = &MockProvider_ReadStateBytesClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockProvider_ReadStateBytesClient) EXPECT() *MockProvider_ReadStateBytesClientMockRecorder { + return m.recorder +} + +// CloseSend mocks base method. +func (m *MockProvider_ReadStateBytesClient) CloseSend() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CloseSend") + ret0, _ := ret[0].(error) + return ret0 +} + +// CloseSend indicates an expected call of CloseSend. +func (mr *MockProvider_ReadStateBytesClientMockRecorder) CloseSend() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseSend", reflect.TypeOf((*MockProvider_ReadStateBytesClient)(nil).CloseSend)) +} + +// Context mocks base method. +func (m *MockProvider_ReadStateBytesClient) Context() context.Context { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Context") + ret0, _ := ret[0].(context.Context) + return ret0 +} + +// Context indicates an expected call of Context. +func (mr *MockProvider_ReadStateBytesClientMockRecorder) Context() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Context", reflect.TypeOf((*MockProvider_ReadStateBytesClient)(nil).Context)) +} + +// Header mocks base method. +func (m *MockProvider_ReadStateBytesClient) Header() (metadata.MD, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Header") + ret0, _ := ret[0].(metadata.MD) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Header indicates an expected call of Header. +func (mr *MockProvider_ReadStateBytesClientMockRecorder) Header() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Header", reflect.TypeOf((*MockProvider_ReadStateBytesClient)(nil).Header)) +} + +// Recv mocks base method. +func (m *MockProvider_ReadStateBytesClient) Recv() (*tfplugin6.ReadStateBytes_ResponseChunk, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Recv") + ret0, _ := ret[0].(*tfplugin6.ReadStateBytes_ResponseChunk) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Recv indicates an expected call of Recv. +func (mr *MockProvider_ReadStateBytesClientMockRecorder) Recv() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Recv", reflect.TypeOf((*MockProvider_ReadStateBytesClient)(nil).Recv)) +} + +// RecvMsg mocks base method. +func (m *MockProvider_ReadStateBytesClient) RecvMsg(arg0 any) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RecvMsg", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// RecvMsg indicates an expected call of RecvMsg. +func (mr *MockProvider_ReadStateBytesClientMockRecorder) RecvMsg(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecvMsg", reflect.TypeOf((*MockProvider_ReadStateBytesClient)(nil).RecvMsg), arg0) +} + +// SendMsg mocks base method. +func (m *MockProvider_ReadStateBytesClient) SendMsg(arg0 any) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendMsg", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// SendMsg indicates an expected call of SendMsg. +func (mr *MockProvider_ReadStateBytesClientMockRecorder) SendMsg(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMsg", reflect.TypeOf((*MockProvider_ReadStateBytesClient)(nil).SendMsg), arg0) +} + +// Trailer mocks base method. +func (m *MockProvider_ReadStateBytesClient) Trailer() metadata.MD { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Trailer") + ret0, _ := ret[0].(metadata.MD) + return ret0 +} + +// Trailer indicates an expected call of Trailer. +func (mr *MockProvider_ReadStateBytesClientMockRecorder) Trailer() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Trailer", reflect.TypeOf((*MockProvider_ReadStateBytesClient)(nil).Trailer)) +} + +// MockProvider_WriteStateBytesClient is a mock of Provider_WriteStateBytesClient interface. +type MockProvider_WriteStateBytesClient struct { + ctrl *gomock.Controller + recorder *MockProvider_WriteStateBytesClientMockRecorder +} + +// MockProvider_WriteStateBytesClientMockRecorder is the mock recorder for MockProvider_WriteStateBytesClient. +type MockProvider_WriteStateBytesClientMockRecorder struct { + mock *MockProvider_WriteStateBytesClient +} + +// NewMockProvider_WriteStateBytesClient creates a new mock instance. +func NewMockProvider_WriteStateBytesClient(ctrl *gomock.Controller) *MockProvider_WriteStateBytesClient { + mock := &MockProvider_WriteStateBytesClient{ctrl: ctrl} + mock.recorder = &MockProvider_WriteStateBytesClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockProvider_WriteStateBytesClient) EXPECT() *MockProvider_WriteStateBytesClientMockRecorder { + return m.recorder +} + +// CloseAndRecv mocks base method. +func (m *MockProvider_WriteStateBytesClient) CloseAndRecv() (*tfplugin6.WriteStateBytes_Response, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CloseAndRecv") + ret0, _ := ret[0].(*tfplugin6.WriteStateBytes_Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CloseAndRecv indicates an expected call of CloseAndRecv. +func (mr *MockProvider_WriteStateBytesClientMockRecorder) CloseAndRecv() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseAndRecv", reflect.TypeOf((*MockProvider_WriteStateBytesClient)(nil).CloseAndRecv)) +} + +// CloseSend mocks base method. +func (m *MockProvider_WriteStateBytesClient) CloseSend() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CloseSend") + ret0, _ := ret[0].(error) + return ret0 +} + +// CloseSend indicates an expected call of CloseSend. +func (mr *MockProvider_WriteStateBytesClientMockRecorder) CloseSend() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseSend", reflect.TypeOf((*MockProvider_WriteStateBytesClient)(nil).CloseSend)) +} + +// Context mocks base method. +func (m *MockProvider_WriteStateBytesClient) Context() context.Context { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Context") + ret0, _ := ret[0].(context.Context) + return ret0 +} + +// Context indicates an expected call of Context. +func (mr *MockProvider_WriteStateBytesClientMockRecorder) Context() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Context", reflect.TypeOf((*MockProvider_WriteStateBytesClient)(nil).Context)) +} + +// Header mocks base method. +func (m *MockProvider_WriteStateBytesClient) Header() (metadata.MD, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Header") + ret0, _ := ret[0].(metadata.MD) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Header indicates an expected call of Header. +func (mr *MockProvider_WriteStateBytesClientMockRecorder) Header() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Header", reflect.TypeOf((*MockProvider_WriteStateBytesClient)(nil).Header)) +} + +// RecvMsg mocks base method. +func (m *MockProvider_WriteStateBytesClient) RecvMsg(arg0 any) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RecvMsg", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// RecvMsg indicates an expected call of RecvMsg. +func (mr *MockProvider_WriteStateBytesClientMockRecorder) RecvMsg(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecvMsg", reflect.TypeOf((*MockProvider_WriteStateBytesClient)(nil).RecvMsg), arg0) +} + +// Send mocks base method. +func (m *MockProvider_WriteStateBytesClient) Send(arg0 *tfplugin6.WriteStateBytes_RequestChunk) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Send", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// Send indicates an expected call of Send. +func (mr *MockProvider_WriteStateBytesClientMockRecorder) Send(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Send", reflect.TypeOf((*MockProvider_WriteStateBytesClient)(nil).Send), arg0) +} + +// SendMsg mocks base method. +func (m *MockProvider_WriteStateBytesClient) SendMsg(arg0 any) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendMsg", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// SendMsg indicates an expected call of SendMsg. +func (mr *MockProvider_WriteStateBytesClientMockRecorder) SendMsg(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMsg", reflect.TypeOf((*MockProvider_WriteStateBytesClient)(nil).SendMsg), arg0) +} + +// Trailer mocks base method. +func (m *MockProvider_WriteStateBytesClient) Trailer() metadata.MD { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Trailer") + ret0, _ := ret[0].(metadata.MD) + return ret0 +} + +// Trailer indicates an expected call of Trailer. +func (mr *MockProvider_WriteStateBytesClientMockRecorder) Trailer() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Trailer", reflect.TypeOf((*MockProvider_WriteStateBytesClient)(nil).Trailer)) +} From 28df6638b7f09898cd8b776dd3c68a6628eb0e6e Mon Sep 17 00:00:00 2001 From: Samsondeen <40821565+dsa0x@users.noreply.github.com> Date: Mon, 18 Aug 2025 12:17:59 +0200 Subject: [PATCH 03/60] go: bump version to 1.25 (#37436) --- .changes/v1.14/UPGRADE NOTES-20250814-162650.yaml | 5 +++++ .changes/v1.14/UPGRADE NOTES-20250814-162752.yaml | 5 +++++ .go-version | 2 +- go.mod | 2 +- internal/backend/remote-state/azure/go.mod | 2 +- internal/backend/remote-state/consul/go.mod | 2 +- internal/backend/remote-state/cos/go.mod | 2 +- internal/backend/remote-state/gcs/go.mod | 2 +- internal/backend/remote-state/kubernetes/go.mod | 2 +- internal/backend/remote-state/oci/go.mod | 2 +- internal/backend/remote-state/oss/go.mod | 2 +- internal/backend/remote-state/pg/go.mod | 2 +- internal/backend/remote-state/s3/go.mod | 2 +- internal/legacy/go.mod | 2 +- 14 files changed, 22 insertions(+), 12 deletions(-) create mode 100644 .changes/v1.14/UPGRADE NOTES-20250814-162650.yaml create mode 100644 .changes/v1.14/UPGRADE NOTES-20250814-162752.yaml diff --git a/.changes/v1.14/UPGRADE NOTES-20250814-162650.yaml b/.changes/v1.14/UPGRADE NOTES-20250814-162650.yaml new file mode 100644 index 000000000000..17baa54cca2e --- /dev/null +++ b/.changes/v1.14/UPGRADE NOTES-20250814-162650.yaml @@ -0,0 +1,5 @@ +kind: UPGRADE NOTES +body: 'The parallelism of Terraform operations within container runtimes may be reduced depending on the CPU bandwidth limit setting.' +time: 2025-08-14T16:26:50.569878+02:00 +custom: + Issue: "37436" diff --git a/.changes/v1.14/UPGRADE NOTES-20250814-162752.yaml b/.changes/v1.14/UPGRADE NOTES-20250814-162752.yaml new file mode 100644 index 000000000000..c42a78470ce1 --- /dev/null +++ b/.changes/v1.14/UPGRADE NOTES-20250814-162752.yaml @@ -0,0 +1,5 @@ +kind: UPGRADE NOTES +body: 'Building Terraform 1.14 requires macOS Monterey or later (due to being built on Go 1.25 which imposes these requirements)' +time: 2025-08-14T16:27:52.659896+02:00 +custom: + Issue: "37436" diff --git a/.go-version b/.go-version index 6521720b4145..5e2b95002760 100644 --- a/.go-version +++ b/.go-version @@ -1 +1 @@ -1.24.5 +1.25 diff --git a/go.mod b/go.mod index 07e81397f4f7..d375fdf6e0f4 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/hashicorp/terraform -go 1.24.5 +go 1.25 godebug winsymlink=0 diff --git a/internal/backend/remote-state/azure/go.mod b/internal/backend/remote-state/azure/go.mod index f1d42da2b6fb..1dcd6c42036f 100644 --- a/internal/backend/remote-state/azure/go.mod +++ b/internal/backend/remote-state/azure/go.mod @@ -1,6 +1,6 @@ module github.com/hashicorp/terraform/internal/backend/remote-state/azure -go 1.24.5 +go 1.25 require ( github.com/hashicorp/go-azure-helpers v0.72.0 diff --git a/internal/backend/remote-state/consul/go.mod b/internal/backend/remote-state/consul/go.mod index a6e698e7d096..2f17f4918689 100644 --- a/internal/backend/remote-state/consul/go.mod +++ b/internal/backend/remote-state/consul/go.mod @@ -1,6 +1,6 @@ module github.com/hashicorp/terraform/internal/backend/remote-state/consul -go 1.24.5 +go 1.25 require ( github.com/hashicorp/consul/api v1.32.1 diff --git a/internal/backend/remote-state/cos/go.mod b/internal/backend/remote-state/cos/go.mod index 9826dd704f50..dde5c75a8fea 100644 --- a/internal/backend/remote-state/cos/go.mod +++ b/internal/backend/remote-state/cos/go.mod @@ -1,6 +1,6 @@ module github.com/hashicorp/terraform/internal/backend/remote-state/cos -go 1.24.5 +go 1.25 require ( github.com/hashicorp/terraform v0.0.0-00010101000000-000000000000 diff --git a/internal/backend/remote-state/gcs/go.mod b/internal/backend/remote-state/gcs/go.mod index cf9c8d163b89..d245f8132fb8 100644 --- a/internal/backend/remote-state/gcs/go.mod +++ b/internal/backend/remote-state/gcs/go.mod @@ -1,6 +1,6 @@ module github.com/hashicorp/terraform/internal/backend/remote-state/gcs -go 1.24.5 +go 1.25 require ( cloud.google.com/go/kms v1.15.5 diff --git a/internal/backend/remote-state/kubernetes/go.mod b/internal/backend/remote-state/kubernetes/go.mod index 84c23c3dbf38..1b297122bdca 100644 --- a/internal/backend/remote-state/kubernetes/go.mod +++ b/internal/backend/remote-state/kubernetes/go.mod @@ -1,6 +1,6 @@ module github.com/hashicorp/terraform/internal/backend/remote-state/kubernetes -go 1.24.5 +go 1.25 require ( github.com/hashicorp/terraform v0.0.0-00010101000000-000000000000 diff --git a/internal/backend/remote-state/oci/go.mod b/internal/backend/remote-state/oci/go.mod index e7707ebfc360..e732c9df21cd 100644 --- a/internal/backend/remote-state/oci/go.mod +++ b/internal/backend/remote-state/oci/go.mod @@ -1,6 +1,6 @@ module github.com/hashicorp/terraform/internal/backend/remote-state/oci -go 1.24.5 +go 1.25 require ( github.com/google/go-cmp v0.7.0 diff --git a/internal/backend/remote-state/oss/go.mod b/internal/backend/remote-state/oss/go.mod index 75fac8e1fe7d..22a6d4828790 100644 --- a/internal/backend/remote-state/oss/go.mod +++ b/internal/backend/remote-state/oss/go.mod @@ -1,6 +1,6 @@ module github.com/hashicorp/terraform/internal/backend/remote-state/oss -go 1.24.5 +go 1.25 require ( github.com/aliyun/alibaba-cloud-sdk-go v1.61.1501 diff --git a/internal/backend/remote-state/pg/go.mod b/internal/backend/remote-state/pg/go.mod index d38e0307de49..ea3e3f7c114a 100644 --- a/internal/backend/remote-state/pg/go.mod +++ b/internal/backend/remote-state/pg/go.mod @@ -1,6 +1,6 @@ module github.com/hashicorp/terraform/internal/backend/remote-state/pg -go 1.24.5 +go 1.25 require ( github.com/hashicorp/go-uuid v1.0.3 diff --git a/internal/backend/remote-state/s3/go.mod b/internal/backend/remote-state/s3/go.mod index dbe60197b5d0..b3651d1a996a 100644 --- a/internal/backend/remote-state/s3/go.mod +++ b/internal/backend/remote-state/s3/go.mod @@ -1,6 +1,6 @@ module github.com/hashicorp/terraform/internal/backend/remote-state/s3 -go 1.24.5 +go 1.25 require ( github.com/aws/aws-sdk-go-v2 v1.36.0 diff --git a/internal/legacy/go.mod b/internal/legacy/go.mod index 928782be4757..21af27b3c9e6 100644 --- a/internal/legacy/go.mod +++ b/internal/legacy/go.mod @@ -2,7 +2,7 @@ module github.com/hashicorp/terraform/internal/legacy replace github.com/hashicorp/terraform => ../.. -go 1.24.5 +go 1.25 require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc From 29a82e93fa4622849349ceeaa5893792d0fc48bc Mon Sep 17 00:00:00 2001 From: Sarah French <15078782+SarahFrench@users.noreply.github.com> Date: Mon, 18 Aug 2025 11:20:18 +0100 Subject: [PATCH 04/60] PSS: Add alternative, experimental version of `init` command that downloads providers in two stages (#37350) * Add forked version of `run` logic that's only used if experiments are enabled * Reorder actions in experimental init - load in full config before configuring the backend. * Add getProvidersFromConfig method, initially as an exact copy of getProviders * Make getProvidersFromConfig not use state to get providers * Add `appendLockedDependencies` method to `Meta` to allow multi-phase saving to the dep locks file * Update experimental init to use new getProvidersFromConfig method * Add new getProvidersFromState method that only accepts state information as input for getting providers. Use in experimental init and append values to existing deps lock file * Update messages sent to view about provider download phases * Change init to save updates to the deps lock file only once * Make Terraform output report that a lock file _will_ be made after providers are determined from config * Remove use of `ProviderDownloadOutcome`s * Move repeated code into separate method * Change provider download approach: determine if locks changed at point of attempting to update the lockfile, keep record of incomplete providers inside init command struct * Refactor `mergeLockedDependencies` and update test * Add comments to provider download methods * Fix issue where incorrect message ouput to view when downloading providers * Update `mergeLockedDependencies` method to be more generic * Update `getProvidersFromState` method to receive in-progress config locks and merge those with any locks on file. This allows re-use of providers downloaded by `getProvidersFromConfig` in the same init command * Fix config for `TestInit_stateStoreBlockIsExperimental` * Improve testing of mergeLockedDependencies; state locks are always missing version constraints * Add tests for 2 phase provider download * Add test case to cover use of the `-upgrade` flag * Change the message shown when a provider is reused during the second provider download step. When downloading providers described only in the state then the provider may already be downloaded from a previous init (i.e. is recorded in the deps lock file) or downloaded during step 1 of provider download. The message here needs to cover both potential scenarios. * Update mergeLockedDependencies comment * fix: completely remove use of upgrade flag in getProvidersFromState * Fix: avoid nil pointer errors by returning an empty collection of locks when there is no state * Fix: use state store data only in diagnostic * Change how we make PSS experimental - avoid relying on a package level variable that causes tests to interact. * Remove full-stop in view message, update tests * Update span names to be unique * Re-add lost early returns * Remove unused view messages * Add comments to new view messages --- internal/command/cloud_test.go | 2 +- internal/command/init.go | 586 +++++++++++++++++- internal/command/init_run.go | 17 + internal/command/init_run_experiment.go | 346 +++++++++++ internal/command/init_test.go | 124 ++++ internal/command/meta_dependencies.go | 37 ++ internal/command/meta_dependencies_test.go | 151 +++++ .../main.tf | 14 + .../state-using-random-provider.tfstate | 28 + .../config-and-state-same-providers/main.tf | 14 + .../state-using-random-provider.tfstate | 28 + .../.terraform.lock.hcl | 6 + .../config-state-file-and-lockfile/main.tf | 14 + .../state-using-random-provider.tfstate | 28 + .../state-file-only/main.tf | 5 + .../state-using-random-provider.tfstate | 28 + .../testdata/init-with-state-store/main.tf | 8 +- internal/command/views/init.go | 20 + internal/configs/parser_config.go | 15 +- 19 files changed, 1454 insertions(+), 17 deletions(-) create mode 100644 internal/command/init_run_experiment.go create mode 100644 internal/command/meta_dependencies_test.go create mode 100644 internal/command/testdata/init-provider-download/config-and-state-different-providers/main.tf create mode 100644 internal/command/testdata/init-provider-download/config-and-state-different-providers/state-using-random-provider.tfstate create mode 100644 internal/command/testdata/init-provider-download/config-and-state-same-providers/main.tf create mode 100644 internal/command/testdata/init-provider-download/config-and-state-same-providers/state-using-random-provider.tfstate create mode 100644 internal/command/testdata/init-provider-download/config-state-file-and-lockfile/.terraform.lock.hcl create mode 100644 internal/command/testdata/init-provider-download/config-state-file-and-lockfile/main.tf create mode 100644 internal/command/testdata/init-provider-download/config-state-file-and-lockfile/state-using-random-provider.tfstate create mode 100644 internal/command/testdata/init-provider-download/state-file-only/main.tf create mode 100644 internal/command/testdata/init-provider-download/state-file-only/state-using-random-provider.tfstate diff --git a/internal/command/cloud_test.go b/internal/command/cloud_test.go index 3661e4b98b64..cf59acfbdf5d 100644 --- a/internal/command/cloud_test.go +++ b/internal/command/cloud_test.go @@ -135,7 +135,7 @@ func TestCloud_withBackendConfig(t *testing.T) { // Initialize the backend ic := &InitCommand{ - Meta{ + Meta: Meta{ Ui: ui, View: view, testingOverrides: metaOverridesForProvider(testProvider()), diff --git a/internal/command/init.go b/internal/command/init.go index 3618f6f287b4..114ce62a291b 100644 --- a/internal/command/init.go +++ b/internal/command/init.go @@ -28,8 +28,10 @@ import ( "github.com/hashicorp/terraform/internal/command/views" "github.com/hashicorp/terraform/internal/configs" "github.com/hashicorp/terraform/internal/configs/configschema" + "github.com/hashicorp/terraform/internal/depsfile" "github.com/hashicorp/terraform/internal/didyoumean" "github.com/hashicorp/terraform/internal/getproviders" + "github.com/hashicorp/terraform/internal/getproviders/providerreqs" "github.com/hashicorp/terraform/internal/providercache" "github.com/hashicorp/terraform/internal/states" "github.com/hashicorp/terraform/internal/tfdiags" @@ -40,6 +42,8 @@ import ( // module and clones it to the working directory. type InitCommand struct { Meta + + incompleteProviders []string } func (c *InitCommand) Run(args []string) int { @@ -65,7 +69,7 @@ func (c *InitCommand) Run(args []string) int { } if c.Meta.AllowExperimentalFeatures && initArgs.EnablePssExperiment { // TODO(SarahFrench/radeksimko): Remove forked init logic once feature is no longer experimental - panic("This experiment is not available yet") + return c.runPssInit(initArgs, view) } else { return c.run(initArgs, view) } @@ -209,7 +213,7 @@ func (c *InitCommand) initBackend(ctx context.Context, root *configs.Module, ext root.StateStore.ProviderAddr, root.StateStore.Type, ), - Subject: &root.Backend.TypeRange, + Subject: &root.StateStore.TypeRange, }) return nil, true, diags } @@ -358,8 +362,11 @@ the backend configuration is present and valid. return back, true, diags } -// Load the complete module tree, and fetch any missing providers. -// This method outputs its own Ui. +// getProviders determines what providers are required given configuration and state data. The method downloads any missing providers +// and replaces the contents of the dependency lock file if any changes happen. +// The calling code is expected to have loaded the complete module tree and read the state file, and passes that data into this method. +// +// This method outputs to the provided view. The returned `output` boolean lets calling code know if anything has been rendered via the view. func (c *InitCommand) getProviders(ctx context.Context, config *configs.Config, state *states.State, upgrade bool, pluginDirs []string, flagLockfile string, view views.Init) (output, abort bool, diags tfdiags.Diagnostics) { ctx, span := tracer.Start(ctx, "install providers") defer span.End() @@ -808,6 +815,577 @@ func (c *InitCommand) getProviders(ctx context.Context, config *configs.Config, return true, false, diags } +// getProvidersFromConfig determines what providers are required by the given configuration data. +// The method downloads any missing providers that aren't already downloaded and then returns +// dependency lock data based on the configuration. +// The dependency lock file itself isn't updated here. +func (c *InitCommand) getProvidersFromConfig(ctx context.Context, config *configs.Config, upgrade bool, pluginDirs []string, flagLockfile string, view views.Init) (output bool, resultingLocks *depsfile.Locks, diags tfdiags.Diagnostics) { + ctx, span := tracer.Start(ctx, "install providers from config") + defer span.End() + + // Dev overrides cause the result of "terraform init" to be irrelevant for + // any overridden providers, so we'll warn about it to avoid later + // confusion when Terraform ends up using a different provider than the + // lock file called for. + diags = diags.Append(c.providerDevOverrideInitWarnings()) + + // Collect the provider dependencies from the configuration. + reqs, hclDiags := config.ProviderRequirements() + diags = diags.Append(hclDiags) + if hclDiags.HasErrors() { + return false, nil, diags + } + + for providerAddr := range reqs { + if providerAddr.IsLegacy() { + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Invalid legacy provider address", + fmt.Sprintf( + "This configuration or its associated state refers to the unqualified provider %q.\n\nYou must complete the Terraform 0.13 upgrade process before upgrading to later versions.", + providerAddr.Type, + ), + )) + } + } + if diags.HasErrors() { + return false, nil, diags + } + + var inst *providercache.Installer + if len(pluginDirs) == 0 { + // By default we use a source that looks for providers in all of the + // standard locations, possibly customized by the user in CLI config. + inst = c.providerInstaller() + } else { + // If the user passes at least one -plugin-dir then that circumvents + // the usual sources and forces Terraform to consult only the given + // directories. Anything not available in one of those directories + // is not available for installation. + source := c.providerCustomLocalDirectorySource(pluginDirs) + inst = c.providerInstallerCustomSource(source) + + // The default (or configured) search paths are logged earlier, in provider_source.go + // Log that those are being overridden by the `-plugin-dir` command line options + log.Println("[DEBUG] init: overriding provider plugin search paths") + log.Printf("[DEBUG] will search for provider plugins in %s", pluginDirs) + } + + evts := c.prepareInstallerEvents(ctx, reqs, diags, inst, view, views.InitializingProviderPluginFromConfigMessage, views.ReusingPreviousVersionInfo) + ctx = evts.OnContext(ctx) + + mode := providercache.InstallNewProvidersOnly + if upgrade { + if flagLockfile == "readonly" { + diags = diags.Append(fmt.Errorf("The -upgrade flag conflicts with -lockfile=readonly.")) + view.Diagnostics(diags) + return true, nil, diags + } + + mode = providercache.InstallUpgrades + } + + // Previous locks from dep locks file are needed so we don't re-download any providers + previousLocks, moreDiags := c.lockedDependencies() + diags = diags.Append(moreDiags) + if diags.HasErrors() { + return false, nil, diags + } + + // Determine which required providers are already downloaded, and download any + // new providers or newer versions of providers + configLocks, err := inst.EnsureProviderVersions(ctx, previousLocks, reqs, mode) + if ctx.Err() == context.Canceled { + diags = diags.Append(fmt.Errorf("Provider installation was canceled by an interrupt signal.")) + view.Diagnostics(diags) + return true, nil, diags + } + if err != nil { + // The errors captured in "err" should be redundant with what we + // received via the InstallerEvents callbacks above, so we'll + // just return those as long as we have some. + if !diags.HasErrors() { + diags = diags.Append(err) + } + + return true, nil, diags + } + + return true, configLocks, diags +} + +// getProvidersFromState determines what providers are required by the given state data. +// The method downloads any missing providers that aren't already downloaded and then returns +// dependency lock data based on the state. +// The calling code is assumed to have already called getProvidersFromConfig, which is used to +// supply the configLocks argument. +// The dependency lock file itself isn't updated here. +func (c *InitCommand) getProvidersFromState(ctx context.Context, state *states.State, configLocks *depsfile.Locks, upgrade bool, pluginDirs []string, flagLockfile string, view views.Init) (output bool, resultingLocks *depsfile.Locks, diags tfdiags.Diagnostics) { + ctx, span := tracer.Start(ctx, "install providers from state") + defer span.End() + + // Dev overrides cause the result of "terraform init" to be irrelevant for + // any overridden providers, so we'll warn about it to avoid later + // confusion when Terraform ends up using a different provider than the + // lock file called for. + diags = diags.Append(c.providerDevOverrideInitWarnings()) + + if state == nil { + // if there is no state there are no providers to get + return true, depsfile.NewLocks(), nil + } + reqs := state.ProviderRequirements() + + for providerAddr := range reqs { + if providerAddr.IsLegacy() { + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Invalid legacy provider address", + fmt.Sprintf( + "This configuration or its associated state refers to the unqualified provider %q.\n\nYou must complete the Terraform 0.13 upgrade process before upgrading to later versions.", + providerAddr.Type, + ), + )) + } + } + if diags.HasErrors() { + return false, nil, diags + } + + // The locks below are used to avoid re-downloading any providers in the + // second download step. + // We combine any locks from the dependency lock file and locks identified + // from the configuration + var moreDiags tfdiags.Diagnostics + previousLocks, moreDiags := c.lockedDependencies() + diags = diags.Append(moreDiags) + if diags.HasErrors() { + return false, nil, diags + } + inProgressLocks := c.mergeLockedDependencies(configLocks, previousLocks) + + var inst *providercache.Installer + if len(pluginDirs) == 0 { + // By default we use a source that looks for providers in all of the + // standard locations, possibly customized by the user in CLI config. + inst = c.providerInstaller() + } else { + // If the user passes at least one -plugin-dir then that circumvents + // the usual sources and forces Terraform to consult only the given + // directories. Anything not available in one of those directories + // is not available for installation. + source := c.providerCustomLocalDirectorySource(pluginDirs) + inst = c.providerInstallerCustomSource(source) + + // The default (or configured) search paths are logged earlier, in provider_source.go + // Log that those are being overridden by the `-plugin-dir` command line options + log.Println("[DEBUG] init: overriding provider plugin search paths") + log.Printf("[DEBUG] will search for provider plugins in %s", pluginDirs) + } + + // Because we're currently just streaming a series of events sequentially + // into the terminal, we're showing only a subset of the events to keep + // things relatively concise. Later it'd be nice to have a progress UI + // where statuses update in-place, but we can't do that as long as we + // are shimming our vt100 output to the legacy console API on Windows. + evts := c.prepareInstallerEvents(ctx, reqs, diags, inst, view, views.InitializingProviderPluginFromStateMessage, views.ReusingVersionIdentifiedFromConfig) + ctx = evts.OnContext(ctx) + + mode := providercache.InstallNewProvidersOnly + + // We don't handle upgrade flags here, i.e. what happens at this point in getProvidersFromConfig: + // > We cannot upgrade a provider used only by the state, as there are no version constraints in state. + // > Given the overlap between providers in the config and state, using the upgrade mode here + // would remove the effects of version constraints from the config. + // > Any validation of CLI flag usage is already done in getProvidersFromConfig + + newLocks, err := inst.EnsureProviderVersions(ctx, inProgressLocks, reqs, mode) + if ctx.Err() == context.Canceled { + diags = diags.Append(fmt.Errorf("Provider installation was canceled by an interrupt signal.")) + view.Diagnostics(diags) + return true, nil, diags + } + if err != nil { + // The errors captured in "err" should be redundant with what we + // received via the InstallerEvents callbacks above, so we'll + // just return those as long as we have some. + if !diags.HasErrors() { + diags = diags.Append(err) + } + + return true, nil, diags + } + + return true, newLocks, diags +} + +// saveDependencyLockFile overwrites the contents of the dependency lock file. +// The calling code is expected to provide the previous locks (if any) and the two sets of locks determined from +// configuration and state data. +func (c *InitCommand) saveDependencyLockFile(previousLocks, configLocks, stateLocks *depsfile.Locks, flagLockfile string, view views.Init) (output bool, diags tfdiags.Diagnostics) { + + // Get the combination of config and state locks + newLocks := c.mergeLockedDependencies(configLocks, stateLocks) + + // If the provider dependencies have changed since the last run then we'll + // say a little about that in case the reader wasn't expecting a change. + // (When we later integrate module dependencies into the lock file we'll + // probably want to refactor this so that we produce one lock-file related + // message for all changes together, but this is here for now just because + // it's the smallest change relative to what came before it, which was + // a hidden JSON file specifically for tracking providers.) + if !newLocks.Equal(previousLocks) { + // if readonly mode + if flagLockfile == "readonly" { + // check if required provider dependencies change + if !newLocks.EqualProviderAddress(previousLocks) { + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + `Provider dependency changes detected`, + `Changes to the required provider dependencies were detected, but the lock file is read-only. To use and record these requirements, run "terraform init" without the "-lockfile=readonly" flag.`, + )) + return output, diags + } + // suppress updating the file to record any new information it learned, + // such as a hash using a new scheme. + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Warning, + `Provider lock file not updated`, + `Changes to the provider selections were detected, but not saved in the .terraform.lock.hcl file. To record these selections, run "terraform init" without the "-lockfile=readonly" flag.`, + )) + return output, diags + } + // Jump in here and add a warning if any of the providers are incomplete. + if len(c.incompleteProviders) > 0 { + // We don't really care about the order here, we just want the + // output to be deterministic. + sort.Slice(c.incompleteProviders, func(i, j int) bool { + return c.incompleteProviders[i] < c.incompleteProviders[j] + }) + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Warning, + incompleteLockFileInformationHeader, + fmt.Sprintf( + incompleteLockFileInformationBody, + strings.Join(c.incompleteProviders, "\n - "), + getproviders.CurrentPlatform.String()))) + } + if previousLocks.Empty() { + // A change from empty to non-empty is special because it suggests + // we're running "terraform init" for the first time against a + // new configuration. In that case we'll take the opportunity to + // say a little about what the dependency lock file is, for new + // users or those who are upgrading from a previous Terraform + // version that didn't have dependency lock files. + view.Output(views.LockInfo) + output = true + } else { + view.Output(views.DependenciesLockChangesInfo) + output = true + } + lockFileDiags := c.replaceLockedDependencies(newLocks) + diags = diags.Append(lockFileDiags) + } + return output, diags +} + +// prepareInstallerEvents returns an instance of *providercache.InstallerEvents. This struct defines callback functions that will be executed +// when a specific type of event occurs during provider installation. +// The calling code needs to provide a tfdiags.Diagnostics collection, so that provider installation code returns diags to the calling code using closures +func (c *InitCommand) prepareInstallerEvents(ctx context.Context, reqs providerreqs.Requirements, diags tfdiags.Diagnostics, inst *providercache.Installer, view views.Init, initMsg views.InitMessageCode, reuseMsg views.InitMessageCode) *providercache.InstallerEvents { + + // Because we're currently just streaming a series of events sequentially + // into the terminal, we're showing only a subset of the events to keep + // things relatively concise. Later it'd be nice to have a progress UI + // where statuses update in-place, but we can't do that as long as we + // are shimming our vt100 output to the legacy console API on Windows. + events := &providercache.InstallerEvents{ + PendingProviders: func(reqs map[addrs.Provider]getproviders.VersionConstraints) { + view.Output(initMsg) + }, + ProviderAlreadyInstalled: func(provider addrs.Provider, selectedVersion getproviders.Version) { + view.LogInitMessage(views.ProviderAlreadyInstalledMessage, provider.ForDisplay(), selectedVersion) + }, + BuiltInProviderAvailable: func(provider addrs.Provider) { + view.LogInitMessage(views.BuiltInProviderAvailableMessage, provider.ForDisplay()) + }, + BuiltInProviderFailure: func(provider addrs.Provider, err error) { + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Invalid dependency on built-in provider", + fmt.Sprintf("Cannot use %s: %s.", provider.ForDisplay(), err), + )) + }, + QueryPackagesBegin: func(provider addrs.Provider, versionConstraints getproviders.VersionConstraints, locked bool) { + if locked { + view.LogInitMessage(reuseMsg, provider.ForDisplay()) + } else { + if len(versionConstraints) > 0 { + view.LogInitMessage(views.FindingMatchingVersionMessage, provider.ForDisplay(), getproviders.VersionConstraintsString(versionConstraints)) + } else { + view.LogInitMessage(views.FindingLatestVersionMessage, provider.ForDisplay()) + } + } + }, + LinkFromCacheBegin: func(provider addrs.Provider, version getproviders.Version, cacheRoot string) { + view.LogInitMessage(views.UsingProviderFromCacheDirInfo, provider.ForDisplay(), version) + }, + FetchPackageBegin: func(provider addrs.Provider, version getproviders.Version, location getproviders.PackageLocation) { + view.LogInitMessage(views.InstallingProviderMessage, provider.ForDisplay(), version) + }, + QueryPackagesFailure: func(provider addrs.Provider, err error) { + switch errorTy := err.(type) { + case getproviders.ErrProviderNotFound: + sources := errorTy.Sources + displaySources := make([]string, len(sources)) + for i, source := range sources { + displaySources[i] = fmt.Sprintf(" - %s", source) + } + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Failed to query available provider packages", + fmt.Sprintf("Could not retrieve the list of available versions for provider %s: %s\n\n%s", + provider.ForDisplay(), err, strings.Join(displaySources, "\n"), + ), + )) + case getproviders.ErrRegistryProviderNotKnown: + // We might be able to suggest an alternative provider to use + // instead of this one. + suggestion := fmt.Sprintf("\n\nAll modules should specify their required_providers so that external consumers will get the correct providers when using a module. To see which modules are currently depending on %s, run the following command:\n terraform providers", provider.ForDisplay()) + alternative := getproviders.MissingProviderSuggestion(ctx, provider, inst.ProviderSource(), reqs) + if alternative != provider { + suggestion = fmt.Sprintf( + "\n\nDid you intend to use %s? If so, you must specify that source address in each module which requires that provider. To see which modules are currently depending on %s, run the following command:\n terraform providers", + alternative.ForDisplay(), provider.ForDisplay(), + ) + } + + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Failed to query available provider packages", + fmt.Sprintf("Could not retrieve the list of available versions for provider %s: %s%s", + provider.ForDisplay(), err, suggestion, + ), + )) + case getproviders.ErrHostNoProviders: + switch { + case errorTy.Hostname == svchost.Hostname("github.com") && !errorTy.HasOtherVersion: + // If a user copies the URL of a GitHub repository into + // the source argument and removes the schema to make it + // provider-address-shaped then that's one way we can end up + // here. We'll use a specialized error message in anticipation + // of that mistake. We only do this if github.com isn't a + // provider registry, to allow for the (admittedly currently + // rather unlikely) possibility that github.com starts being + // a real Terraform provider registry in the future. + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Invalid provider registry host", + fmt.Sprintf("The given source address %q specifies a GitHub repository rather than a Terraform provider. Refer to the documentation of the provider to find the correct source address to use.", + provider.String(), + ), + )) + + case errorTy.HasOtherVersion: + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Invalid provider registry host", + fmt.Sprintf("The host %q given in provider source address %q does not offer a Terraform provider registry that is compatible with this Terraform version, but it may be compatible with a different Terraform version.", + errorTy.Hostname, provider.String(), + ), + )) + + default: + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Invalid provider registry host", + fmt.Sprintf("The host %q given in provider source address %q does not offer a Terraform provider registry.", + errorTy.Hostname, provider.String(), + ), + )) + } + + case getproviders.ErrRequestCanceled: + // We don't attribute cancellation to any particular operation, + // but rather just emit a single general message about it at + // the end, by checking ctx.Err(). + + default: + suggestion := fmt.Sprintf("\n\nTo see which modules are currently depending on %s and what versions are specified, run the following command:\n terraform providers", provider.ForDisplay()) + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Failed to query available provider packages", + fmt.Sprintf("Could not retrieve the list of available versions for provider %s: %s%s", + provider.ForDisplay(), err, suggestion, + ), + )) + } + + }, + QueryPackagesWarning: func(provider addrs.Provider, warnings []string) { + displayWarnings := make([]string, len(warnings)) + for i, warning := range warnings { + displayWarnings[i] = fmt.Sprintf("- %s", warning) + } + + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Warning, + "Additional provider information from registry", + fmt.Sprintf("The remote registry returned warnings for %s:\n%s", + provider.String(), + strings.Join(displayWarnings, "\n"), + ), + )) + }, + LinkFromCacheFailure: func(provider addrs.Provider, version getproviders.Version, err error) { + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Failed to install provider from shared cache", + fmt.Sprintf("Error while importing %s v%s from the shared cache directory: %s.", provider.ForDisplay(), version, err), + )) + }, + FetchPackageFailure: func(provider addrs.Provider, version getproviders.Version, err error) { + const summaryIncompatible = "Incompatible provider version" + switch err := err.(type) { + case getproviders.ErrProtocolNotSupported: + closestAvailable := err.Suggestion + switch { + case closestAvailable == getproviders.UnspecifiedVersion: + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + summaryIncompatible, + fmt.Sprintf(errProviderVersionIncompatible, provider.String()), + )) + case version.GreaterThan(closestAvailable): + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + summaryIncompatible, + fmt.Sprintf(providerProtocolTooNew, provider.ForDisplay(), + version, tfversion.String(), closestAvailable, closestAvailable, + getproviders.VersionConstraintsString(reqs[provider]), + ), + )) + default: // version is less than closestAvailable + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + summaryIncompatible, + fmt.Sprintf(providerProtocolTooOld, provider.ForDisplay(), + version, tfversion.String(), closestAvailable, closestAvailable, + getproviders.VersionConstraintsString(reqs[provider]), + ), + )) + } + case getproviders.ErrPlatformNotSupported: + switch { + case err.MirrorURL != nil: + // If we're installing from a mirror then it may just be + // the mirror lacking the package, rather than it being + // unavailable from upstream. + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + summaryIncompatible, + fmt.Sprintf( + "Your chosen provider mirror at %s does not have a %s v%s package available for your current platform, %s.\n\nProvider releases are separate from Terraform CLI releases, so this provider might not support your current platform. Alternatively, the mirror itself might have only a subset of the plugin packages available in the origin registry, at %s.", + err.MirrorURL, err.Provider, err.Version, err.Platform, + err.Provider.Hostname, + ), + )) + default: + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + summaryIncompatible, + fmt.Sprintf( + "Provider %s v%s does not have a package available for your current platform, %s.\n\nProvider releases are separate from Terraform CLI releases, so not all providers are available for all platforms. Other versions of this provider may have different platforms supported.", + err.Provider, err.Version, err.Platform, + ), + )) + } + + case getproviders.ErrRequestCanceled: + // We don't attribute cancellation to any particular operation, + // but rather just emit a single general message about it at + // the end, by checking ctx.Err(). + + default: + // We can potentially end up in here under cancellation too, + // in spite of our getproviders.ErrRequestCanceled case above, + // because not all of the outgoing requests we do under the + // "fetch package" banner are source metadata requests. + // In that case we will emit a redundant error here about + // the request being cancelled, but we'll still detect it + // as a cancellation after the installer returns and do the + // normal cancellation handling. + + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Failed to install provider", + fmt.Sprintf("Error while installing %s v%s: %s", provider.ForDisplay(), version, err), + )) + } + }, + FetchPackageSuccess: func(provider addrs.Provider, version getproviders.Version, localDir string, authResult *getproviders.PackageAuthenticationResult) { + var keyID string + if authResult != nil && authResult.ThirdPartySigned() { + keyID = authResult.KeyID + } + if keyID != "" { + keyID = view.PrepareMessage(views.KeyID, keyID) + } + + view.LogInitMessage(views.InstalledProviderVersionInfo, provider.ForDisplay(), version, authResult, keyID) + }, + ProvidersLockUpdated: func(provider addrs.Provider, version getproviders.Version, localHashes []getproviders.Hash, signedHashes []getproviders.Hash, priorHashes []getproviders.Hash) { + // We're going to use this opportunity to track if we have any + // "incomplete" installs of providers. An incomplete install is + // when we are only going to write the local hashes into our lock + // file which means a `terraform init` command will fail in future + // when used on machines of a different architecture. + // + // We want to print a warning about this. + + if len(signedHashes) > 0 { + // If we have any signedHashes hashes then we don't worry - as + // we know we retrieved all available hashes for this version + // anyway. + return + } + + // If local hashes and prior hashes are exactly the same then + // it means we didn't record any signed hashes previously, and + // we know we're not adding any extra in now (because we already + // checked the signedHashes), so that's a problem. + // + // In the actual check here, if we have any priorHashes and those + // hashes are not the same as the local hashes then we're going to + // accept that this provider has been configured correctly. + if len(priorHashes) > 0 && !reflect.DeepEqual(localHashes, priorHashes) { + return + } + + // Now, either signedHashes is empty, or priorHashes is exactly the + // same as our localHashes which means we never retrieved the + // signedHashes previously. + // + // Either way, this is bad. Let's complain/warn. + c.incompleteProviders = append(c.incompleteProviders, provider.ForDisplay()) + }, + ProvidersFetched: func(authResults map[addrs.Provider]*getproviders.PackageAuthenticationResult) { + thirdPartySigned := false + for _, authResult := range authResults { + if authResult.ThirdPartySigned() { + thirdPartySigned = true + break + } + } + if thirdPartySigned { + view.LogInitMessage(views.PartnerAndCommunityProvidersMessage) + } + }, + } + + return events +} + // backendConfigOverrideBody interprets the raw values of -backend-config // arguments into a hcl Body that should override the backend settings given // in the configuration. diff --git a/internal/command/init_run.go b/internal/command/init_run.go index 5d3e6d172a65..1e866bc63c9b 100644 --- a/internal/command/init_run.go +++ b/internal/command/init_run.go @@ -8,6 +8,7 @@ import ( "fmt" "strings" + "github.com/hashicorp/hcl/v2" "github.com/hashicorp/terraform/internal/backend" "github.com/hashicorp/terraform/internal/cloud" "github.com/hashicorp/terraform/internal/command/arguments" @@ -141,6 +142,22 @@ func (c *InitCommand) run(initArgs *arguments.Init, view views.Init) int { return 1 } + if !c.Meta.AllowExperimentalFeatures && rootModEarly.StateStore != nil { + // TODO(SarahFrench/radeksimko) - remove when this feature isn't experimental. + // This approach for making the feature experimental is required + // to let us assert the feature is gated behind an experiment in tests. + // See https://github.com/hashicorp/terraform/pull/37350#issuecomment-3168555619 + diags = diags.Append(earlyConfDiags) + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Unsupported block type", + Detail: "Blocks of type \"state_store\" are not expected here.", + Subject: &rootModEarly.StateStore.TypeRange, + }) + view.Diagnostics(diags) + + return 1 + } var back backend.Backend diff --git a/internal/command/init_run_experiment.go b/internal/command/init_run_experiment.go new file mode 100644 index 000000000000..b62b40f515f9 --- /dev/null +++ b/internal/command/init_run_experiment.go @@ -0,0 +1,346 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package command + +import ( + "errors" + "fmt" + "strings" + + "github.com/hashicorp/terraform/internal/backend" + "github.com/hashicorp/terraform/internal/cloud" + "github.com/hashicorp/terraform/internal/command/arguments" + "github.com/hashicorp/terraform/internal/command/views" + "github.com/hashicorp/terraform/internal/configs" + "github.com/hashicorp/terraform/internal/states" + "github.com/hashicorp/terraform/internal/terraform" + "github.com/hashicorp/terraform/internal/tfdiags" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" +) + +// `runPssInit` is an altered version of the logic in `run` that contains changes +// related to the PSS project. This is used by the (InitCommand.Run method only if Terraform has +// experimental features enabled. +func (c *InitCommand) runPssInit(initArgs *arguments.Init, view views.Init) int { + var diags tfdiags.Diagnostics + + c.forceInitCopy = initArgs.ForceInitCopy + c.Meta.stateLock = initArgs.StateLock + c.Meta.stateLockTimeout = initArgs.StateLockTimeout + c.reconfigure = initArgs.Reconfigure + c.migrateState = initArgs.MigrateState + c.Meta.ignoreRemoteVersion = initArgs.IgnoreRemoteVersion + c.Meta.input = initArgs.InputEnabled + c.Meta.targetFlags = initArgs.TargetFlags + c.Meta.compactWarnings = initArgs.CompactWarnings + + varArgs := initArgs.Vars.All() + items := make([]arguments.FlagNameValue, len(varArgs)) + for i := range varArgs { + items[i].Name = varArgs[i].Name + items[i].Value = varArgs[i].Value + } + c.Meta.variableArgs = arguments.FlagNameValueSlice{Items: &items} + + // Copying the state only happens during backend migration, so setting + // -force-copy implies -migrate-state + if c.forceInitCopy { + c.migrateState = true + } + + if len(initArgs.PluginPath) > 0 { + c.pluginPath = initArgs.PluginPath + } + + // Validate the arg count and get the working directory + path, err := ModulePath(initArgs.Args) + if err != nil { + diags = diags.Append(err) + view.Diagnostics(diags) + return 1 + } + + if err := c.storePluginPath(c.pluginPath); err != nil { + diags = diags.Append(fmt.Errorf("Error saving -plugin-dir to workspace directory: %s", err)) + view.Diagnostics(diags) + return 1 + } + + // Initialization can be aborted by interruption signals + ctx, done := c.InterruptibleContext(c.CommandContext()) + defer done() + + // This will track whether we outputted anything so that we know whether + // to output a newline before the success message + var header bool + + if initArgs.FromModule != "" { + src := initArgs.FromModule + + empty, err := configs.IsEmptyDir(path, initArgs.TestsDirectory) + if err != nil { + diags = diags.Append(fmt.Errorf("Error validating destination directory: %s", err)) + view.Diagnostics(diags) + return 1 + } + if !empty { + diags = diags.Append(errors.New(strings.TrimSpace(errInitCopyNotEmpty))) + view.Diagnostics(diags) + return 1 + } + + view.Output(views.CopyingConfigurationMessage, src) + header = true + + hooks := uiModuleInstallHooks{ + Ui: c.Ui, + ShowLocalPaths: false, // since they are in a weird location for init + View: view, + } + + ctx, span := tracer.Start(ctx, "-from-module=...", trace.WithAttributes( + attribute.String("module_source", src), + )) + + initDirFromModuleAbort, initDirFromModuleDiags := c.initDirFromModule(ctx, path, src, hooks) + diags = diags.Append(initDirFromModuleDiags) + if initDirFromModuleAbort || initDirFromModuleDiags.HasErrors() { + view.Diagnostics(diags) + span.SetStatus(codes.Error, "module installation failed") + span.End() + return 1 + } + span.End() + + view.Output(views.EmptyMessage) + } + + // If our directory is empty, then we're done. We can't get or set up + // the backend with an empty directory. + empty, err := configs.IsEmptyDir(path, initArgs.TestsDirectory) + if err != nil { + diags = diags.Append(fmt.Errorf("Error checking configuration: %s", err)) + view.Diagnostics(diags) + return 1 + } + if empty { + view.Output(views.OutputInitEmptyMessage) + return 0 + } + + // Load just the root module to begin backend and module initialization + rootModEarly, earlyConfDiags := c.loadSingleModuleWithTests(path, initArgs.TestsDirectory) + + // There may be parsing errors in config loading but these will be shown later _after_ + // checking for core version requirement errors. Not meeting the version requirement should + // be the first error displayed if that is an issue, but other operations are required + // before being able to check core version requirements. + if rootModEarly == nil { + diags = diags.Append(errors.New(view.PrepareMessage(views.InitConfigError)), earlyConfDiags) + view.Diagnostics(diags) + + return 1 + } + + if initArgs.Get { + modsOutput, modsAbort, modsDiags := c.getModules(ctx, path, initArgs.TestsDirectory, rootModEarly, initArgs.Upgrade, view) + diags = diags.Append(modsDiags) + if modsAbort || modsDiags.HasErrors() { + view.Diagnostics(diags) + return 1 + } + if modsOutput { + header = true + } + } + + // With all of the modules (hopefully) installed, we can now try to load the + // whole configuration tree. + config, confDiags := c.loadConfigWithTests(path, initArgs.TestsDirectory) + // configDiags will be handled after the version constraint check, since an + // incorrect version of terraform may be producing errors for configuration + // constructs added in later versions. + + // Before we go further, we'll check to make sure none of the modules in + // the configuration declare that they don't support this Terraform + // version, so we can produce a version-related error message rather than + // potentially-confusing downstream errors. + versionDiags := terraform.CheckCoreVersionRequirements(config) + if versionDiags.HasErrors() { + view.Diagnostics(versionDiags) + return 1 + } + + // Now the full configuration is loaded, we can download the providers specified in the configuration. + // This is step one of a two-step provider download process + // Providers may be downloaded by this code, but the dependency lock file is only updated later in `init` + // after step two of provider download is complete. + previousLocks, moreDiags := c.lockedDependencies() + diags = diags.Append(moreDiags) + + configProvidersOutput, configLocks, configProviderDiags := c.getProvidersFromConfig(ctx, config, initArgs.Upgrade, initArgs.PluginPath, initArgs.Lockfile, view) + diags = diags.Append(configProviderDiags) + if configProviderDiags.HasErrors() { + view.Diagnostics(diags) + return 1 + } + if configProvidersOutput { + header = true + } + + // If we outputted information, then we need to output a newline + // so that our success message is nicely spaced out from prior text. + if header { + view.Output(views.EmptyMessage) + } + + var back backend.Backend + + var backDiags tfdiags.Diagnostics + var backendOutput bool + switch { + case initArgs.Cloud && rootModEarly.CloudConfig != nil: + back, backendOutput, backDiags = c.initCloud(ctx, rootModEarly, initArgs.BackendConfig, initArgs.ViewType, view) + case initArgs.Backend: + // TODO(SarahFrench/radeksimko) - pass information about config locks (`configLocks`) into initBackend to + // enable PSS + back, backendOutput, backDiags = c.initBackend(ctx, rootModEarly, initArgs.BackendConfig, initArgs.ViewType, view) + default: + // load the previously-stored backend config + back, backDiags = c.Meta.backendFromState(ctx) + } + if backendOutput { + header = true + } + if header { + // If we outputted information, then we need to output a newline + // so that our success message is nicely spaced out from prior text. + view.Output(views.EmptyMessage) + } + + var state *states.State + + // If we have a functional backend (either just initialized or initialized + // on a previous run) we'll use the current state as a potential source + // of provider dependencies. + if back != nil { + c.ignoreRemoteVersionConflict(back) + workspace, err := c.Workspace() + if err != nil { + diags = diags.Append(fmt.Errorf("Error selecting workspace: %s", err)) + view.Diagnostics(diags) + return 1 + } + sMgr, err := back.StateMgr(workspace) + if err != nil { + diags = diags.Append(fmt.Errorf("Error loading state: %s", err)) + view.Diagnostics(diags) + return 1 + } + + if err := sMgr.RefreshState(); err != nil { + diags = diags.Append(fmt.Errorf("Error refreshing state: %s", err)) + view.Diagnostics(diags) + return 1 + } + + state = sMgr.State() + } + + // Now the resource state is loaded, we can download the providers specified in the state but not the configuration. + // This is step two of a two-step provider download process + stateProvidersOutput, stateLocks, stateProvidersDiags := c.getProvidersFromState(ctx, state, configLocks, initArgs.Upgrade, initArgs.PluginPath, initArgs.Lockfile, view) + diags = diags.Append(configProviderDiags) + if stateProvidersDiags.HasErrors() { + view.Diagnostics(diags) + return 1 + } + if stateProvidersOutput { + header = true + } + if header { + // If we outputted information, then we need to output a newline + // so that our success message is nicely spaced out from prior text. + view.Output(views.EmptyMessage) + } + + // Now the two steps of provider download have happened, update the dependency lock file if it has changed. + lockFileOutput, lockFileDiags := c.saveDependencyLockFile(previousLocks, configLocks, stateLocks, initArgs.Lockfile, view) + diags = diags.Append(lockFileDiags) + if lockFileDiags.HasErrors() { + view.Diagnostics(diags) + return 1 + } + if lockFileOutput { + header = true + } + if header { + // If we outputted information, then we need to output a newline + // so that our success message is nicely spaced out from prior text. + view.Output(views.EmptyMessage) + } + + // As Terraform version-related diagnostics are handled above, we can now + // check the diagnostics from the early configuration and the backend. + diags = diags.Append(earlyConfDiags) + diags = diags.Append(backDiags) + if earlyConfDiags.HasErrors() { + diags = diags.Append(errors.New(view.PrepareMessage(views.InitConfigError))) + view.Diagnostics(diags) + return 1 + } + + // Now, we can show any errors from initializing the backend, but we won't + // show the InitConfigError preamble as we didn't detect problems with + // the early configuration. + if backDiags.HasErrors() { + view.Diagnostics(diags) + return 1 + } + + // If everything is ok with the core version check and backend initialization, + // show other errors from loading the full configuration tree. + diags = diags.Append(confDiags) + if confDiags.HasErrors() { + diags = diags.Append(errors.New(view.PrepareMessage(views.InitConfigError))) + view.Diagnostics(diags) + return 1 + } + + if cb, ok := back.(*cloud.Cloud); ok { + if c.RunningInAutomation { + if err := cb.AssertImportCompatible(config); err != nil { + diags = diags.Append(tfdiags.Sourceless(tfdiags.Error, "Compatibility error", err.Error())) + view.Diagnostics(diags) + return 1 + } + } + } + + // If we accumulated any warnings along the way that weren't accompanied + // by errors then we'll output them here so that the success message is + // still the final thing shown. + view.Diagnostics(diags) + _, cloud := back.(*cloud.Cloud) + output := views.OutputInitSuccessMessage + if cloud { + output = views.OutputInitSuccessCloudMessage + } + + view.Output(output) + + if !c.RunningInAutomation { + // If we're not running in an automation wrapper, give the user + // some more detailed next steps that are appropriate for interactive + // shell usage. + output = views.OutputInitSuccessCLIMessage + if cloud { + output = views.OutputInitSuccessCLICloudMessage + } + view.Output(output) + } + return 0 +} diff --git a/internal/command/init_test.go b/internal/command/init_test.go index d243344f52a8..9de0c76f8e43 100644 --- a/internal/command/init_test.go +++ b/internal/command/init_test.go @@ -111,6 +111,130 @@ func TestInit_only_test_files(t *testing.T) { } } +func TestInit_two_step_provider_download(t *testing.T) { + cases := map[string]struct { + workDirPath string + flags []string + expectedDownloadMsgs []string + }{ + "providers required by only the state file": { + // TODO - should the output indicate that no providers were found in config? + workDirPath: "init-provider-download/state-file-only", + expectedDownloadMsgs: []string{ + views.MessageRegistry[views.OutputInitSuccessCLIMessage].JSONValue, + `Initializing provider plugins found in the configuration... + Initializing the backend...`, // No providers found in the configuration so next output is backend-related + `Initializing provider plugins found in the state... + - Finding latest version of hashicorp/random... + - Installing hashicorp/random v9.9.9...`, // The latest version is expected, as state has no version constraints + }, + }, + "different providers required by config and state": { + workDirPath: "init-provider-download/config-and-state-different-providers", + expectedDownloadMsgs: []string{ + views.MessageRegistry[views.OutputInitSuccessCLIMessage].JSONValue, + + // Config - this provider is affected by a version constraint + `Initializing provider plugins found in the configuration... + - Finding hashicorp/null versions matching "< 9.0.0"... + - Installing hashicorp/null v1.0.0... + - Installed hashicorp/null v1.0.0`, + + // State - the latest version of this provider is expected, as state has no version constraints + `Initializing provider plugins found in the state... + - Finding latest version of hashicorp/random... + - Installing hashicorp/random v9.9.9...`, + }, + }, + "does not re-download providers that are present in both config and state": { + workDirPath: "init-provider-download/config-and-state-same-providers", + expectedDownloadMsgs: []string{ + // Config + `Initializing provider plugins found in the configuration... + - Finding hashicorp/random versions matching "< 9.0.0"... + - Installing hashicorp/random v1.0.0... + - Installed hashicorp/random v1.0.0`, + // State + `Initializing provider plugins found in the state... + - Reusing previous version of hashicorp/random + - Using previously-installed hashicorp/random v1.0.0`, + }, + }, + "reuses providers already represented in a dependency lock file": { + workDirPath: "init-provider-download/config-state-file-and-lockfile", + expectedDownloadMsgs: []string{ + // Config + `Initializing provider plugins found in the configuration... + - Reusing previous version of hashicorp/random from the dependency lock file + - Installing hashicorp/random v1.0.0... + - Installed hashicorp/random v1.0.0`, + // State + `Initializing provider plugins found in the state... + - Reusing previous version of hashicorp/random + - Using previously-installed hashicorp/random v1.0.0`, + }, + }, + "using the -upgrade flag causes provider download to ignore the lock file": { + workDirPath: "init-provider-download/config-state-file-and-lockfile", + flags: []string{"-upgrade"}, + expectedDownloadMsgs: []string{ + // Config - lock file is not mentioned due to the -upgrade flag + `Initializing provider plugins found in the configuration... + - Finding hashicorp/random versions matching "< 9.0.0"... + - Installing hashicorp/random v1.0.0... + - Installed hashicorp/random v1.0.0`, + // State - reuses the provider download from the config + `Initializing provider plugins found in the state... + - Reusing previous version of hashicorp/random + - Using previously-installed hashicorp/random v1.0.0`, + }, + }, + } + + for tn, tc := range cases { + t.Run(tn, func(t *testing.T) { + + // Create a temporary working directory no tf configuration but has state + td := t.TempDir() + testCopyDir(t, testFixturePath(tc.workDirPath), td) + os.MkdirAll(td, 0755) + t.Chdir(td) + + // A provider source containing the random and null providers + providerSource, close := newMockProviderSource(t, map[string][]string{ + "hashicorp/random": {"1.0.0", "9.9.9"}, + "hashicorp/null": {"1.0.0", "9.9.9"}, + }) + defer close() + + ui := new(cli.MockUi) + view, done := testView(t) + c := &InitCommand{ + Meta: Meta{ + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, + View: view, + ProviderSource: providerSource, + + AllowExperimentalFeatures: true, // Needed to test init changes for PSS project + }, + } + + args := append(tc.flags, "-enable-pluggable-state-storage-experiment") // Needed to test init changes for PSS project + if code := c.Run(args); code != 0 { + t.Fatalf("bad: \n%s", done(t).All()) + } + + actual := cleanString(done(t).All()) + for _, downloadMsg := range tc.expectedDownloadMsgs { + if !strings.Contains(cleanString(actual), cleanString(downloadMsg)) { + t.Fatalf("expected output to contain %q\n, got %q", cleanString(downloadMsg), cleanString(actual)) + } + } + }) + } +} + func TestInit_multipleArgs(t *testing.T) { // Create a temporary working directory that is empty td := t.TempDir() diff --git a/internal/command/meta_dependencies.go b/internal/command/meta_dependencies.go index e7f213e43dfd..b84afecf3afb 100644 --- a/internal/command/meta_dependencies.go +++ b/internal/command/meta_dependencies.go @@ -66,6 +66,43 @@ func (m *Meta) replaceLockedDependencies(new *depsfile.Locks) tfdiags.Diagnostic return depsfile.SaveLocksToFile(new, dependencyLockFilename) } +// mergeLockedDependencies combines two sets of locks. The 'base' locks are copied, and any providers +// present in the additional locks that aren't present in the base are added to that copy. The merged +// combination is returned. +// +// If you're combining locks derived from config with other locks (from state or deps locks file), then +// the config locks need to be the first argument to ensure that the merged locks contain any +// version constraints. Version constraint data is only present in configuration. +// This allows code in the init command to download providers in separate phases and +// keep the lock file updated accurately after each phase. +// +// This method supports downloading providers in 2 steps, and is used during the second download step and +// while updating the dependency lock file. +func (m *Meta) mergeLockedDependencies(baseLocks, additionalLocks *depsfile.Locks) *depsfile.Locks { + + mergedLocks := baseLocks.DeepCopy() + + // Append locks derived from the state to locks derived from config. + for _, lock := range additionalLocks.AllProviders() { + match := mergedLocks.Provider(lock.Provider()) + if match != nil { + log.Printf("[TRACE] Ignoring provider %s version %s in appendLockedDependencies; lock file contains %s version %s already", + lock.Provider(), + lock.Version(), + match.Provider(), + match.Version(), + ) + } else { + // This is a new provider now present in the lockfile yet + log.Printf("[DEBUG] Appending provider %s to the lock file", lock.Provider()) + mergedLocks.SetProvider(lock.Provider(), lock.Version(), lock.VersionConstraints(), lock.AllHashes()) + } + } + + // Override the locks file with the new combination of locks + return mergedLocks +} + // annotateDependencyLocksWithOverrides modifies the given Locks object in-place // to track as overridden any provider address that's subject to testing // overrides, development overrides, or "unmanaged provider" status. diff --git a/internal/command/meta_dependencies_test.go b/internal/command/meta_dependencies_test.go new file mode 100644 index 000000000000..b245648867f1 --- /dev/null +++ b/internal/command/meta_dependencies_test.go @@ -0,0 +1,151 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package command + +import ( + "testing" + + "github.com/apparentlymart/go-versions/versions" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/cli" + tfaddr "github.com/hashicorp/terraform-registry-address" + "github.com/hashicorp/terraform/internal/depsfile" + "github.com/hashicorp/terraform/internal/getproviders/providerreqs" +) + +// This tests combining locks from config and state. Locks derived from state are always unconstrained, i.e. no version constraint data, +// so this test +func Test_mergeLockedDependencies_config_and_state(t *testing.T) { + + providerA := tfaddr.NewProvider(tfaddr.DefaultProviderRegistryHost, "my-org", "providerA") + providerB := tfaddr.NewProvider(tfaddr.DefaultProviderRegistryHost, "my-org", "providerB") + v1_0_0 := providerreqs.MustParseVersion("1.0.0") + versionConstraintv1, _ := providerreqs.ParseVersionConstraints("1.0.0") + hashesProviderA := []providerreqs.Hash{providerreqs.MustParseHash("providerA:this-is-providerA")} + hashesProviderB := []providerreqs.Hash{providerreqs.MustParseHash("providerB:this-is-providerB")} + + var versionUnconstrained providerreqs.VersionConstraints = nil + noVersion := versions.Version{} + + cases := map[string]struct { + configLocks *depsfile.Locks + stateLocks *depsfile.Locks + expectedLocks *depsfile.Locks + }{ + "no locks when all inputs empty": { + configLocks: depsfile.NewLocks(), + stateLocks: depsfile.NewLocks(), + expectedLocks: depsfile.NewLocks(), + }, + "when provider only described in config, output locks have matching constraints": { + configLocks: func() *depsfile.Locks { + configLocks := depsfile.NewLocks() + configLocks.SetProvider(providerA, v1_0_0, versionConstraintv1, hashesProviderA) + return configLocks + }(), + stateLocks: depsfile.NewLocks(), + expectedLocks: func() *depsfile.Locks { + combinedLocks := depsfile.NewLocks() + combinedLocks.SetProvider(providerA, v1_0_0, versionConstraintv1, hashesProviderA) + return combinedLocks + }(), + }, + "when provider only described in state, output locks are unconstrained": { + configLocks: depsfile.NewLocks(), + stateLocks: func() *depsfile.Locks { + stateLocks := depsfile.NewLocks() + stateLocks.SetProvider(providerA, noVersion, versionUnconstrained, hashesProviderA) + return stateLocks + }(), + expectedLocks: func() *depsfile.Locks { + combinedLocks := depsfile.NewLocks() + combinedLocks.SetProvider(providerA, noVersion, versionUnconstrained, hashesProviderA) + return combinedLocks + }(), + }, + "different providers present in state and config are combined, with version constraints kept on config providers": { + configLocks: func() *depsfile.Locks { + configLocks := depsfile.NewLocks() + configLocks.SetProvider(providerA, v1_0_0, versionConstraintv1, hashesProviderA) + return configLocks + }(), + stateLocks: func() *depsfile.Locks { + stateLocks := depsfile.NewLocks() + + // Imagine that the state locks contain: + // 1) provider for resources in the config + stateLocks.SetProvider(providerA, noVersion, versionUnconstrained, hashesProviderA) + + // 2) also, a provider that's deleted from the config and only present in state + stateLocks.SetProvider(providerB, noVersion, versionUnconstrained, hashesProviderB) + + return stateLocks + }(), + expectedLocks: func() *depsfile.Locks { + combinedLocks := depsfile.NewLocks() + combinedLocks.SetProvider(providerA, v1_0_0, versionConstraintv1, hashesProviderA) // version constraint preserved + combinedLocks.SetProvider(providerB, noVersion, versionUnconstrained, hashesProviderB) // sourced from state only + return combinedLocks + }(), + }, + } + + for tn, tc := range cases { + t.Run(tn, func(t *testing.T) { + // Use tmp dir as we're creating lock files in the test + td := t.TempDir() + t.Chdir(td) + + ui := new(cli.MockUi) + view, _ := testView(t) + + m := Meta{ + Ui: ui, + View: view, + } + + // Code under test - combine deps from state with prior deps from config + // mergedLocks := m.mergeLockedDependencies(tc.configLocks, tc.stateLocks) + mergedLocks := m.mergeLockedDependencies(tc.configLocks, tc.stateLocks) + + // We cannot use (l *depsfile.Locks) Equal here as it doesn't compare version constraints + // Instead, inspect entries directly + if len(mergedLocks.AllProviders()) != len(tc.expectedLocks.AllProviders()) { + t.Fatalf("expected merged dependencies to include %d providers, but got %d:\n %#v", + len(tc.expectedLocks.AllProviders()), + len(mergedLocks.AllProviders()), + mergedLocks.AllProviders(), + ) + } + for _, lock := range tc.expectedLocks.AllProviders() { + match := mergedLocks.Provider(lock.Provider()) + if match == nil { + t.Fatalf("expected merged dependencies to include provider %s, but it's missing", lock.Provider()) + } + if len(match.VersionConstraints()) != len(lock.VersionConstraints()) { + t.Fatalf("detected a problem with version constraints for provider %s, got: %d, want %d", + lock.Provider(), + len(match.VersionConstraints()), + len(lock.VersionConstraints()), + ) + } + if len(match.VersionConstraints()) > 0 && len(lock.VersionConstraints()) > 0 { + gotConstraints := match.VersionConstraints()[0] + wantConstraints := lock.VersionConstraints()[0] + + if gotConstraints.Boundary.String() != wantConstraints.Boundary.String() { + t.Fatalf("expected merged dependencies to include provider %s with version constraint %v, but instead got %v", + lock.Provider(), + gotConstraints.Boundary.String(), + wantConstraints.Boundary.String(), + ) + } + } + } + if diff := cmp.Diff(tc.expectedLocks, mergedLocks); diff != "" { + t.Errorf("difference in file contents detected\n%s", diff) + } + }) + } +} diff --git a/internal/command/testdata/init-provider-download/config-and-state-different-providers/main.tf b/internal/command/testdata/init-provider-download/config-and-state-different-providers/main.tf new file mode 100644 index 000000000000..f0687030bb2f --- /dev/null +++ b/internal/command/testdata/init-provider-download/config-and-state-different-providers/main.tf @@ -0,0 +1,14 @@ +terraform { + required_providers { + null = { + source = "hashicorp/null" + version = "<9.0.0" + } + } + + backend "local" { + path = "./state-using-random-provider.tfstate" + } +} + +resource "null_resource" "null" {} diff --git a/internal/command/testdata/init-provider-download/config-and-state-different-providers/state-using-random-provider.tfstate b/internal/command/testdata/init-provider-download/config-and-state-different-providers/state-using-random-provider.tfstate new file mode 100644 index 000000000000..76b543b06af6 --- /dev/null +++ b/internal/command/testdata/init-provider-download/config-and-state-different-providers/state-using-random-provider.tfstate @@ -0,0 +1,28 @@ +{ + "version": 4, + "terraform_version": "1.14.0", + "serial": 1, + "lineage": "foobar", + "resources": [ + { + "mode": "managed", + "type": "random_pet", + "name": "maurice", + "provider": "provider[\"registry.terraform.io/hashicorp/random\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "id": "sassy-ferret", + "keepers": null, + "length": 2, + "prefix": null, + "separator": "-" + }, + "sensitive_attributes": [], + "identity_schema_version": 0 + } + ] + } + ] +} \ No newline at end of file diff --git a/internal/command/testdata/init-provider-download/config-and-state-same-providers/main.tf b/internal/command/testdata/init-provider-download/config-and-state-same-providers/main.tf new file mode 100644 index 000000000000..d5857489d6de --- /dev/null +++ b/internal/command/testdata/init-provider-download/config-and-state-same-providers/main.tf @@ -0,0 +1,14 @@ +terraform { + required_providers { + random = { + source = "hashicorp/random" + version = "<9.0.0" + } + } + + backend "local" { + path = "./state-using-random-provider.tfstate" + } +} + +resource "random_pet" "maurice" {} diff --git a/internal/command/testdata/init-provider-download/config-and-state-same-providers/state-using-random-provider.tfstate b/internal/command/testdata/init-provider-download/config-and-state-same-providers/state-using-random-provider.tfstate new file mode 100644 index 000000000000..76b543b06af6 --- /dev/null +++ b/internal/command/testdata/init-provider-download/config-and-state-same-providers/state-using-random-provider.tfstate @@ -0,0 +1,28 @@ +{ + "version": 4, + "terraform_version": "1.14.0", + "serial": 1, + "lineage": "foobar", + "resources": [ + { + "mode": "managed", + "type": "random_pet", + "name": "maurice", + "provider": "provider[\"registry.terraform.io/hashicorp/random\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "id": "sassy-ferret", + "keepers": null, + "length": 2, + "prefix": null, + "separator": "-" + }, + "sensitive_attributes": [], + "identity_schema_version": 0 + } + ] + } + ] +} \ No newline at end of file diff --git a/internal/command/testdata/init-provider-download/config-state-file-and-lockfile/.terraform.lock.hcl b/internal/command/testdata/init-provider-download/config-state-file-and-lockfile/.terraform.lock.hcl new file mode 100644 index 000000000000..2140e1a14471 --- /dev/null +++ b/internal/command/testdata/init-provider-download/config-state-file-and-lockfile/.terraform.lock.hcl @@ -0,0 +1,6 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/random" { + version = "1.0.0" +} diff --git a/internal/command/testdata/init-provider-download/config-state-file-and-lockfile/main.tf b/internal/command/testdata/init-provider-download/config-state-file-and-lockfile/main.tf new file mode 100644 index 000000000000..d5857489d6de --- /dev/null +++ b/internal/command/testdata/init-provider-download/config-state-file-and-lockfile/main.tf @@ -0,0 +1,14 @@ +terraform { + required_providers { + random = { + source = "hashicorp/random" + version = "<9.0.0" + } + } + + backend "local" { + path = "./state-using-random-provider.tfstate" + } +} + +resource "random_pet" "maurice" {} diff --git a/internal/command/testdata/init-provider-download/config-state-file-and-lockfile/state-using-random-provider.tfstate b/internal/command/testdata/init-provider-download/config-state-file-and-lockfile/state-using-random-provider.tfstate new file mode 100644 index 000000000000..76b543b06af6 --- /dev/null +++ b/internal/command/testdata/init-provider-download/config-state-file-and-lockfile/state-using-random-provider.tfstate @@ -0,0 +1,28 @@ +{ + "version": 4, + "terraform_version": "1.14.0", + "serial": 1, + "lineage": "foobar", + "resources": [ + { + "mode": "managed", + "type": "random_pet", + "name": "maurice", + "provider": "provider[\"registry.terraform.io/hashicorp/random\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "id": "sassy-ferret", + "keepers": null, + "length": 2, + "prefix": null, + "separator": "-" + }, + "sensitive_attributes": [], + "identity_schema_version": 0 + } + ] + } + ] +} \ No newline at end of file diff --git a/internal/command/testdata/init-provider-download/state-file-only/main.tf b/internal/command/testdata/init-provider-download/state-file-only/main.tf new file mode 100644 index 000000000000..d568a66bf2ed --- /dev/null +++ b/internal/command/testdata/init-provider-download/state-file-only/main.tf @@ -0,0 +1,5 @@ +terraform { + backend "local" { + path = "./state-using-random-provider.tfstate" + } +} diff --git a/internal/command/testdata/init-provider-download/state-file-only/state-using-random-provider.tfstate b/internal/command/testdata/init-provider-download/state-file-only/state-using-random-provider.tfstate new file mode 100644 index 000000000000..76b543b06af6 --- /dev/null +++ b/internal/command/testdata/init-provider-download/state-file-only/state-using-random-provider.tfstate @@ -0,0 +1,28 @@ +{ + "version": 4, + "terraform_version": "1.14.0", + "serial": 1, + "lineage": "foobar", + "resources": [ + { + "mode": "managed", + "type": "random_pet", + "name": "maurice", + "provider": "provider[\"registry.terraform.io/hashicorp/random\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "id": "sassy-ferret", + "keepers": null, + "length": 2, + "prefix": null, + "separator": "-" + }, + "sensitive_attributes": [], + "identity_schema_version": 0 + } + ] + } + ] +} \ No newline at end of file diff --git a/internal/command/testdata/init-with-state-store/main.tf b/internal/command/testdata/init-with-state-store/main.tf index a4042b401b17..9939e9dece2b 100644 --- a/internal/command/testdata/init-with-state-store/main.tf +++ b/internal/command/testdata/init-with-state-store/main.tf @@ -1,5 +1,11 @@ terraform { - state_store "foo_foo" { + required_providers { + foo = { + source = "my-org/foo" + } + } + state_store "foo_foo" { + provider "foo" {} } } diff --git a/internal/command/views/init.go b/internal/command/views/init.go index d228cb81d566..35d79e1ba815 100644 --- a/internal/command/views/init.go +++ b/internal/command/views/init.go @@ -186,6 +186,14 @@ var MessageRegistry map[InitMessageCode]InitMessage = map[InitMessageCode]InitMe HumanValue: "\n[reset][bold]Initializing provider plugins...", JSONValue: "Initializing provider plugins...", }, + "initializing_provider_plugin_from_config_message": { + HumanValue: "\n[reset][bold]Initializing provider plugins found in the configuration...", + JSONValue: "Initializing provider plugins found in the configuration...", + }, + "initializing_provider_plugin_from_state_message": { + HumanValue: "\n[reset][bold]Initializing provider plugins found in the state...", + JSONValue: "Initializing provider plugins found in the state...", + }, "initializing_state_store_message": { HumanValue: "\n[reset][bold]Initializing the state store...", JSONValue: "Initializing the state store...", @@ -210,6 +218,10 @@ var MessageRegistry map[InitMessageCode]InitMessage = map[InitMessageCode]InitMe HumanValue: "- Reusing previous version of %s from the dependency lock file", JSONValue: "%s: Reusing previous version from the dependency lock file", }, + "reusing_version_during_state_provider_init": { + HumanValue: "- Reusing previous version of %s", + JSONValue: "%s: Reusing previous version of %s", + }, "finding_matching_version_message": { HumanValue: "- Finding %s versions matching %q...", JSONValue: "Finding matching versions for provider: %s, version_constraint: %q", @@ -271,6 +283,14 @@ const ( DependenciesLockChangesInfo InitMessageCode = "dependencies_lock_changes_info" //// Message codes below are ONLY used INTERNALLY (for now) + + // InitializingProviderPluginFromConfigMessage indicates the beginning of installing of providers described in configuration + InitializingProviderPluginFromConfigMessage InitMessageCode = "initializing_provider_plugin_from_config_message" + // InitializingProviderPluginFromStateMessage indicates the beginning of installing of providers described in state + InitializingProviderPluginFromStateMessage InitMessageCode = "initializing_provider_plugin_from_state_message" + // DependenciesLockPendingChangesInfo indicates when a provider installation step will reuse a provider from a previous installation step in the current operation + ReusingVersionIdentifiedFromConfig InitMessageCode = "reusing_version_during_state_provider_init" + // InitConfigError indicates problems encountered during initialisation InitConfigError InitMessageCode = "init_config_error" // FindingMatchingVersionMessage indicates that Terraform is looking for a provider version that matches the constraint during installation diff --git a/internal/configs/parser_config.go b/internal/configs/parser_config.go index fa72ba7f9c05..4047fe1db83a 100644 --- a/internal/configs/parser_config.go +++ b/internal/configs/parser_config.go @@ -104,14 +104,6 @@ func parseConfigFile(body hcl.Body, diags hcl.Diagnostics, override, allowExperi switch block.Type { case "terraform": - // TODO: Update once pluggable state store is out of experimental phase - if allowExperiments { - terraformBlockSchema.Blocks = append(terraformBlockSchema.Blocks, - hcl.BlockHeaderSchema{ - Type: "state_store", - LabelNames: []string{"type"}, - }) - } content, contentDiags := block.Body.Content(terraformBlockSchema) diags = append(diags, contentDiags...) @@ -394,9 +386,10 @@ var terraformBlockSchema = &hcl.BodySchema{ { Type: "required_providers", }, - // NOTE: An entry for state_store is not present here - // because we conditionally add it in the calling code - // depending on whether experiments are enabled or not. + { + Type: "state_store", + LabelNames: []string{"type"}, + }, { Type: "provider_meta", LabelNames: []string{"provider"}, From 7a6d8eca618f1efca74627eaefbae59d37e2a4d7 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Thu, 10 Jul 2025 11:51:06 +0100 Subject: [PATCH 05/60] Store the FQN of the provider used in PSS in representations of the parsed config. This can only be done once modules have been parsed and the required providers data is available. There are multiple places where config is parsed, into either Config or Module structs, so this needs to be implemented in multiple places. --- internal/configs/state_store.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/configs/state_store.go b/internal/configs/state_store.go index aa12509095c3..16da71827daa 100644 --- a/internal/configs/state_store.go +++ b/internal/configs/state_store.go @@ -116,8 +116,9 @@ func resolveStateStoreProviderType(requiredProviders map[string]*RequiredProvide diags = append(diags, &hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Missing entry in required_providers", - Detail: fmt.Sprintf("The provider used for state storage must have a matching entry in required_providers. Please add an entry for provider %q", - stateStore.Provider.Name), + Detail: fmt.Sprintf("The provider used for state storage must have a matching entry in required_providers. Please add an entry for provider %s (%q)", + stateStore.Provider.Name, + stateStore.ProviderAddr), Subject: &stateStore.DeclRange, }) return tfaddr.Provider{}, diags From e0dbc86eb76e7374b274f9c39e359f1b152b8fb2 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 11 Jul 2025 14:05:56 +0100 Subject: [PATCH 06/60] Add test that hits the code path for adding a state store to a new (or implied local) project --- internal/command/meta_backend_test.go | 69 +++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/internal/command/meta_backend_test.go b/internal/command/meta_backend_test.go index 014b43d00c25..e14d544ac2e4 100644 --- a/internal/command/meta_backend_test.go +++ b/internal/command/meta_backend_test.go @@ -312,6 +312,75 @@ func TestMetaBackend_configureNewBackend(t *testing.T) { } } +// Newly configured state store +// +// TODO(SarahFrench/radeksimko): currently this test only confirms that we're hitting the switch +// case for this scenario, and will need to be updated when that init feature is implemented. +func TestMetaBackend_configureNewStateStore(t *testing.T) { + td := t.TempDir() + testCopyDir(t, testFixturePath("state-store-new"), td) + defer testChdir(t, td)() + + // Setup the meta + m := testMetaBackend(t, nil) + m.AllowExperimentalFeatures = true + + // Get the state store's config + mod, loadDiags := m.loadSingleModule(td) + if loadDiags.HasErrors() { + t.Fatalf("unexpected error when loading test config: %s", loadDiags.Err()) + } + + // Get mock provider factory to be used during init + // + // This imagines a provider called foo that contains + // a pluggable state store implementation called bar. + mock := &testing_provider.MockProvider{ + GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ + Provider: providers.Schema{ + Body: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "region": {Type: cty.String, Optional: true}, + }, + }, + }, + DataSources: map[string]providers.Schema{}, + ResourceTypes: map[string]providers.Schema{}, + ListResourceTypes: map[string]providers.Schema{}, + StateStores: map[string]providers.Schema{ + "foo_bar": { + Body: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "bar": { + Type: cty.String, + Required: true, + }, + }, + }, + }, + }, + }, + } + factory := func() (providers.Interface, error) { + return mock, nil + } + + // Get the operations backend + _, beDiags := m.Backend(&BackendOpts{ + Init: true, + StateStoreConfig: mod.StateStore, + ProviderFactory: factory, + }) + if !beDiags.HasErrors() { + t.Fatal("expected an error to be returned during partial implementation of PSS") + } + wantErr := "Configuring a state store for the first time is not implemented yet" + if !strings.Contains(beDiags.Err().Error(), wantErr) { + t.Fatalf("expected the returned error to contain %q, but got: %s", wantErr, beDiags.Err()) + } + +} + // Newly configured backend with prior local state and no remote state func TestMetaBackend_configureNewBackendWithState(t *testing.T) { // Create a temporary working directory that is empty From ca338f522380dcb4d0b6fdd57dfd1e1b7f5582cb Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 11 Jul 2025 14:12:43 +0100 Subject: [PATCH 07/60] Add test for use of `-reconfigure` flag; show that it hits the code path for adding a state store for the first time --- internal/command/meta_backend_test.go | 78 +++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/internal/command/meta_backend_test.go b/internal/command/meta_backend_test.go index e14d544ac2e4..1413f4afe48e 100644 --- a/internal/command/meta_backend_test.go +++ b/internal/command/meta_backend_test.go @@ -865,6 +865,84 @@ func TestMetaBackend_reconfigureBackendChange(t *testing.T) { } } +// Reconfiguring with an already configured state store. +// This should ignore the existing state_store config, and configure the new +// state store is if this is the first time. +// +// TODO(SarahFrench/radeksimko): currently this test only confirms that we're hitting the switch +// case for this scenario, and will need to be updated when that init feature is implemented. +func TestMetaBackend_reconfigureStateStoreChange(t *testing.T) { + td := t.TempDir() + testCopyDir(t, testFixturePath("state-store-reconfigure"), td) + defer testChdir(t, td)() + + // Setup the meta + m := testMetaBackend(t, nil) + m.AllowExperimentalFeatures = true + + // this should not ask for input + m.input = false + + // cli flag -reconfigure + m.reconfigure = true + + // Get the state store's config + mod, loadDiags := m.loadSingleModule(td) + if loadDiags.HasErrors() { + t.Fatalf("unexpected error when loading test config: %s", loadDiags.Err()) + } + + // Get mock provider factory to be used during init + // + // This imagines a provider called foo that contains + // a pluggable state store implementation called bar. + mock := &testing_provider.MockProvider{ + GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ + Provider: providers.Schema{ + Body: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "region": {Type: cty.String, Optional: true}, + }, + }, + }, + DataSources: map[string]providers.Schema{}, + ResourceTypes: map[string]providers.Schema{}, + ListResourceTypes: map[string]providers.Schema{}, + StateStores: map[string]providers.Schema{ + "foo_bar": { + Body: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "bar": { + Type: cty.String, + Required: true, + }, + }, + }, + }, + }, + }, + } + factory := func() (providers.Interface, error) { + return mock, nil + } + + // Get the operations backend + _, beDiags := m.Backend(&BackendOpts{ + Init: true, + StateStoreConfig: mod.StateStore, + ProviderFactory: factory, + }) + + if !beDiags.HasErrors() { + t.Fatal("expected an error to be returned during partial implementation of PSS") + } + wantErr := "Configuring a state store for the first time is not implemented yet" + if !strings.Contains(beDiags.Err().Error(), wantErr) { + t.Fatalf("expected the returned error to contain %q, but got: %s", wantErr, beDiags.Err()) + } + +} + // Initializing a backend which supports workspaces and does *not* have // the currently selected workspace should prompt the user with a list of // workspaces to choose from to select a valid one, if more than one workspace From 0c88777d047c89acecc9be5dcd2221654815cb8d Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 11 Jul 2025 15:03:21 +0100 Subject: [PATCH 08/60] Add test that hits the code path for removing use of a state store, migrating to (implied) local backend --- internal/command/meta_backend_test.go | 38 +++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/internal/command/meta_backend_test.go b/internal/command/meta_backend_test.go index 1413f4afe48e..3ea2447a1a05 100644 --- a/internal/command/meta_backend_test.go +++ b/internal/command/meta_backend_test.go @@ -1630,6 +1630,44 @@ func TestMetaBackend_configuredBackendUnset(t *testing.T) { } } +// Unsetting a saved state store +// +// TODO(SarahFrench/radeksimko): currently this test only confirms that we're hitting the switch +// case for this scenario, and will need to be updated when that init feature is implemented. +func TestMetaBackend_configuredStateStoreUnset(t *testing.T) { + td := t.TempDir() + testCopyDir(t, testFixturePath("state-store-unset"), td) + defer testChdir(t, td)() + + // Setup the meta + m := testMetaBackend(t, nil) + m.AllowExperimentalFeatures = true + + // Get the state store's config + mod, loadDiags := m.loadSingleModule(td) + if loadDiags.HasErrors() { + t.Fatalf("unexpected error when loading test config: %s", loadDiags.Err()) + } + + // No mock provider is used here - yet + // Logic will need to be implemented that lets the init have access to + // a factory for the 'old' provider used for PSS previously. This will be + // used when migrating away from PSS entirely, or to a new PSS configuration. + + // Get the operations backend + _, beDiags := m.Backend(&BackendOpts{ + Init: true, + StateStoreConfig: mod.StateStore, + }) + if !beDiags.HasErrors() { + t.Fatal("expected an error to be returned during partial implementation of PSS") + } + wantErr := "Unsetting a state store is not implemented yet" + if !strings.Contains(beDiags.Err().Error(), wantErr) { + t.Fatalf("expected the returned error to contain %q, but got: %s", wantErr, beDiags.Err()) + } +} + // Unsetting a saved backend and copying the remote state func TestMetaBackend_configuredBackendUnsetCopy(t *testing.T) { // Create a temporary working directory that is empty From f175c11b211610d3a039cad413b6b902b59a5e27 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 11 Jul 2025 15:04:37 +0100 Subject: [PATCH 09/60] Add test that hits the code path for changing a state store's configuration --- internal/command/meta_backend_test.go | 69 +++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/internal/command/meta_backend_test.go b/internal/command/meta_backend_test.go index 3ea2447a1a05..ca89c99b2941 100644 --- a/internal/command/meta_backend_test.go +++ b/internal/command/meta_backend_test.go @@ -813,6 +813,75 @@ func TestMetaBackend_changeConfiguredBackend(t *testing.T) { } } +// Changing a configured state store +// +// TODO(SarahFrench/radeksimko): currently this test only confirms that we're hitting the switch +// case for this scenario, and will need to be updated when that init feature is implemented. +// ALSO, this test will need to be split into multiple scenarios in future. +func TestMetaBackend_changeConfiguredStateStore(t *testing.T) { + td := t.TempDir() + testCopyDir(t, testFixturePath("state-store-changed"), td) + defer testChdir(t, td)() + + // Setup the meta + m := testMetaBackend(t, nil) + m.AllowExperimentalFeatures = true + + // Get the state store's config + mod, loadDiags := m.loadSingleModule(td) + if loadDiags.HasErrors() { + t.Fatalf("unexpected error when loading test config: %s", loadDiags.Err()) + } + + // Get mock provider factory to be used during init + // + // This imagines a provider called foo that contains + // a pluggable state store implementation called bar. + mock := &testing_provider.MockProvider{ + GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ + Provider: providers.Schema{ + Body: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "region": {Type: cty.String, Optional: true}, + }, + }, + }, + DataSources: map[string]providers.Schema{}, + ResourceTypes: map[string]providers.Schema{}, + ListResourceTypes: map[string]providers.Schema{}, + StateStores: map[string]providers.Schema{ + "foo_bar": { + Body: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "bar": { + Type: cty.String, + Required: true, + }, + }, + }, + }, + }, + }, + } + factory := func() (providers.Interface, error) { + return mock, nil + } + + // Get the operations backend + _, beDiags := m.Backend(&BackendOpts{ + Init: true, + StateStoreConfig: mod.StateStore, + ProviderFactory: factory, + }) + if !beDiags.HasErrors() { + t.Fatal("expected an error to be returned during partial implementation of PSS") + } + wantErr := "Changing a state store configuration is not implemented yet" + if !strings.Contains(beDiags.Err().Error(), wantErr) { + t.Fatalf("expected the returned error to contain %q, but got: %s", wantErr, beDiags.Err()) + } +} + // Reconfiguring with an already configured backend. // This should ignore the existing backend config, and configure the new // backend is if this is the first time. From 99f69634eaba36ef7f6ff6c6311d14497f934b8f Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 11 Jul 2025 15:10:23 +0100 Subject: [PATCH 10/60] Update existing test names to be backend-specific --- internal/command/meta_backend_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/command/meta_backend_test.go b/internal/command/meta_backend_test.go index ca89c99b2941..8039b6dd3868 100644 --- a/internal/command/meta_backend_test.go +++ b/internal/command/meta_backend_test.go @@ -695,7 +695,7 @@ func TestMetaBackend_configureNewBackendWithStateExistingNoMigrate(t *testing.T) } // Saved backend state matching config -func TestMetaBackend_configuredUnchanged(t *testing.T) { +func TestMetaBackend_configuredBackendUnchanged(t *testing.T) { td := t.TempDir() testCopyDir(t, testFixturePath("backend-unchanged"), td) t.Chdir(td) From 368eaad468b9f369dfb373a069b9fd1d25886615 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 11 Jul 2025 15:31:22 +0100 Subject: [PATCH 11/60] Add tests that hits the code path for migrating between PSS and backends --- internal/command/meta_backend_test.go | 98 +++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/internal/command/meta_backend_test.go b/internal/command/meta_backend_test.go index 8039b6dd3868..38401b64d2e3 100644 --- a/internal/command/meta_backend_test.go +++ b/internal/command/meta_backend_test.go @@ -882,6 +882,104 @@ func TestMetaBackend_changeConfiguredStateStore(t *testing.T) { } } +func TestMetaBackend_configuredBackendToStateStore(t *testing.T) { + td := t.TempDir() + testCopyDir(t, testFixturePath("backend-to-state-store"), td) + defer testChdir(t, td)() + + // Setup the meta + m := testMetaBackend(t, nil) + m.AllowExperimentalFeatures = true + + // Get the state store's config + mod, loadDiags := m.loadSingleModule(td) + if loadDiags.HasErrors() { + t.Fatalf("unexpected error when loading test config: %s", loadDiags.Err()) + } + + // Get mock provider factory to be used during init + // + // This imagines a provider called foo that contains + // a pluggable state store implementation called bar. + mock := &testing_provider.MockProvider{ + GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ + Provider: providers.Schema{ + Body: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "region": {Type: cty.String, Optional: true}, + }, + }, + }, + DataSources: map[string]providers.Schema{}, + ResourceTypes: map[string]providers.Schema{}, + ListResourceTypes: map[string]providers.Schema{}, + StateStores: map[string]providers.Schema{ + "foo_bar": { + Body: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "bar": { + Type: cty.String, + Required: true, + }, + }, + }, + }, + }, + }, + } + factory := func() (providers.Interface, error) { + return mock, nil + } + + // Get the operations backend + _, beDiags := m.Backend(&BackendOpts{ + Init: true, + StateStoreConfig: mod.StateStore, + ProviderFactory: factory, + }) + if !beDiags.HasErrors() { + t.Fatal("expected an error to be returned during partial implementation of PSS") + } + wantErr := "Migration from backend to state store is not implemented yet" + if !strings.Contains(beDiags.Err().Error(), wantErr) { + t.Fatalf("expected the returned error to contain %q, but got: %s", wantErr, beDiags.Err()) + } +} + +func TestMetaBackend_configuredStateStoreToBackend(t *testing.T) { + td := t.TempDir() + testCopyDir(t, testFixturePath("state-store-to-backend"), td) + defer testChdir(t, td)() + + // Setup the meta + m := testMetaBackend(t, nil) + m.AllowExperimentalFeatures = true + + // Get the backend's config + mod, loadDiags := m.loadSingleModule(td) + if loadDiags.HasErrors() { + t.Fatalf("unexpected error when loading test config: %s", loadDiags.Err()) + } + + // No mock provider is used here - yet + // Logic will need to be implemented that lets the init have access to + // a factory for the 'old' provider used for PSS previously. This will be + // used when migrating away from PSS entirely, or to a new PSS configuration. + + // Get the operations backend + _, beDiags := m.Backend(&BackendOpts{ + Init: true, + BackendConfig: mod.Backend, + }) + if !beDiags.HasErrors() { + t.Fatal("expected an error to be returned during partial implementation of PSS") + } + wantErr := "Migration from state store to backend is not implemented yet" + if !strings.Contains(beDiags.Err().Error(), wantErr) { + t.Fatalf("expected the returned error to contain %q, but got: %s", wantErr, beDiags.Err()) + } +} + // Reconfiguring with an already configured backend. // This should ignore the existing backend config, and configure the new // backend is if this is the first time. From 4a8e427f2af9bd5ee8f5a0e821996ff395a7d08a Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 11 Jul 2025 16:55:43 +0100 Subject: [PATCH 12/60] Consolidate PSS-related tests at end of the file --- internal/command/meta_backend_test.go | 352 -------------------------- 1 file changed, 352 deletions(-) diff --git a/internal/command/meta_backend_test.go b/internal/command/meta_backend_test.go index 38401b64d2e3..189cae64ec09 100644 --- a/internal/command/meta_backend_test.go +++ b/internal/command/meta_backend_test.go @@ -312,75 +312,6 @@ func TestMetaBackend_configureNewBackend(t *testing.T) { } } -// Newly configured state store -// -// TODO(SarahFrench/radeksimko): currently this test only confirms that we're hitting the switch -// case for this scenario, and will need to be updated when that init feature is implemented. -func TestMetaBackend_configureNewStateStore(t *testing.T) { - td := t.TempDir() - testCopyDir(t, testFixturePath("state-store-new"), td) - defer testChdir(t, td)() - - // Setup the meta - m := testMetaBackend(t, nil) - m.AllowExperimentalFeatures = true - - // Get the state store's config - mod, loadDiags := m.loadSingleModule(td) - if loadDiags.HasErrors() { - t.Fatalf("unexpected error when loading test config: %s", loadDiags.Err()) - } - - // Get mock provider factory to be used during init - // - // This imagines a provider called foo that contains - // a pluggable state store implementation called bar. - mock := &testing_provider.MockProvider{ - GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ - Provider: providers.Schema{ - Body: &configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "region": {Type: cty.String, Optional: true}, - }, - }, - }, - DataSources: map[string]providers.Schema{}, - ResourceTypes: map[string]providers.Schema{}, - ListResourceTypes: map[string]providers.Schema{}, - StateStores: map[string]providers.Schema{ - "foo_bar": { - Body: &configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "bar": { - Type: cty.String, - Required: true, - }, - }, - }, - }, - }, - }, - } - factory := func() (providers.Interface, error) { - return mock, nil - } - - // Get the operations backend - _, beDiags := m.Backend(&BackendOpts{ - Init: true, - StateStoreConfig: mod.StateStore, - ProviderFactory: factory, - }) - if !beDiags.HasErrors() { - t.Fatal("expected an error to be returned during partial implementation of PSS") - } - wantErr := "Configuring a state store for the first time is not implemented yet" - if !strings.Contains(beDiags.Err().Error(), wantErr) { - t.Fatalf("expected the returned error to contain %q, but got: %s", wantErr, beDiags.Err()) - } - -} - // Newly configured backend with prior local state and no remote state func TestMetaBackend_configureNewBackendWithState(t *testing.T) { // Create a temporary working directory that is empty @@ -813,173 +744,6 @@ func TestMetaBackend_changeConfiguredBackend(t *testing.T) { } } -// Changing a configured state store -// -// TODO(SarahFrench/radeksimko): currently this test only confirms that we're hitting the switch -// case for this scenario, and will need to be updated when that init feature is implemented. -// ALSO, this test will need to be split into multiple scenarios in future. -func TestMetaBackend_changeConfiguredStateStore(t *testing.T) { - td := t.TempDir() - testCopyDir(t, testFixturePath("state-store-changed"), td) - defer testChdir(t, td)() - - // Setup the meta - m := testMetaBackend(t, nil) - m.AllowExperimentalFeatures = true - - // Get the state store's config - mod, loadDiags := m.loadSingleModule(td) - if loadDiags.HasErrors() { - t.Fatalf("unexpected error when loading test config: %s", loadDiags.Err()) - } - - // Get mock provider factory to be used during init - // - // This imagines a provider called foo that contains - // a pluggable state store implementation called bar. - mock := &testing_provider.MockProvider{ - GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ - Provider: providers.Schema{ - Body: &configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "region": {Type: cty.String, Optional: true}, - }, - }, - }, - DataSources: map[string]providers.Schema{}, - ResourceTypes: map[string]providers.Schema{}, - ListResourceTypes: map[string]providers.Schema{}, - StateStores: map[string]providers.Schema{ - "foo_bar": { - Body: &configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "bar": { - Type: cty.String, - Required: true, - }, - }, - }, - }, - }, - }, - } - factory := func() (providers.Interface, error) { - return mock, nil - } - - // Get the operations backend - _, beDiags := m.Backend(&BackendOpts{ - Init: true, - StateStoreConfig: mod.StateStore, - ProviderFactory: factory, - }) - if !beDiags.HasErrors() { - t.Fatal("expected an error to be returned during partial implementation of PSS") - } - wantErr := "Changing a state store configuration is not implemented yet" - if !strings.Contains(beDiags.Err().Error(), wantErr) { - t.Fatalf("expected the returned error to contain %q, but got: %s", wantErr, beDiags.Err()) - } -} - -func TestMetaBackend_configuredBackendToStateStore(t *testing.T) { - td := t.TempDir() - testCopyDir(t, testFixturePath("backend-to-state-store"), td) - defer testChdir(t, td)() - - // Setup the meta - m := testMetaBackend(t, nil) - m.AllowExperimentalFeatures = true - - // Get the state store's config - mod, loadDiags := m.loadSingleModule(td) - if loadDiags.HasErrors() { - t.Fatalf("unexpected error when loading test config: %s", loadDiags.Err()) - } - - // Get mock provider factory to be used during init - // - // This imagines a provider called foo that contains - // a pluggable state store implementation called bar. - mock := &testing_provider.MockProvider{ - GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ - Provider: providers.Schema{ - Body: &configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "region": {Type: cty.String, Optional: true}, - }, - }, - }, - DataSources: map[string]providers.Schema{}, - ResourceTypes: map[string]providers.Schema{}, - ListResourceTypes: map[string]providers.Schema{}, - StateStores: map[string]providers.Schema{ - "foo_bar": { - Body: &configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "bar": { - Type: cty.String, - Required: true, - }, - }, - }, - }, - }, - }, - } - factory := func() (providers.Interface, error) { - return mock, nil - } - - // Get the operations backend - _, beDiags := m.Backend(&BackendOpts{ - Init: true, - StateStoreConfig: mod.StateStore, - ProviderFactory: factory, - }) - if !beDiags.HasErrors() { - t.Fatal("expected an error to be returned during partial implementation of PSS") - } - wantErr := "Migration from backend to state store is not implemented yet" - if !strings.Contains(beDiags.Err().Error(), wantErr) { - t.Fatalf("expected the returned error to contain %q, but got: %s", wantErr, beDiags.Err()) - } -} - -func TestMetaBackend_configuredStateStoreToBackend(t *testing.T) { - td := t.TempDir() - testCopyDir(t, testFixturePath("state-store-to-backend"), td) - defer testChdir(t, td)() - - // Setup the meta - m := testMetaBackend(t, nil) - m.AllowExperimentalFeatures = true - - // Get the backend's config - mod, loadDiags := m.loadSingleModule(td) - if loadDiags.HasErrors() { - t.Fatalf("unexpected error when loading test config: %s", loadDiags.Err()) - } - - // No mock provider is used here - yet - // Logic will need to be implemented that lets the init have access to - // a factory for the 'old' provider used for PSS previously. This will be - // used when migrating away from PSS entirely, or to a new PSS configuration. - - // Get the operations backend - _, beDiags := m.Backend(&BackendOpts{ - Init: true, - BackendConfig: mod.Backend, - }) - if !beDiags.HasErrors() { - t.Fatal("expected an error to be returned during partial implementation of PSS") - } - wantErr := "Migration from state store to backend is not implemented yet" - if !strings.Contains(beDiags.Err().Error(), wantErr) { - t.Fatalf("expected the returned error to contain %q, but got: %s", wantErr, beDiags.Err()) - } -} - // Reconfiguring with an already configured backend. // This should ignore the existing backend config, and configure the new // backend is if this is the first time. @@ -1032,84 +796,6 @@ func TestMetaBackend_reconfigureBackendChange(t *testing.T) { } } -// Reconfiguring with an already configured state store. -// This should ignore the existing state_store config, and configure the new -// state store is if this is the first time. -// -// TODO(SarahFrench/radeksimko): currently this test only confirms that we're hitting the switch -// case for this scenario, and will need to be updated when that init feature is implemented. -func TestMetaBackend_reconfigureStateStoreChange(t *testing.T) { - td := t.TempDir() - testCopyDir(t, testFixturePath("state-store-reconfigure"), td) - defer testChdir(t, td)() - - // Setup the meta - m := testMetaBackend(t, nil) - m.AllowExperimentalFeatures = true - - // this should not ask for input - m.input = false - - // cli flag -reconfigure - m.reconfigure = true - - // Get the state store's config - mod, loadDiags := m.loadSingleModule(td) - if loadDiags.HasErrors() { - t.Fatalf("unexpected error when loading test config: %s", loadDiags.Err()) - } - - // Get mock provider factory to be used during init - // - // This imagines a provider called foo that contains - // a pluggable state store implementation called bar. - mock := &testing_provider.MockProvider{ - GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ - Provider: providers.Schema{ - Body: &configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "region": {Type: cty.String, Optional: true}, - }, - }, - }, - DataSources: map[string]providers.Schema{}, - ResourceTypes: map[string]providers.Schema{}, - ListResourceTypes: map[string]providers.Schema{}, - StateStores: map[string]providers.Schema{ - "foo_bar": { - Body: &configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "bar": { - Type: cty.String, - Required: true, - }, - }, - }, - }, - }, - }, - } - factory := func() (providers.Interface, error) { - return mock, nil - } - - // Get the operations backend - _, beDiags := m.Backend(&BackendOpts{ - Init: true, - StateStoreConfig: mod.StateStore, - ProviderFactory: factory, - }) - - if !beDiags.HasErrors() { - t.Fatal("expected an error to be returned during partial implementation of PSS") - } - wantErr := "Configuring a state store for the first time is not implemented yet" - if !strings.Contains(beDiags.Err().Error(), wantErr) { - t.Fatalf("expected the returned error to contain %q, but got: %s", wantErr, beDiags.Err()) - } - -} - // Initializing a backend which supports workspaces and does *not* have // the currently selected workspace should prompt the user with a list of // workspaces to choose from to select a valid one, if more than one workspace @@ -1797,44 +1483,6 @@ func TestMetaBackend_configuredBackendUnset(t *testing.T) { } } -// Unsetting a saved state store -// -// TODO(SarahFrench/radeksimko): currently this test only confirms that we're hitting the switch -// case for this scenario, and will need to be updated when that init feature is implemented. -func TestMetaBackend_configuredStateStoreUnset(t *testing.T) { - td := t.TempDir() - testCopyDir(t, testFixturePath("state-store-unset"), td) - defer testChdir(t, td)() - - // Setup the meta - m := testMetaBackend(t, nil) - m.AllowExperimentalFeatures = true - - // Get the state store's config - mod, loadDiags := m.loadSingleModule(td) - if loadDiags.HasErrors() { - t.Fatalf("unexpected error when loading test config: %s", loadDiags.Err()) - } - - // No mock provider is used here - yet - // Logic will need to be implemented that lets the init have access to - // a factory for the 'old' provider used for PSS previously. This will be - // used when migrating away from PSS entirely, or to a new PSS configuration. - - // Get the operations backend - _, beDiags := m.Backend(&BackendOpts{ - Init: true, - StateStoreConfig: mod.StateStore, - }) - if !beDiags.HasErrors() { - t.Fatal("expected an error to be returned during partial implementation of PSS") - } - wantErr := "Unsetting a state store is not implemented yet" - if !strings.Contains(beDiags.Err().Error(), wantErr) { - t.Fatalf("expected the returned error to contain %q, but got: %s", wantErr, beDiags.Err()) - } -} - // Unsetting a saved backend and copying the remote state func TestMetaBackend_configuredBackendUnsetCopy(t *testing.T) { // Create a temporary working directory that is empty From 55b7c86bff57c95635e7b4f9cc0cb6429433dd88 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Mon, 14 Jul 2025 15:25:56 +0100 Subject: [PATCH 13/60] Make init command output report if a backend or state store is being initialized --- internal/command/views/init.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/command/views/init.go b/internal/command/views/init.go index 35d79e1ba815..c97d2f7ffcf5 100644 --- a/internal/command/views/init.go +++ b/internal/command/views/init.go @@ -182,6 +182,10 @@ var MessageRegistry map[InitMessageCode]InitMessage = map[InitMessageCode]InitMe HumanValue: "\n[reset][bold]Initializing the backend...", JSONValue: "Initializing the backend...", }, + "initializing_state_store_message": { + HumanValue: "\n[reset][bold]Initializing the state store...", + JSONValue: "Initializing the state store...", + }, "initializing_provider_plugin_message": { HumanValue: "\n[reset][bold]Initializing provider plugins...", JSONValue: "Initializing provider plugins...", @@ -194,10 +198,6 @@ var MessageRegistry map[InitMessageCode]InitMessage = map[InitMessageCode]InitMe HumanValue: "\n[reset][bold]Initializing provider plugins found in the state...", JSONValue: "Initializing provider plugins found in the state...", }, - "initializing_state_store_message": { - HumanValue: "\n[reset][bold]Initializing the state store...", - JSONValue: "Initializing the state store...", - }, "dependencies_lock_changes_info": { HumanValue: dependenciesLockChangesInfo, JSONValue: dependenciesLockChangesInfo, From 1e2a403ec57532aab8e6d685e45872679715db90 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Mon, 14 Jul 2025 15:39:04 +0100 Subject: [PATCH 14/60] Only process -backend-config flags for use with backend if the user has passed some through --- internal/command/init.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/internal/command/init.go b/internal/command/init.go index 114ce62a291b..11254e65c19a 100644 --- a/internal/command/init.go +++ b/internal/command/init.go @@ -311,15 +311,19 @@ func (c *InitCommand) initBackend(ctx context.Context, root *configs.Module, ext backendSchema := b.ConfigSchema() backendConfig := root.Backend - backendConfigOverride, overrideDiags := c.backendConfigOverrideBody(extraConfig, backendSchema) - diags = diags.Append(overrideDiags) - if overrideDiags.HasErrors() { - return nil, true, diags + var configOverride hcl.Body + if len(*extraConfig.Items) > 0 { + var overrideDiags tfdiags.Diagnostics + configOverride, overrideDiags = c.backendConfigOverrideBody(extraConfig, backendSchema) + diags = diags.Append(overrideDiags) + if overrideDiags.HasErrors() { + return nil, true, diags + } } opts = &BackendOpts{ BackendConfig: backendConfig, - ConfigOverride: backendConfigOverride, + ConfigOverride: configOverride, Init: true, ViewType: viewType, } From ce02b0cfcef260edf2bb6bd1a5ac1e5b026e929c Mon Sep 17 00:00:00 2001 From: Sarah French Date: Mon, 14 Jul 2025 16:08:11 +0100 Subject: [PATCH 15/60] Implement initialising an uninitialised dir with PSS --- internal/backend/pluggable/pluggable.go | 9 + internal/command/init.go | 3 +- internal/command/init_run.go | 4 +- internal/command/init_run_experiment.go | 7 +- internal/command/meta_backend.go | 437 +++++++++++++++++- internal/getproviders/providerreqs/version.go | 6 + 6 files changed, 456 insertions(+), 10 deletions(-) diff --git a/internal/backend/pluggable/pluggable.go b/internal/backend/pluggable/pluggable.go index 2b7fd8f98d33..920016cbcff7 100644 --- a/internal/backend/pluggable/pluggable.go +++ b/internal/backend/pluggable/pluggable.go @@ -68,6 +68,15 @@ func (p *Pluggable) ConfigSchema() *configschema.Block { return val.Body } +// ProviderSchema returns the schema for the provider implementing the state store. +// +// This isn't part of the backend.Backend interface but is needed in calling code. +// When it's used the backend.Backend will need to be cast to a Pluggable. +func (p *Pluggable) ProviderSchema() *configschema.Block { + schemaResp := p.provider.GetProviderSchema() + return schemaResp.Provider.Body +} + // PrepareConfig validates configuration for the state store in // the state storage provider. The configuration sent from Terraform core // will not include any values from environment variables; it is the diff --git a/internal/command/init.go b/internal/command/init.go index 11254e65c19a..e27d3c2e2013 100644 --- a/internal/command/init.go +++ b/internal/command/init.go @@ -159,7 +159,7 @@ func (c *InitCommand) initCloud(ctx context.Context, root *configs.Module, extra return back, true, diags } -func (c *InitCommand) initBackend(ctx context.Context, root *configs.Module, extraConfig arguments.FlagNameValueSlice, viewType arguments.ViewType, view views.Init) (be backend.Backend, output bool, diags tfdiags.Diagnostics) { +func (c *InitCommand) initBackend(ctx context.Context, root *configs.Module, extraConfig arguments.FlagNameValueSlice, viewType arguments.ViewType, locks *depsfile.Locks, view views.Init) (be backend.Backend, output bool, diags tfdiags.Diagnostics) { ctx, span := tracer.Start(ctx, "initialize backend") _ = ctx // prevent staticcheck from complaining to avoid a maintenance hazard of having the wrong ctx in scope here defer span.End() @@ -272,6 +272,7 @@ func (c *InitCommand) initBackend(ctx context.Context, root *configs.Module, ext opts = &BackendOpts{ StateStoreConfig: root.StateStore, + Locks: locks, ProviderFactory: factory, ConfigOverride: configOverride, Init: true, diff --git a/internal/command/init_run.go b/internal/command/init_run.go index 1e866bc63c9b..bdb637522212 100644 --- a/internal/command/init_run.go +++ b/internal/command/init_run.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/terraform/internal/command/arguments" "github.com/hashicorp/terraform/internal/command/views" "github.com/hashicorp/terraform/internal/configs" + "github.com/hashicorp/terraform/internal/depsfile" "github.com/hashicorp/terraform/internal/states" "github.com/hashicorp/terraform/internal/terraform" "github.com/hashicorp/terraform/internal/tfdiags" @@ -170,7 +171,8 @@ func (c *InitCommand) run(initArgs *arguments.Init, view views.Init) int { case initArgs.Cloud && rootModEarly.CloudConfig != nil: back, backendOutput, backDiags = c.initCloud(ctx, rootModEarly, initArgs.BackendConfig, initArgs.ViewType, view) case initArgs.Backend: - back, backendOutput, backDiags = c.initBackend(ctx, rootModEarly, initArgs.BackendConfig, initArgs.ViewType, view) + var locks *depsfile.Locks // Empty locks- this value is unused when a `backend` is used (vs. a `state_store`) + back, backendOutput, backDiags = c.initBackend(ctx, rootModEarly, initArgs.BackendConfig, initArgs.ViewType, locks, view) default: // load the previously-stored backend config back, backDiags = c.Meta.backendFromState(ctx) diff --git a/internal/command/init_run_experiment.go b/internal/command/init_run_experiment.go index b62b40f515f9..25fe349db5a8 100644 --- a/internal/command/init_run_experiment.go +++ b/internal/command/init_run_experiment.go @@ -205,9 +205,10 @@ func (c *InitCommand) runPssInit(initArgs *arguments.Init, view views.Init) int case initArgs.Cloud && rootModEarly.CloudConfig != nil: back, backendOutput, backDiags = c.initCloud(ctx, rootModEarly, initArgs.BackendConfig, initArgs.ViewType, view) case initArgs.Backend: - // TODO(SarahFrench/radeksimko) - pass information about config locks (`configLocks`) into initBackend to - // enable PSS - back, backendOutput, backDiags = c.initBackend(ctx, rootModEarly, initArgs.BackendConfig, initArgs.ViewType, view) + // This handles case when config contains either backend or state_store blocks. + // This is valid as either can be implementations of backend.Backend, which is what we + // obtain here. + back, backendOutput, backDiags = c.initBackend(ctx, rootModEarly, initArgs.BackendConfig, initArgs.ViewType, configLocks, view) default: // load the previously-stored backend config back, backDiags = c.Meta.backendFromState(ctx) diff --git a/internal/command/meta_backend.go b/internal/command/meta_backend.go index 8b3559e7471a..a02ee408ae91 100644 --- a/internal/command/meta_backend.go +++ b/internal/command/meta_backend.go @@ -19,23 +19,29 @@ import ( "strconv" "strings" + version "github.com/hashicorp/go-version" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hcldec" "github.com/zclconf/go-cty/cty" + "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/backend" "github.com/hashicorp/terraform/internal/backend/backendrun" backendInit "github.com/hashicorp/terraform/internal/backend/init" backendLocal "github.com/hashicorp/terraform/internal/backend/local" + backendPluggable "github.com/hashicorp/terraform/internal/backend/pluggable" "github.com/hashicorp/terraform/internal/cloud" "github.com/hashicorp/terraform/internal/command/arguments" "github.com/hashicorp/terraform/internal/command/clistate" "github.com/hashicorp/terraform/internal/command/views" "github.com/hashicorp/terraform/internal/command/workdir" "github.com/hashicorp/terraform/internal/configs" + "github.com/hashicorp/terraform/internal/depsfile" "github.com/hashicorp/terraform/internal/didyoumean" + "github.com/hashicorp/terraform/internal/getproviders/providerreqs" "github.com/hashicorp/terraform/internal/plans" "github.com/hashicorp/terraform/internal/providers" + "github.com/hashicorp/terraform/internal/states" "github.com/hashicorp/terraform/internal/states/statemgr" "github.com/hashicorp/terraform/internal/terraform" "github.com/hashicorp/terraform/internal/tfdiags" @@ -58,6 +64,13 @@ type BackendOpts struct { // This will only be set if the configuration contains a state_store block. ProviderFactory providers.Factory + // Locks allows state-migration logic to detect when the provider used for pluggable state storage + // during the last init (i.e. what's in the backend state file) is mismatched with the provider + // version in use currently. + // + // This will only be set if the configuration contains a state_store block. + Locks *depsfile.Locks + // ConfigOverride is an hcl.Body that, if non-nil, will be used with // configs.MergeBodies to override the type-specific backend configuration // arguments in Config. @@ -784,11 +797,22 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Di stateStoreConfig.Provider.Name, stateStoreConfig.ProviderAddr, ) - return nil, diags.Append(&hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Not implemented yet", - Detail: "Configuring a state store for the first time is not implemented yet", - }) + + if !opts.Init { + initReason := fmt.Sprintf("Initial configuration of the requested state_store %q in provider %s (%q)", + stateStoreConfig.Type, + stateStoreConfig.Provider.Name, + stateStoreConfig.ProviderAddr, + ) + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "State store initialization required, please run \"terraform init\"", + fmt.Sprintf(strings.TrimSpace(errStateStoreInit), initReason), + )) + return nil, diags + } + + return m.stateStore_C_s(stateStoreConfig, cHash, sMgr, opts) // Migration from state store to backend case backendConfig != nil && s.Backend.Empty() && @@ -1447,6 +1471,211 @@ func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, sMgr *clista return b, diags } +//------------------------------------------------------------------- +// State Store Config Scenarios +// The functions below cover handling all the various scenarios that +// can exist when loading a state store. They are named in the format of +// "stateStore_C_S" where C and S may be upper or lowercase. Lowercase +// means it is false, uppercase means it is true. +// +// The fields are: +// +// * C - State store configuration is set and changed in TF files +// * S - State store configuration is set in the state +// +//------------------------------------------------------------------- + +// Configuring a state_store for the first time. +func (m *Meta) stateStore_C_s(c *configs.StateStore, cHash int, sMgr *clistate.LocalState, opts *BackendOpts) (backend.Backend, tfdiags.Diagnostics) { + var diags tfdiags.Diagnostics + + vt := arguments.ViewJSON + // Set default viewtype if none was set as the StateLocker needs to know exactly + // what viewType we want to have. + if opts == nil || opts.ViewType != vt { + vt = arguments.ViewHuman + } + + // Grab a purely local backend to get the local state if it exists + localB, localBDiags := m.Backend(&BackendOpts{ForceLocal: true, Init: true}) + if localBDiags.HasErrors() { + diags = diags.Append(localBDiags) + return nil, diags + } + + workspaces, err := localB.Workspaces() + if err != nil { + diags = diags.Append(fmt.Errorf(errBackendLocalRead, err)) + return nil, diags + } + + var localStates []statemgr.Full + for _, workspace := range workspaces { + localState, err := localB.StateMgr(workspace) + if err != nil { + diags = diags.Append(fmt.Errorf(errBackendLocalRead, err)) + return nil, diags + } + if err := localState.RefreshState(); err != nil { + diags = diags.Append(fmt.Errorf(errBackendLocalRead, err)) + return nil, diags + } + + // We only care about non-empty states. + if localS := localState.State(); !localS.Empty() { + log.Printf("[TRACE] Meta.Backend: will need to migrate workspace states because of existing %q workspace", workspace) + localStates = append(localStates, localState) + } else { + log.Printf("[TRACE] Meta.Backend: ignoring local %q workspace because its state is empty", workspace) + } + } + + // Get the state store as an instance of backend.Backend + b, storeConfigVal, providerConfigVal, moreDiags := m.stateStoreInitFromConfig(c, opts) + diags = diags.Append(moreDiags) + if diags.HasErrors() { + return nil, diags + } + + if len(localStates) > 0 { + // Migrate any local states into the new state store + err = m.backendMigrateState(&backendMigrateOpts{ + SourceType: "local", + DestinationType: c.Type, + Source: localB, + Destination: b, + ViewType: vt, + }) + if err != nil { + diags = diags.Append(err) + return nil, diags + } + + // We remove the local state after migration to prevent confusion + // As we're migrating to a state store we don't have insight into whether it stores + // files locally at all, and whether those local files conflict with the location of + // the old local state. + log.Printf("[TRACE] Meta.Backend: removing old state snapshots from old backend") + for _, localState := range localStates { + // We always delete the local state, unless that was our new state too. + if err := localState.WriteState(nil); err != nil { + diags = diags.Append(fmt.Errorf(errBackendMigrateLocalDelete, err)) + return nil, diags + } + if err := localState.PersistState(nil); err != nil { + diags = diags.Append(fmt.Errorf(errBackendMigrateLocalDelete, err)) + return nil, diags + } + } + } + + if m.stateLock { + view := views.NewStateLocker(vt, m.View) + stateLocker := clistate.NewLocker(m.stateLockTimeout, view) + if err := stateLocker.Lock(sMgr, "state_store from plan"); err != nil { + diags = diags.Append(fmt.Errorf("Error locking state: %s", err)) + return nil, diags + } + defer stateLocker.Unlock() + } + + // Store the state_store metadata in our saved state location + s := sMgr.State() + if s == nil { + s = workdir.NewBackendStateFile() + } + + var pVersion *version.Version + if c.ProviderAddr.Equals(addrs.NewBuiltInProvider("terraform")) { + // If we're handling the builtin "terraform" provider then there's no version information to store in the dependency lock file, so don't access it. + // We must record a value into the backend state file, and we cannot include a value that changes (e.g. the Terraform core binary version) as migration + // is impossible with builtin providers. + // So, we use a hardcoded version number of 42. + pVersion, err = version.NewVersion("0.42.0") + if err != nil { + diags = diags.Append(fmt.Errorf("Error when creating a backend state file containing a builtin provider. This is a bug in Terraform and should be reported: %w", + err)) + return nil, diags + } + } else { + pLock := opts.Locks.Provider(c.ProviderAddr) + if pLock == nil { + diags = diags.Append(fmt.Errorf("The provider %s (%q) is not present in the lockfile, despite being used for state store %q. This is a bug in Terraform and should be reported: %w", + c.Provider.Name, + c.ProviderAddr, + c.Type, + err)) + return nil, diags + } + var err error + pVersion, err = providerreqs.GoVersionFromVersion(pLock.Version()) + if err != nil { + diags = diags.Append(fmt.Errorf("Failed obtain the in-use version of provider %s (%q) when recording backend state for state store %q. This is a bug in Terraform and should be reported: %w", + c.Provider.Name, + c.ProviderAddr, + c.Type, + err)) + return nil, diags + } + } + s.StateStore = &workdir.StateStoreConfigState{ + Type: c.Type, + Hash: uint64(cHash), + Provider: &workdir.ProviderConfigState{ + Source: &c.ProviderAddr, + Version: pVersion, + }, + } + s.StateStore.SetConfig(storeConfigVal, b.ConfigSchema()) + if plug, ok := b.(*backendPluggable.Pluggable); ok { + // We need to convert away from backend.Backend interface to use the method + // for accessing the provider schema. + s.StateStore.Provider.SetConfig(providerConfigVal, plug.ProviderSchema()) + } + + // Verify that selected workspace exists in the state store. + // TODO (SarahFrench/radeksimko) - TF core should be responsible for creating the new workspace. + // > Is this the correct place to do so? + // > Should we prompt the user to approve creating a new workspace? + if opts.Init && b != nil { + err := m.selectWorkspace(b) + if strings.Contains(err.Error(), "No existing workspaces") { + // Make the default workspace. All other workspaces are user-created via the workspace commands. + bStateMgr, err := b.StateMgr(backend.DefaultStateName) + if err != nil { + diags = diags.Append(fmt.Errorf("Failed to create a state manager for state store %q in provider %s (%q). This is a bug in Terraform and should be reported: %w", + c.Type, + c.Provider.Name, + c.ProviderAddr, + err)) + return nil, diags + } + emptyState := states.NewState() + if err := bStateMgr.WriteState(emptyState); err != nil { + diags = diags.Append(fmt.Errorf(errStateStoreWorkspaceCreate, c.Type, err)) + return nil, diags + } + if err := sMgr.PersistState(); err != nil { + diags = diags.Append(fmt.Errorf(errStateStoreWorkspaceCreate, c.Type, err)) + return nil, diags + } + } else if err != nil { + diags = diags.Append(err) + } + } + + if err := sMgr.WriteState(s); err != nil { + diags = diags.Append(fmt.Errorf(errBackendWriteSaved, err)) + return nil, diags + } + if err := sMgr.PersistState(); err != nil { + diags = diags.Append(fmt.Errorf(errBackendWriteSaved, err)) + return nil, diags + } + + return b, diags +} + // Initializing a saved backend from the cache file (legacy state file) // // TODO: This is extremely similar to Meta.backendFromState() but for legacy reasons this is the @@ -1644,6 +1873,181 @@ func (m *Meta) backendInitFromConfig(c *configs.Backend) (backend.Backend, cty.V return b, configVal, diags } +// stateStoreInitFromConfig returns an initialized and configured state store, using the backend.Backend interface. +// During this process: +// > Users are prompted for input if required attributes are missing. +// > The provider is configured, after validating provider config +// > The state store is configured, after validating state_store config +func (m *Meta) stateStoreInitFromConfig(c *configs.StateStore, opts *BackendOpts) (backend.Backend, cty.Value, cty.Value, tfdiags.Diagnostics) { + var diags tfdiags.Diagnostics + + provider, err := opts.ProviderFactory() + if err != nil { + diags = diags.Append(fmt.Errorf("error when obtaining provider instance during state store initialization: %w", err)) + return nil, cty.NilVal, cty.NilVal, diags + } + // We purposefully don't have a deferred call to the provider's Close method here because the calling code needs a + // running provider instance inside the returned backend.Backend instance. + // Stopping the provider process is the responsibility of the calling code. + + resp := provider.GetProviderSchema() + + if len(resp.StateStores) == 0 { + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Provider does not support pluggable state storage", + Detail: fmt.Sprintf("There are no state stores implemented by provider %s (%q)", + c.Provider.Name, + c.ProviderAddr), + Subject: &c.DeclRange, + }) + return nil, cty.NilVal, cty.NilVal, diags + } + + schema, exists := resp.StateStores[c.Type] + if !exists { + suggestions := slices.Sorted(maps.Keys(resp.StateStores)) + suggestion := didyoumean.NameSuggestion(c.Type, suggestions) + if suggestion != "" { + suggestion = fmt.Sprintf(" Did you mean %q?", suggestion) + } + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "State store not implemented by the provider", + Detail: fmt.Sprintf("State store %q is not implemented by provider %s (%q)%s", + c.Type, c.Provider.Name, + c.ProviderAddr, suggestion), + Subject: &c.DeclRange, + }) + return nil, cty.NilVal, cty.NilVal, diags + } + + // Handle the nested provider block. + pDecSpec := resp.Provider.Body.NoneRequired().DecoderSpec() + pConfig := c.Provider.Config + providerConfigVal, pDecDiags := hcldec.Decode(pConfig, pDecSpec, nil) + diags = diags.Append(pDecDiags) + + if !providerConfigVal.IsWhollyKnown() { + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Unknown values within state_store's nested provider block", + "The `terraform` configuration block should contain only concrete and static values. Another diagnostic should contain more information about which part of the configuration is problematic.")) + return nil, cty.NilVal, cty.NilVal, diags + } + + if m.Input() { + var err error + // TODO (SarahFrench/radeksimko) - Should we allow input to the provider block? + providerConfigVal, err = m.inputForSchema(providerConfigVal, resp.Provider.Body) + if err != nil { + diags = diags.Append( + fmt.Errorf("Error asking for input to configure provider %s (%q) for state store %q: %s", + c.Provider.Name, + c.ProviderAddr, + c.Type, + err, + ), + ) + return nil, cty.NilVal, cty.NilVal, diags + } + + // We get an unknown here if the if the user aborted input, but we can't + // turn that into a config value, so set it to null and let the provider + // handle it in PrepareConfig. + if !providerConfigVal.IsKnown() { + providerConfigVal = cty.NullVal(providerConfigVal.Type()) + } + } + + // Handle the schema for the state store itself, excluding the provider block. + ssdecSpec := schema.Body.NoneRequired().DecoderSpec() + stateStoreConfigVal, ssDecDiags := hcldec.Decode(c.Config, ssdecSpec, nil) + diags = diags.Append(ssDecDiags) + if ssDecDiags.HasErrors() { + return nil, cty.NilVal, cty.NilVal, diags + } + + if !stateStoreConfigVal.IsWhollyKnown() { + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Unknown values within state_store definition", + "The `terraform` configuration block should contain only concrete and static values. Another diagnostic should contain more information about which part of the configuration is problematic.")) + return nil, cty.NilVal, cty.NilVal, diags + } + + if m.Input() { + var err error + // TODO (SarahFrench/radeksimko) + // > Should we do this? + // > Could this accidentally prompt users to supply values in the nested provider block? + stateStoreConfigVal, err = m.inputForSchema(stateStoreConfigVal, schema.Body) + if err != nil { + diags = diags.Append(fmt.Errorf("Error asking for input to configure state store %q: %s", c.Type, err)) + return nil, cty.NilVal, cty.NilVal, diags + } + + // We get an unknown here if the if the user aborted input, but we can't + // turn that into a config value, so set it to null and let the provider + // handle it in PrepareConfig. + if !stateStoreConfigVal.IsKnown() { + stateStoreConfigVal = cty.NullVal(stateStoreConfigVal.Type()) + } + } + + // Validate and configure the provider + + // TODO (SarahFrench/radeksimko) : deal with marks + validateResp := provider.ValidateProviderConfig(providers.ValidateProviderConfigRequest{ + Config: providerConfigVal, + }) + diags = diags.Append(validateResp.Diagnostics) + if validateResp.Diagnostics.HasErrors() { + return nil, cty.NilVal, cty.NilVal, diags + } + + configureResp := provider.ConfigureProvider(providers.ConfigureProviderRequest{ + // TODO TerraformVersion: , + Config: validateResp.PreparedConfig, + // TODO ClientCapabilities: , + }) + diags = diags.Append(configureResp.Diagnostics) + if configureResp.Diagnostics.HasErrors() { + return nil, cty.NilVal, cty.NilVal, diags + } + + // Validate Store Config + // TODO (SarahFrench/radeksimko) : deal with marks + validateStoreResp := provider.ValidateStateStoreConfig(providers.ValidateStateStoreConfigRequest{ + TypeName: c.Type, + Config: stateStoreConfigVal, + }) + diags = diags.Append(validateStoreResp.Diagnostics) + if validateStoreResp.Diagnostics.HasErrors() { + return nil, cty.NilVal, cty.NilVal, diags + } + + // Configure State Store + cfgStoreResp := provider.ConfigureStateStore(providers.ConfigureStateStoreRequest{ + TypeName: c.Type, + Config: stateStoreConfigVal, + }) + diags = diags.Append(cfgStoreResp.Diagnostics) + if cfgStoreResp.Diagnostics.HasErrors() { + return nil, cty.NilVal, cty.NilVal, diags + } + + // Now we have a fully configured state store, ready to be used. + // To make it usable we need to return it in a backend.Backend interface. + b, err := backendPluggable.NewPluggable(provider, c.Type) + if err != nil { + diags = diags.Append(err) + return nil, cty.NilVal, cty.NilVal, diags + } + + return b, stateStoreConfigVal, providerConfigVal, diags +} + // Helper method to get aliases from the enhanced backend and alias them // in the Meta service discovery. It's unfortunate that the Meta backend // is modifying the service discovery at this level, but the owner @@ -1825,6 +2229,21 @@ hasn't changed and try again. At this point, no changes to your existing configuration or state have been made. ` +const errStateStoreInit = ` +Reason: %s +The "state store" is the interface that Terraform uses to store state when +performing operations on the local machine. If this message is showing up, +it means that the Terraform configuration you're using is using a custom +configuration for state storage in Terraform. +Changes to state store configurations require reinitialization. This allows +Terraform to set up the new configuration, copy existing state, etc. Please run +"terraform init" with either the "-reconfigure" or "-migrate-state" flags to +use the current configuration. +If the change reason above is incorrect, please verify your configuration +hasn't changed and try again. At this point, no changes to your existing +configuration or state have been made. +` + const errBackendInitCloud = ` Reason: %s. @@ -1845,6 +2264,14 @@ are usually due to simple file permission errors. Please look at the error above, resolve it, and try again. ` +const errStateStoreWorkspaceCreate = ` +Error creating the default workspace using pluggable state store %s: %s + +This could be a bug in the provider used for state storage, or a bug in +Terraform. Please file an issue with the provider developers before reporting +a bug for Terraform. +` + const outputBackendMigrateChange = ` Terraform detected that the backend type changed from %q to %q. ` diff --git a/internal/getproviders/providerreqs/version.go b/internal/getproviders/providerreqs/version.go index 579eceb9ac4c..cc78ccdcbb44 100644 --- a/internal/getproviders/providerreqs/version.go +++ b/internal/getproviders/providerreqs/version.go @@ -20,6 +20,8 @@ import ( "github.com/apparentlymart/go-versions/versions" "github.com/apparentlymart/go-versions/versions/constraints" + "github.com/hashicorp/go-version" + "github.com/hashicorp/terraform/internal/addrs" ) @@ -86,6 +88,10 @@ func ParseVersion(str string) (Version, error) { return versions.ParseVersion(str) } +func GoVersionFromVersion(v Version) (*version.Version, error) { + return version.NewVersion(v.String()) +} + // MustParseVersion is a variant of ParseVersion that panics if it encounters // an error while parsing. func MustParseVersion(str string) Version { From 3dec5b01d09ce75d61a8d86b8a86e677a51ed495 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Thu, 7 Aug 2025 11:46:48 +0100 Subject: [PATCH 16/60] Fix call to `ConfigureProvider` by avoiding use of returned config value from `ValidateProviderConfig` --- internal/command/meta_backend.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/command/meta_backend.go b/internal/command/meta_backend.go index a02ee408ae91..105673d468b2 100644 --- a/internal/command/meta_backend.go +++ b/internal/command/meta_backend.go @@ -2008,7 +2008,7 @@ func (m *Meta) stateStoreInitFromConfig(c *configs.StateStore, opts *BackendOpts configureResp := provider.ConfigureProvider(providers.ConfigureProviderRequest{ // TODO TerraformVersion: , - Config: validateResp.PreparedConfig, + Config: providerConfigVal, // TODO ClientCapabilities: , }) diags = diags.Append(configureResp.Diagnostics) From 37f326bef23a8918e65401de72c33e1d89ed58bd Mon Sep 17 00:00:00 2001 From: Sarah French Date: Thu, 7 Aug 2025 11:56:22 +0100 Subject: [PATCH 17/60] Add TF version to `ConfigureProviderRequest` --- internal/command/meta_backend.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/command/meta_backend.go b/internal/command/meta_backend.go index 105673d468b2..c08938fcb26f 100644 --- a/internal/command/meta_backend.go +++ b/internal/command/meta_backend.go @@ -45,6 +45,7 @@ import ( "github.com/hashicorp/terraform/internal/states/statemgr" "github.com/hashicorp/terraform/internal/terraform" "github.com/hashicorp/terraform/internal/tfdiags" + tfversion "github.com/hashicorp/terraform/version" ) // BackendOpts are the options used to initialize a backendrun.OperationsBackend. @@ -2007,9 +2008,9 @@ func (m *Meta) stateStoreInitFromConfig(c *configs.StateStore, opts *BackendOpts } configureResp := provider.ConfigureProvider(providers.ConfigureProviderRequest{ - // TODO TerraformVersion: , + TerraformVersion: tfversion.String(), Config: providerConfigVal, - // TODO ClientCapabilities: , + // TODO ClientCapabilities? }) diags = diags.Append(configureResp.Diagnostics) if configureResp.Diagnostics.HasErrors() { From 2136e3ad0c5069394f70adbdef163200a6e724d2 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Thu, 7 Aug 2025 17:17:42 +0100 Subject: [PATCH 18/60] Update test to assert that the newly-initialised backend is reflected in the backend state file --- internal/command/meta_backend_test.go | 53 ++++++++++++++++--- .../command/testdata/state-store-new/main.tf | 4 +- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/internal/command/meta_backend_test.go b/internal/command/meta_backend_test.go index 189cae64ec09..ac7e5959ccf1 100644 --- a/internal/command/meta_backend_test.go +++ b/internal/command/meta_backend_test.go @@ -13,6 +13,7 @@ import ( "strings" "testing" + "github.com/apparentlymart/go-versions/versions" "github.com/hashicorp/cli" "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/backend" @@ -21,6 +22,8 @@ import ( "github.com/hashicorp/terraform/internal/configs" "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/copy" + "github.com/hashicorp/terraform/internal/depsfile" + "github.com/hashicorp/terraform/internal/getproviders/providerreqs" "github.com/hashicorp/terraform/internal/plans" "github.com/hashicorp/terraform/internal/providers" testing_provider "github.com/hashicorp/terraform/internal/providers/testing" @@ -2090,6 +2093,7 @@ func TestMetaBackend_configureNewStateStore(t *testing.T) { // // This imagines a provider called foo that contains // a pluggable state store implementation called bar. + pssName := "foo_bar" mock := &testing_provider.MockProvider{ GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ Provider: providers.Schema{ @@ -2103,7 +2107,7 @@ func TestMetaBackend_configureNewStateStore(t *testing.T) { ResourceTypes: map[string]providers.Schema{}, ListResourceTypes: map[string]providers.Schema{}, StateStores: map[string]providers.Schema{ - "foo_bar": { + pssName: { Body: &configschema.Block{ Attributes: map[string]*configschema.Attribute{ "bar": { @@ -2120,20 +2124,55 @@ func TestMetaBackend_configureNewStateStore(t *testing.T) { return mock, nil } - // Get the operations backend + // Create locks - these would normally be the locks derived from config + locks := depsfile.NewLocks() + constraint, err := providerreqs.ParseVersionConstraints(">9.0.0") + if err != nil { + t.Fatalf("test setup failed when making constraint: %s", err) + } + expectedVersionString := "9.9.9" + expectedProviderSource := "registry.terraform.io/my-org/foo" + locks.SetProvider( + addrs.MustParseProviderSourceString(expectedProviderSource), + versions.MustParseVersion(expectedVersionString), + constraint, + []providerreqs.Hash{"h1:foo"}, + ) + + // Act - get the operations backend _, beDiags := m.Backend(&BackendOpts{ Init: true, StateStoreConfig: mod.StateStore, ProviderFactory: factory, + Locks: locks, }) - if !beDiags.HasErrors() { - t.Fatal("expected an error to be returned during partial implementation of PSS") + if beDiags.HasErrors() { + t.Fatalf("unexpected error: %s", err) } - wantErr := "Configuring a state store for the first time is not implemented yet" - if !strings.Contains(beDiags.Err().Error(), wantErr) { - t.Fatalf("expected the returned error to contain %q, but got: %s", wantErr, beDiags.Err()) + + // Check the backend state file exists & assert its contents + s := testDataStateRead(t, filepath.Join(DefaultDataDir, backendLocal.DefaultStateFilename)) + if s == nil { + t.Fatal("expected backend state file to be created, but it was missing") } + if s.StateStore.Type != pssName { + t.Fatalf("backend state file contains unexpected state store type, want %q, got %q", pssName, s.StateStore.Type) + } + if s.StateStore.Provider.Version.String() != expectedVersionString { + t.Fatalf("backend state file contains unexpected version, want %q, got %q", expectedVersionString, s.StateStore.Provider.Version) + } + if s.StateStore.Provider.Source.String() != expectedProviderSource { + t.Fatalf("backend state file contains unexpected source, want %q, got %q", expectedProviderSource, s.StateStore.Provider.Source) + } + expectedProviderConfig := "{ \"region\": \"mars\" }" + expectedStoreConfig := "{ \"bar\": \"foobar\" }" + if cleanString(string(s.StateStore.Provider.ConfigRaw)) != expectedProviderConfig { + t.Fatalf("backend state file contains unexpected raw config data for the provider, want %q, got %q", expectedProviderConfig, cleanString(string(s.StateStore.Provider.ConfigRaw))) + } + if cleanString(string(s.StateStore.ConfigRaw)) != expectedStoreConfig { + t.Fatalf("backend state file contains unexpected raw config data for the state store, want %q, got %q", expectedStoreConfig, cleanString(string(s.StateStore.ConfigRaw))) + } } // Unsetting a saved state store diff --git a/internal/command/testdata/state-store-new/main.tf b/internal/command/testdata/state-store-new/main.tf index 72643dbb9505..6ae0ba26acb7 100644 --- a/internal/command/testdata/state-store-new/main.tf +++ b/internal/command/testdata/state-store-new/main.tf @@ -5,7 +5,9 @@ terraform { } } state_store "foo_bar" { - provider "foo" {} + provider "foo" { + region = "mars" + } bar = "foobar" } From 19e735a949d317cf7578a7afb0b3ffa336a7849c Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 8 Aug 2025 10:29:08 +0100 Subject: [PATCH 19/60] Update test to test method's behaviour during init and non-init commands --- internal/command/meta_backend_test.go | 203 +++++++++++++++----------- 1 file changed, 114 insertions(+), 89 deletions(-) diff --git a/internal/command/meta_backend_test.go b/internal/command/meta_backend_test.go index ac7e5959ccf1..ff295d6aebf5 100644 --- a/internal/command/meta_backend_test.go +++ b/internal/command/meta_backend_test.go @@ -2071,107 +2071,132 @@ func Test_determineInitReason(t *testing.T) { } // Newly configured state store -// -// TODO(SarahFrench/radeksimko): currently this test only confirms that we're hitting the switch -// case for this scenario, and will need to be updated when that init feature is implemented. +// Working directory has state_store in config but no preexisting backend state file func TestMetaBackend_configureNewStateStore(t *testing.T) { - td := t.TempDir() - testCopyDir(t, testFixturePath("state-store-new"), td) - t.Chdir(td) - // Setup the meta - m := testMetaBackend(t, nil) - m.AllowExperimentalFeatures = true - - // Get the state store's config - mod, loadDiags := m.loadSingleModule(td) - if loadDiags.HasErrors() { - t.Fatalf("unexpected error when loading test config: %s", loadDiags.Err()) + cases := map[string]struct { + isInitCommand bool + expectedError string + }{ + "during an init command, the working directory is initialized and a backend state file is created": { + isInitCommand: true, + }, + "during a non-init command, the command ends in with an error telling the user to run an init command": { + isInitCommand: false, + expectedError: "State store initialization required, please run \"terraform init\": Reason: Initial configuration of the requested state_store \"foo_bar\" in provider foo (\"registry.terraform.io/my-org/foo\")", + }, } - // Get mock provider factory to be used during init - // - // This imagines a provider called foo that contains - // a pluggable state store implementation called bar. - pssName := "foo_bar" - mock := &testing_provider.MockProvider{ - GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ - Provider: providers.Schema{ - Body: &configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "region": {Type: cty.String, Optional: true}, + for tn, tc := range cases { + t.Run(tn, func(t *testing.T) { + td := t.TempDir() + testCopyDir(t, testFixturePath("state-store-new"), td) + t.Chdir(td) + + // Setup the meta + m := testMetaBackend(t, nil) + m.AllowExperimentalFeatures = true + + // Get the state store's config + mod, loadDiags := m.loadSingleModule(td) + if loadDiags.HasErrors() { + t.Fatalf("unexpected error when loading test config: %s", loadDiags.Err()) + } + + // Get mock provider factory to be used during init + // + // This imagines a provider called foo that contains + // a pluggable state store implementation called bar. + pssName := "foo_bar" + mock := &testing_provider.MockProvider{ + GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ + Provider: providers.Schema{ + Body: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "region": {Type: cty.String, Optional: true}, + }, + }, }, - }, - }, - DataSources: map[string]providers.Schema{}, - ResourceTypes: map[string]providers.Schema{}, - ListResourceTypes: map[string]providers.Schema{}, - StateStores: map[string]providers.Schema{ - pssName: { - Body: &configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "bar": { - Type: cty.String, - Required: true, + DataSources: map[string]providers.Schema{}, + ResourceTypes: map[string]providers.Schema{}, + ListResourceTypes: map[string]providers.Schema{}, + StateStores: map[string]providers.Schema{ + pssName: { + Body: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "bar": { + Type: cty.String, + Required: true, + }, + }, }, }, }, }, - }, - }, - } - factory := func() (providers.Interface, error) { - return mock, nil - } - - // Create locks - these would normally be the locks derived from config - locks := depsfile.NewLocks() - constraint, err := providerreqs.ParseVersionConstraints(">9.0.0") - if err != nil { - t.Fatalf("test setup failed when making constraint: %s", err) - } - expectedVersionString := "9.9.9" - expectedProviderSource := "registry.terraform.io/my-org/foo" - locks.SetProvider( - addrs.MustParseProviderSourceString(expectedProviderSource), - versions.MustParseVersion(expectedVersionString), - constraint, - []providerreqs.Hash{"h1:foo"}, - ) + } + factory := func() (providers.Interface, error) { + return mock, nil + } - // Act - get the operations backend - _, beDiags := m.Backend(&BackendOpts{ - Init: true, - StateStoreConfig: mod.StateStore, - ProviderFactory: factory, - Locks: locks, - }) - if beDiags.HasErrors() { - t.Fatalf("unexpected error: %s", err) - } + // Create locks - these would normally be the locks derived from config + locks := depsfile.NewLocks() + constraint, err := providerreqs.ParseVersionConstraints(">9.0.0") + if err != nil { + t.Fatalf("test setup failed when making constraint: %s", err) + } + expectedVersionString := "9.9.9" + expectedProviderSource := "registry.terraform.io/my-org/foo" + locks.SetProvider( + addrs.MustParseProviderSourceString(expectedProviderSource), + versions.MustParseVersion(expectedVersionString), + constraint, + []providerreqs.Hash{"h1:foo"}, + ) + + // Act - get the operations backend + _, beDiags := m.Backend(&BackendOpts{ + Init: tc.isInitCommand, // Changes with test case + StateStoreConfig: mod.StateStore, + ProviderFactory: factory, + Locks: locks, + }) + if beDiags.HasErrors() { + if tc.expectedError == "" { + t.Fatalf("unexpected error: %s", err) + } + if !strings.Contains(cleanString(beDiags.Err().Error()), tc.expectedError) { + t.Fatalf("expected error to contain %s, but instead got: %s", tc.expectedError, cleanString(beDiags.Err().Error())) + } + return // error is as expected + } + if tc.expectedError != "" && !beDiags.HasErrors() { + t.Fatal("expected error missing") + } - // Check the backend state file exists & assert its contents - s := testDataStateRead(t, filepath.Join(DefaultDataDir, backendLocal.DefaultStateFilename)) - if s == nil { - t.Fatal("expected backend state file to be created, but it was missing") - } + // Check the backend state file exists & assert its contents + s := testDataStateRead(t, filepath.Join(DefaultDataDir, backendLocal.DefaultStateFilename)) + if s == nil { + t.Fatal("expected backend state file to be created, but it was missing") + } - if s.StateStore.Type != pssName { - t.Fatalf("backend state file contains unexpected state store type, want %q, got %q", pssName, s.StateStore.Type) - } - if s.StateStore.Provider.Version.String() != expectedVersionString { - t.Fatalf("backend state file contains unexpected version, want %q, got %q", expectedVersionString, s.StateStore.Provider.Version) - } - if s.StateStore.Provider.Source.String() != expectedProviderSource { - t.Fatalf("backend state file contains unexpected source, want %q, got %q", expectedProviderSource, s.StateStore.Provider.Source) - } - expectedProviderConfig := "{ \"region\": \"mars\" }" - expectedStoreConfig := "{ \"bar\": \"foobar\" }" - if cleanString(string(s.StateStore.Provider.ConfigRaw)) != expectedProviderConfig { - t.Fatalf("backend state file contains unexpected raw config data for the provider, want %q, got %q", expectedProviderConfig, cleanString(string(s.StateStore.Provider.ConfigRaw))) - } - if cleanString(string(s.StateStore.ConfigRaw)) != expectedStoreConfig { - t.Fatalf("backend state file contains unexpected raw config data for the state store, want %q, got %q", expectedStoreConfig, cleanString(string(s.StateStore.ConfigRaw))) + if s.StateStore.Type != pssName { + t.Fatalf("backend state file contains unexpected state store type, want %q, got %q", pssName, s.StateStore.Type) + } + if s.StateStore.Provider.Version.String() != expectedVersionString { + t.Fatalf("backend state file contains unexpected version, want %q, got %q", expectedVersionString, s.StateStore.Provider.Version) + } + if s.StateStore.Provider.Source.String() != expectedProviderSource { + t.Fatalf("backend state file contains unexpected source, want %q, got %q", expectedProviderSource, s.StateStore.Provider.Source) + } + expectedProviderConfig := "{ \"region\": \"mars\" }" + expectedStoreConfig := "{ \"bar\": \"foobar\" }" + if cleanString(string(s.StateStore.Provider.ConfigRaw)) != expectedProviderConfig { + t.Fatalf("backend state file contains unexpected raw config data for the provider, want %q, got %q", expectedProviderConfig, cleanString(string(s.StateStore.Provider.ConfigRaw))) + } + if cleanString(string(s.StateStore.ConfigRaw)) != expectedStoreConfig { + t.Fatalf("backend state file contains unexpected raw config data for the state store, want %q, got %q", expectedStoreConfig, cleanString(string(s.StateStore.ConfigRaw))) + } + }) } } From 146019f6281cba03e9376a22587c456b38e3e08a Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 8 Aug 2025 11:28:46 +0100 Subject: [PATCH 20/60] Add godoc comment to `GoVersionFromVersion` --- internal/getproviders/providerreqs/version.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/getproviders/providerreqs/version.go b/internal/getproviders/providerreqs/version.go index cc78ccdcbb44..2fc15fc499de 100644 --- a/internal/getproviders/providerreqs/version.go +++ b/internal/getproviders/providerreqs/version.go @@ -88,6 +88,8 @@ func ParseVersion(str string) (Version, error) { return versions.ParseVersion(str) } +// GoVersionFromVersion converts a Version from the providerreqs package +// into a Version from the hashicorp/go-version module. func GoVersionFromVersion(v Version) (*version.Version, error) { return version.NewVersion(v.String()) } From 6e5834755a245a1acff226b9ee82bdd1abc4395a Mon Sep 17 00:00:00 2001 From: Radek Simko Date: Mon, 4 Aug 2025 15:23:38 +0100 Subject: [PATCH 21/60] command: Prompt user for default workspace creation --- internal/command/meta_backend.go | 61 ++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/internal/command/meta_backend.go b/internal/command/meta_backend.go index c08938fcb26f..b27303478dac 100644 --- a/internal/command/meta_backend.go +++ b/internal/command/meta_backend.go @@ -1635,31 +1635,56 @@ func (m *Meta) stateStore_C_s(c *configs.StateStore, cHash int, sMgr *clistate.L } // Verify that selected workspace exists in the state store. - // TODO (SarahFrench/radeksimko) - TF core should be responsible for creating the new workspace. - // > Is this the correct place to do so? - // > Should we prompt the user to approve creating a new workspace? if opts.Init && b != nil { err := m.selectWorkspace(b) if strings.Contains(err.Error(), "No existing workspaces") { - // Make the default workspace. All other workspaces are user-created via the workspace commands. - bStateMgr, err := b.StateMgr(backend.DefaultStateName) + ws, err := m.Workspace() if err != nil { - diags = diags.Append(fmt.Errorf("Failed to create a state manager for state store %q in provider %s (%q). This is a bug in Terraform and should be reported: %w", - c.Type, - c.Provider.Name, - c.ProviderAddr, - err)) - return nil, diags - } - emptyState := states.NewState() - if err := bStateMgr.WriteState(emptyState); err != nil { - diags = diags.Append(fmt.Errorf(errStateStoreWorkspaceCreate, c.Type, err)) + diags = diags.Append(fmt.Errorf("Failed to check current workspace: %w", err)) return nil, diags } - if err := sMgr.PersistState(); err != nil { - diags = diags.Append(fmt.Errorf(errStateStoreWorkspaceCreate, c.Type, err)) - return nil, diags + + if m.Input() && ws == backend.DefaultStateName { + input := m.UIInput() + desc := fmt.Sprintf("Terraform will create the %q workspace via %q.\n"+ + "Only 'yes' will be accepted to approve.", backend.DefaultStateName, c.Type) + v, err := input.Input(context.Background(), &terraform.InputOpts{ + Id: "approve", + Query: fmt.Sprintf("Workspace %q does not exit, would you like to create one?", backend.DefaultStateName), + Description: desc, + }) + if err != nil { + diags = diags.Append(fmt.Errorf("Failed to confirm default workspace creation: %w", err)) + return nil, diags + } + if v != "yes" { + diags = diags.Append(errors.New("Failed to create default workspace")) + return nil, diags + } + + // TODO: Confirm if defaulting to creation on first use (rather than error) is a good idea + // Make the default workspace. All other workspaces are user-created via the workspace commands. + bStateMgr, err := b.StateMgr(backend.DefaultStateName) + if err != nil { + diags = diags.Append(fmt.Errorf("Failed to create a state manager for state store %q in provider %s (%q). This is a bug in Terraform and should be reported: %w", + c.Type, + c.Provider.Name, + c.ProviderAddr, + err)) + return nil, diags + } + emptyState := states.NewState() + if err := bStateMgr.WriteState(emptyState); err != nil { + diags = diags.Append(fmt.Errorf(errStateStoreWorkspaceCreate, c.Type, err)) + return nil, diags + } + if err := sMgr.PersistState(); err != nil { + diags = diags.Append(fmt.Errorf(errStateStoreWorkspaceCreate, c.Type, err)) + return nil, diags + } } + // TODO: handle if input is not enabled + // TODO: handle if non-default workspace is not used } else if err != nil { diags = diags.Append(err) } From 00b55a0cd52ee4e01efc33195ea8ad65cc7675cc Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 8 Aug 2025 12:19:11 +0100 Subject: [PATCH 22/60] Rename argument to backend and state store init methods to help distinguish between state managers --- internal/command/meta_backend.go | 58 +++++++++++++++++--------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/internal/command/meta_backend.go b/internal/command/meta_backend.go index b27303478dac..0057e2be8561 100644 --- a/internal/command/meta_backend.go +++ b/internal/command/meta_backend.go @@ -1097,7 +1097,7 @@ func (m *Meta) backendFromState(_ context.Context) (backend.Backend, tfdiags.Dia // Unconfiguring a backend (moving from backend => local). func (m *Meta) backend_c_r_S( - c *configs.Backend, cHash int, sMgr *clistate.LocalState, output bool, opts *BackendOpts) (backend.Backend, tfdiags.Diagnostics) { + c *configs.Backend, cHash int, backendSMgr *clistate.LocalState, output bool, opts *BackendOpts) (backend.Backend, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics @@ -1108,7 +1108,8 @@ func (m *Meta) backend_c_r_S( vt = arguments.ViewHuman } - s := sMgr.State() + // Get backend state file data + s := backendSMgr.State() cloudMode := cloud.DetectConfigChangeType(s.Backend, c, false) diags = diags.Append(m.assertSupportedCloudInitOptions(cloudMode)) @@ -1133,7 +1134,7 @@ func (m *Meta) backend_c_r_S( } // Initialize the configured backend - b, moreDiags := m.savedBackend(sMgr) + b, moreDiags := m.savedBackend(backendSMgr) diags = diags.Append(moreDiags) if moreDiags.HasErrors() { return nil, diags @@ -1154,11 +1155,11 @@ func (m *Meta) backend_c_r_S( // Remove the stored metadata s.Backend = nil - if err := sMgr.WriteState(s); err != nil { + if err := backendSMgr.WriteState(s); err != nil { diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendClearSaved), err)) return nil, diags } - if err := sMgr.PersistState(); err != nil { + if err := backendSMgr.PersistState(); err != nil { diags = diags.Append(fmt.Errorf(strings.TrimSpace(errBackendClearSaved), err)) return nil, diags } @@ -1174,7 +1175,7 @@ func (m *Meta) backend_c_r_S( } // Configuring a backend for the first time. -func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, sMgr *clistate.LocalState, opts *BackendOpts) (backend.Backend, tfdiags.Diagnostics) { +func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, backendSMgr *clistate.LocalState, opts *BackendOpts) (backend.Backend, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics vt := arguments.ViewJSON @@ -1278,15 +1279,15 @@ func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, sMgr *clistate.Local if m.stateLock { view := views.NewStateLocker(vt, m.View) stateLocker := clistate.NewLocker(m.stateLockTimeout, view) - if err := stateLocker.Lock(sMgr, "backend from plan"); err != nil { + if err := stateLocker.Lock(backendSMgr, "backend from plan"); err != nil { diags = diags.Append(fmt.Errorf("Error locking state: %s", err)) return nil, diags } defer stateLocker.Unlock() } - // Store the metadata in our saved state location - s := sMgr.State() + // Store the backend's metadata in our the backend state file location + s := backendSMgr.State() if s == nil { s = workdir.NewBackendStateFile() } @@ -1321,11 +1322,12 @@ func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, sMgr *clistate.Local } } - if err := sMgr.WriteState(s); err != nil { + // Update backend state file + if err := backendSMgr.WriteState(s); err != nil { diags = diags.Append(fmt.Errorf(errBackendWriteSaved, err)) return nil, diags } - if err := sMgr.PersistState(); err != nil { + if err := backendSMgr.PersistState(); err != nil { diags = diags.Append(fmt.Errorf(errBackendWriteSaved, err)) return nil, diags } @@ -1341,7 +1343,7 @@ func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, sMgr *clistate.Local } // Changing a previously saved backend. -func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, sMgr *clistate.LocalState, output bool, opts *BackendOpts) (backend.Backend, tfdiags.Diagnostics) { +func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, backendSMgr *clistate.LocalState, output bool, opts *BackendOpts) (backend.Backend, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics vt := arguments.ViewJSON @@ -1351,8 +1353,8 @@ func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, sMgr *clista vt = arguments.ViewHuman } - // Get the old state - s := sMgr.State() + // Get the old backend state file data + s := backendSMgr.State() cloudMode := cloud.DetectConfigChangeType(s.Backend, c, false) diags = diags.Append(m.assertSupportedCloudInitOptions(cloudMode)) @@ -1398,7 +1400,7 @@ func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, sMgr *clista // state lives. if cloudMode != cloud.ConfigChangeInPlace { // Grab the existing backend - oldB, oldBDiags := m.savedBackend(sMgr) + oldB, oldBDiags := m.savedBackend(backendSMgr) diags = diags.Append(oldBDiags) if oldBDiags.HasErrors() { return nil, diags @@ -1420,7 +1422,7 @@ func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, sMgr *clista if m.stateLock { view := views.NewStateLocker(vt, m.View) stateLocker := clistate.NewLocker(m.stateLockTimeout, view) - if err := stateLocker.Lock(sMgr, "backend from plan"); err != nil { + if err := stateLocker.Lock(backendSMgr, "backend from plan"); err != nil { diags = diags.Append(fmt.Errorf("Error locking state: %s", err)) return nil, diags } @@ -1429,7 +1431,7 @@ func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, sMgr *clista } // Update the backend state - s = sMgr.State() + s = backendSMgr.State() if s == nil { s = workdir.NewBackendStateFile() } @@ -1451,11 +1453,12 @@ func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, sMgr *clista } } - if err := sMgr.WriteState(s); err != nil { + // Save data to backend state file + if err := backendSMgr.WriteState(s); err != nil { diags = diags.Append(fmt.Errorf(errBackendWriteSaved, err)) return nil, diags } - if err := sMgr.PersistState(); err != nil { + if err := backendSMgr.PersistState(); err != nil { diags = diags.Append(fmt.Errorf(errBackendWriteSaved, err)) return nil, diags } @@ -1487,7 +1490,7 @@ func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, sMgr *clista //------------------------------------------------------------------- // Configuring a state_store for the first time. -func (m *Meta) stateStore_C_s(c *configs.StateStore, cHash int, sMgr *clistate.LocalState, opts *BackendOpts) (backend.Backend, tfdiags.Diagnostics) { +func (m *Meta) stateStore_C_s(c *configs.StateStore, cHash int, backendSMgr *clistate.LocalState, opts *BackendOpts) (backend.Backend, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics vt := arguments.ViewJSON @@ -1573,7 +1576,7 @@ func (m *Meta) stateStore_C_s(c *configs.StateStore, cHash int, sMgr *clistate.L if m.stateLock { view := views.NewStateLocker(vt, m.View) stateLocker := clistate.NewLocker(m.stateLockTimeout, view) - if err := stateLocker.Lock(sMgr, "state_store from plan"); err != nil { + if err := stateLocker.Lock(backendSMgr, "state_store from plan"); err != nil { diags = diags.Append(fmt.Errorf("Error locking state: %s", err)) return nil, diags } @@ -1581,7 +1584,7 @@ func (m *Meta) stateStore_C_s(c *configs.StateStore, cHash int, sMgr *clistate.L } // Store the state_store metadata in our saved state location - s := sMgr.State() + s := backendSMgr.State() if s == nil { s = workdir.NewBackendStateFile() } @@ -1678,7 +1681,7 @@ func (m *Meta) stateStore_C_s(c *configs.StateStore, cHash int, sMgr *clistate.L diags = diags.Append(fmt.Errorf(errStateStoreWorkspaceCreate, c.Type, err)) return nil, diags } - if err := sMgr.PersistState(); err != nil { + if err := backendSMgr.PersistState(); err != nil { diags = diags.Append(fmt.Errorf(errStateStoreWorkspaceCreate, c.Type, err)) return nil, diags } @@ -1690,11 +1693,12 @@ func (m *Meta) stateStore_C_s(c *configs.StateStore, cHash int, sMgr *clistate.L } } - if err := sMgr.WriteState(s); err != nil { + // Update backend state file + if err := backendSMgr.WriteState(s); err != nil { diags = diags.Append(fmt.Errorf(errBackendWriteSaved, err)) return nil, diags } - if err := sMgr.PersistState(); err != nil { + if err := backendSMgr.PersistState(); err != nil { diags = diags.Append(fmt.Errorf(errBackendWriteSaved, err)) return nil, diags } @@ -1707,10 +1711,10 @@ func (m *Meta) stateStore_C_s(c *configs.StateStore, cHash int, sMgr *clistate.L // TODO: This is extremely similar to Meta.backendFromState() but for legacy reasons this is the // function used by the migration APIs within this file. The other handles 'init -backend=false', // specifically. -func (m *Meta) savedBackend(sMgr *clistate.LocalState) (backend.Backend, tfdiags.Diagnostics) { +func (m *Meta) savedBackend(backendSMgr *clistate.LocalState) (backend.Backend, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics - s := sMgr.State() + s := backendSMgr.State() // Get the backend f := backendInit.Backend(s.Backend.Type) From d0bcab9bc068b2e0f8a84a5dbfd85cd3926bff26 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 8 Aug 2025 12:20:32 +0100 Subject: [PATCH 23/60] Fix: avoid updating backend state file if an unhandled error has occurred --- internal/command/meta_backend.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/internal/command/meta_backend.go b/internal/command/meta_backend.go index 0057e2be8561..6471c32e1e58 100644 --- a/internal/command/meta_backend.go +++ b/internal/command/meta_backend.go @@ -1687,10 +1687,9 @@ func (m *Meta) stateStore_C_s(c *configs.StateStore, cHash int, backendSMgr *cli } } // TODO: handle if input is not enabled - // TODO: handle if non-default workspace is not used - } else if err != nil { - diags = diags.Append(err) - } + } + if diags.HasErrors() { + return nil, diags } // Update backend state file From 16a402964663e6cc2cb3cfca867f615158e651ee Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 8 Aug 2025 12:42:39 +0100 Subject: [PATCH 24/60] Fix missing } --- internal/command/meta_backend.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/command/meta_backend.go b/internal/command/meta_backend.go index 6471c32e1e58..bcaa2d2ca047 100644 --- a/internal/command/meta_backend.go +++ b/internal/command/meta_backend.go @@ -1687,6 +1687,7 @@ func (m *Meta) stateStore_C_s(c *configs.StateStore, cHash int, backendSMgr *cli } } // TODO: handle if input is not enabled + } } if diags.HasErrors() { return nil, diags From e442714f2ca67881b5829e23c765dfbcc49caecd Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 8 Aug 2025 13:08:41 +0100 Subject: [PATCH 25/60] Add handling of non-default workspace, remove input prompting for now. --- internal/command/meta_backend.go | 56 ++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/internal/command/meta_backend.go b/internal/command/meta_backend.go index bcaa2d2ca047..2d9758ade478 100644 --- a/internal/command/meta_backend.go +++ b/internal/command/meta_backend.go @@ -1641,29 +1641,48 @@ func (m *Meta) stateStore_C_s(c *configs.StateStore, cHash int, backendSMgr *cli if opts.Init && b != nil { err := m.selectWorkspace(b) if strings.Contains(err.Error(), "No existing workspaces") { + // If there are no workspaces, Terraform either needs to create the default workspace here, + // or instruct the user to run a `terraform workspace new` command. ws, err := m.Workspace() if err != nil { diags = diags.Append(fmt.Errorf("Failed to check current workspace: %w", err)) return nil, diags } - if m.Input() && ws == backend.DefaultStateName { - input := m.UIInput() - desc := fmt.Sprintf("Terraform will create the %q workspace via %q.\n"+ - "Only 'yes' will be accepted to approve.", backend.DefaultStateName, c.Type) - v, err := input.Input(context.Background(), &terraform.InputOpts{ - Id: "approve", - Query: fmt.Sprintf("Workspace %q does not exit, would you like to create one?", backend.DefaultStateName), - Description: desc, - }) - if err != nil { - diags = diags.Append(fmt.Errorf("Failed to confirm default workspace creation: %w", err)) - return nil, diags - } - if v != "yes" { - diags = diags.Append(errors.New("Failed to create default workspace")) - return nil, diags - } + switch { + case ws != backend.DefaultStateName: + // User needs to run a `terraform workspace new` command. + diags = append(diags, tfdiags.Sourceless( + tfdiags.Error, + fmt.Sprintf("Workspace %q has not been created yet", ws), + fmt.Sprintf("State store %q in provider %s (%q) reports that no workspaces currently exist. To create the custom workspace %q use the command `terraform workspace new %s`.", + c.Type, + c.Provider.Name, + c.ProviderAddr, + ws, + ws, + ), + )) + return nil, diags + + case ws == backend.DefaultStateName: + // TODO: do we want to prompt for input here (m.Input()), or create automatically unless -readonly flag present? + // input := m.UIInput() + // desc := fmt.Sprintf("Terraform will create the %q workspace via %q.\n"+ + // "Only 'yes' will be accepted to approve.", backend.DefaultStateName, c.Type) + // v, err := input.Input(context.Background(), &terraform.InputOpts{ + // Id: "approve", + // Query: fmt.Sprintf("Workspace %q does not exit, would you like to create one?", backend.DefaultStateName), + // Description: desc, + // }) + // if err != nil { + // diags = diags.Append(fmt.Errorf("Failed to confirm default workspace creation: %w", err)) + // return nil, diags + // } + // if v != "yes" { + // diags = diags.Append(errors.New("Failed to create default workspace")) + // return nil, diags + // } // TODO: Confirm if defaulting to creation on first use (rather than error) is a good idea // Make the default workspace. All other workspaces are user-created via the workspace commands. @@ -1685,6 +1704,9 @@ func (m *Meta) stateStore_C_s(c *configs.StateStore, cHash int, backendSMgr *cli diags = diags.Append(fmt.Errorf(errStateStoreWorkspaceCreate, c.Type, err)) return nil, diags } + default: + diags = diags.Append(err) + return nil, diags } // TODO: handle if input is not enabled } From c9a8d491c79592ca434f1a6b5ae53b8c554238d2 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 8 Aug 2025 13:09:07 +0100 Subject: [PATCH 26/60] Rename state manager var, fix bug where PersistState was called on wrong state manager --- internal/command/meta_backend.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/command/meta_backend.go b/internal/command/meta_backend.go index 2d9758ade478..b05d525bb6b6 100644 --- a/internal/command/meta_backend.go +++ b/internal/command/meta_backend.go @@ -1686,7 +1686,7 @@ func (m *Meta) stateStore_C_s(c *configs.StateStore, cHash int, backendSMgr *cli // TODO: Confirm if defaulting to creation on first use (rather than error) is a good idea // Make the default workspace. All other workspaces are user-created via the workspace commands. - bStateMgr, err := b.StateMgr(backend.DefaultStateName) + defaultSMgr, err := b.StateMgr(backend.DefaultStateName) if err != nil { diags = diags.Append(fmt.Errorf("Failed to create a state manager for state store %q in provider %s (%q). This is a bug in Terraform and should be reported: %w", c.Type, @@ -1696,11 +1696,11 @@ func (m *Meta) stateStore_C_s(c *configs.StateStore, cHash int, backendSMgr *cli return nil, diags } emptyState := states.NewState() - if err := bStateMgr.WriteState(emptyState); err != nil { + if err := defaultSMgr.WriteState(emptyState); err != nil { diags = diags.Append(fmt.Errorf(errStateStoreWorkspaceCreate, c.Type, err)) return nil, diags } - if err := backendSMgr.PersistState(); err != nil { + if err := defaultSMgr.PersistState(nil); err != nil { diags = diags.Append(fmt.Errorf(errStateStoreWorkspaceCreate, c.Type, err)) return nil, diags } From 0835d26b40ab207c4096c9b3961c527b23c800cd Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 8 Aug 2025 13:09:37 +0100 Subject: [PATCH 27/60] Make TODO indicating blocker --- internal/command/meta_backend.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/command/meta_backend.go b/internal/command/meta_backend.go index b05d525bb6b6..a44ae63cf874 100644 --- a/internal/command/meta_backend.go +++ b/internal/command/meta_backend.go @@ -1700,6 +1700,7 @@ func (m *Meta) stateStore_C_s(c *configs.StateStore, cHash int, backendSMgr *cli diags = diags.Append(fmt.Errorf(errStateStoreWorkspaceCreate, c.Type, err)) return nil, diags } + // TODO - implement Read/Write state RPC methods if err := defaultSMgr.PersistState(nil); err != nil { diags = diags.Append(fmt.Errorf(errStateStoreWorkspaceCreate, c.Type, err)) return nil, diags From 374b48898dd9a28d1692693bdc537c7323a4fb97 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Tue, 12 Aug 2025 10:50:08 +0100 Subject: [PATCH 28/60] Add createDefaultWorkspace method --- internal/command/meta_backend.go | 49 +++++++++++++++++++------------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/internal/command/meta_backend.go b/internal/command/meta_backend.go index a44ae63cf874..952376f166d7 100644 --- a/internal/command/meta_backend.go +++ b/internal/command/meta_backend.go @@ -1686,25 +1686,7 @@ func (m *Meta) stateStore_C_s(c *configs.StateStore, cHash int, backendSMgr *cli // TODO: Confirm if defaulting to creation on first use (rather than error) is a good idea // Make the default workspace. All other workspaces are user-created via the workspace commands. - defaultSMgr, err := b.StateMgr(backend.DefaultStateName) - if err != nil { - diags = diags.Append(fmt.Errorf("Failed to create a state manager for state store %q in provider %s (%q). This is a bug in Terraform and should be reported: %w", - c.Type, - c.Provider.Name, - c.ProviderAddr, - err)) - return nil, diags - } - emptyState := states.NewState() - if err := defaultSMgr.WriteState(emptyState); err != nil { - diags = diags.Append(fmt.Errorf(errStateStoreWorkspaceCreate, c.Type, err)) - return nil, diags - } - // TODO - implement Read/Write state RPC methods - if err := defaultSMgr.PersistState(nil); err != nil { - diags = diags.Append(fmt.Errorf(errStateStoreWorkspaceCreate, c.Type, err)) - return nil, diags - } + m.createDefaultWorkspace(c, b) default: diags = diags.Append(err) return nil, diags @@ -1729,6 +1711,35 @@ func (m *Meta) stateStore_C_s(c *configs.StateStore, cHash int, backendSMgr *cli return b, diags } +// createDefaultWorkspace receives a backend made using a pluggable state store, and details about that store's config, +// and persists an empty state file in the default workspace. By creating this artifact we ensure that the default +// workspace is created and usable by Terraform in later operations. +func (m *Meta) createDefaultWorkspace(c *configs.StateStore, b backend.Backend) tfdiags.Diagnostics { + var diags tfdiags.Diagnostics + + defaultSMgr, err := b.StateMgr(backend.DefaultStateName) + if err != nil { + diags = diags.Append(fmt.Errorf("Failed to create a state manager for state store %q in provider %s (%q). This is a bug in Terraform and should be reported: %w", + c.Type, + c.Provider.Name, + c.ProviderAddr, + err)) + return diags + } + emptyState := states.NewState() + if err := defaultSMgr.WriteState(emptyState); err != nil { + diags = diags.Append(fmt.Errorf(errStateStoreWorkspaceCreate, c.Type, err)) + return diags + } + // TODO - implement Read/Write state RPC methods + if err := defaultSMgr.PersistState(nil); err != nil { + diags = diags.Append(fmt.Errorf(errStateStoreWorkspaceCreate, c.Type, err)) + return diags + } + + return diags +} + // Initializing a saved backend from the cache file (legacy state file) // // TODO: This is extremely similar to Meta.backendFromState() but for legacy reasons this is the From 52aaa5c0dbb40c57ae845d112b3a9675ef811f72 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Tue, 12 Aug 2025 11:31:30 +0100 Subject: [PATCH 29/60] Add locks to test --- internal/command/meta_backend_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/internal/command/meta_backend_test.go b/internal/command/meta_backend_test.go index ff295d6aebf5..decc92447f80 100644 --- a/internal/command/meta_backend_test.go +++ b/internal/command/meta_backend_test.go @@ -2299,11 +2299,27 @@ func TestMetaBackend_reconfigureStateStoreChange(t *testing.T) { return mock, nil } + // Create locks - these would normally be the locks derived from config + locks := depsfile.NewLocks() + constraint, err := providerreqs.ParseVersionConstraints(">9.0.0") + if err != nil { + t.Fatalf("test setup failed when making constraint: %s", err) + } + expectedVersionString := "9.9.9" + expectedProviderSource := "registry.terraform.io/my-org/foo" + locks.SetProvider( + addrs.MustParseProviderSourceString(expectedProviderSource), + versions.MustParseVersion(expectedVersionString), + constraint, + []providerreqs.Hash{"h1:foo"}, + ) + // Get the operations backend _, beDiags := m.Backend(&BackendOpts{ Init: true, StateStoreConfig: mod.StateStore, ProviderFactory: factory, + Locks: locks, }) if !beDiags.HasErrors() { From 3c8814234c50f0be9d0e105daf1f79a83cee9437 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 15 Aug 2025 15:07:02 +0100 Subject: [PATCH 30/60] Add `-create-default-workspace` flag to init command, for overriding behaviour when TF isn't in interactive mode This required refactoring the signature of the initBackend method; pass in all init args instead of a subset --- internal/command/arguments/init.go | 14 +++++- internal/command/init.go | 21 +++++---- internal/command/init_run.go | 2 +- internal/command/init_run_experiment.go | 2 +- internal/command/meta_backend.go | 59 ++++++++++++++++--------- 5 files changed, 65 insertions(+), 33 deletions(-) diff --git a/internal/command/arguments/init.go b/internal/command/arguments/init.go index 6bb74473847a..91b2846b7456 100644 --- a/internal/command/arguments/init.go +++ b/internal/command/arguments/init.go @@ -78,6 +78,10 @@ type Init struct { // TODO(SarahFrench/radeksimko): Remove this once the feature is no longer // experimental EnablePssExperiment bool + + // CreateDefaultWorkspace indicates whether the default workspace should be created by + // Terraform when initializing a state store for the first time. + CreateDefaultWorkspace bool } // ParseInit processes CLI arguments, returning an Init value and errors. @@ -111,7 +115,7 @@ func ParseInit(args []string) (*Init, tfdiags.Diagnostics) { cmdFlags.BoolVar(&init.Json, "json", false, "json") cmdFlags.Var(&init.BackendConfig, "backend-config", "") cmdFlags.Var(&init.PluginPath, "plugin-dir", "plugin directory") - + cmdFlags.BoolVar(&init.CreateDefaultWorkspace, "create-default-workspace", true, "when -input=false, use this flag to block creation of the default workspace") // Used for enabling experimental code that's invoked before configuration is parsed. cmdFlags.BoolVar(&init.EnablePssExperiment, "enable-pluggable-state-storage-experiment", false, "Enable the pluggable state storage experiment") @@ -139,6 +143,14 @@ func ParseInit(args []string) (*Init, tfdiags.Diagnostics) { )) } + if init.InputEnabled && !init.CreateDefaultWorkspace { + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Warning, + "Invalid init options", + "The flag -create-default-workspace=false is ignored when Terraform is configured to ask users for input. Instead, add -input=false or remove the -create-default-workspace flag", + )) + } + init.Args = cmdFlags.Args() backendFlagSet := FlagIsSet(cmdFlags, "backend") diff --git a/internal/command/init.go b/internal/command/init.go index e27d3c2e2013..1c7128bbff8e 100644 --- a/internal/command/init.go +++ b/internal/command/init.go @@ -51,7 +51,7 @@ func (c *InitCommand) Run(args []string) int { args = c.Meta.process(args) initArgs, initDiags := arguments.ParseInit(args) - view := views.NewInit(initArgs.ViewType, c.View) + view := views.NewInit(viewType, c.View) if initDiags.HasErrors() { diags = diags.Append(initDiags) @@ -159,7 +159,11 @@ func (c *InitCommand) initCloud(ctx context.Context, root *configs.Module, extra return back, true, diags } -func (c *InitCommand) initBackend(ctx context.Context, root *configs.Module, extraConfig arguments.FlagNameValueSlice, viewType arguments.ViewType, locks *depsfile.Locks, view views.Init) (be backend.Backend, output bool, diags tfdiags.Diagnostics) { +func (c *InitCommand) initBackend(ctx context.Context, root *configs.Module, initArgs *arguments.Init, locks *depsfile.Locks, view views.Init) (be backend.Backend, output bool, diags tfdiags.Diagnostics) { + // Temporary vars to account for refactoring the method's parameters + extraConfig := initArgs.BackendConfig + viewType := initArgs.ViewType + ctx, span := tracer.Start(ctx, "initialize backend") _ = ctx // prevent staticcheck from complaining to avoid a maintenance hazard of having the wrong ctx in scope here defer span.End() @@ -271,12 +275,13 @@ func (c *InitCommand) initBackend(ctx context.Context, root *configs.Module, ext } opts = &BackendOpts{ - StateStoreConfig: root.StateStore, - Locks: locks, - ProviderFactory: factory, - ConfigOverride: configOverride, - Init: true, - ViewType: viewType, + StateStoreConfig: root.StateStore, + Locks: locks, + ProviderFactory: factory, + CreateDefaultWorkspace: initArgs.CreateDefaultWorkspace, + ConfigOverride: configOverride, + Init: true, + ViewType: viewType, } case root.Backend != nil: diff --git a/internal/command/init_run.go b/internal/command/init_run.go index bdb637522212..50ddd5f5749d 100644 --- a/internal/command/init_run.go +++ b/internal/command/init_run.go @@ -172,7 +172,7 @@ func (c *InitCommand) run(initArgs *arguments.Init, view views.Init) int { back, backendOutput, backDiags = c.initCloud(ctx, rootModEarly, initArgs.BackendConfig, initArgs.ViewType, view) case initArgs.Backend: var locks *depsfile.Locks // Empty locks- this value is unused when a `backend` is used (vs. a `state_store`) - back, backendOutput, backDiags = c.initBackend(ctx, rootModEarly, initArgs.BackendConfig, initArgs.ViewType, locks, view) + back, backendOutput, backDiags = c.initBackend(ctx, rootModEarly, initArgs, locks, view) default: // load the previously-stored backend config back, backDiags = c.Meta.backendFromState(ctx) diff --git a/internal/command/init_run_experiment.go b/internal/command/init_run_experiment.go index 25fe349db5a8..e4773d93b91f 100644 --- a/internal/command/init_run_experiment.go +++ b/internal/command/init_run_experiment.go @@ -208,7 +208,7 @@ func (c *InitCommand) runPssInit(initArgs *arguments.Init, view views.Init) int // This handles case when config contains either backend or state_store blocks. // This is valid as either can be implementations of backend.Backend, which is what we // obtain here. - back, backendOutput, backDiags = c.initBackend(ctx, rootModEarly, initArgs.BackendConfig, initArgs.ViewType, configLocks, view) + back, backendOutput, backDiags = c.initBackend(ctx, rootModEarly, initArgs, configLocks, view) default: // load the previously-stored backend config back, backDiags = c.Meta.backendFromState(ctx) diff --git a/internal/command/meta_backend.go b/internal/command/meta_backend.go index 952376f166d7..9f0aa8c96831 100644 --- a/internal/command/meta_backend.go +++ b/internal/command/meta_backend.go @@ -89,6 +89,10 @@ type BackendOpts struct { // ViewType will set console output format for the // initialization operation (JSON or human-readable). ViewType arguments.ViewType + + // CreateDefaultWorkspace signifies whether the operations backend should create + // the default workspace or not + CreateDefaultWorkspace bool } // BackendWithRemoteTerraformVersion is a shared interface between the 'remote' and 'cloud' backends @@ -1666,32 +1670,43 @@ func (m *Meta) stateStore_C_s(c *configs.StateStore, cHash int, backendSMgr *cli return nil, diags case ws == backend.DefaultStateName: - // TODO: do we want to prompt for input here (m.Input()), or create automatically unless -readonly flag present? - // input := m.UIInput() - // desc := fmt.Sprintf("Terraform will create the %q workspace via %q.\n"+ - // "Only 'yes' will be accepted to approve.", backend.DefaultStateName, c.Type) - // v, err := input.Input(context.Background(), &terraform.InputOpts{ - // Id: "approve", - // Query: fmt.Sprintf("Workspace %q does not exit, would you like to create one?", backend.DefaultStateName), - // Description: desc, - // }) - // if err != nil { - // diags = diags.Append(fmt.Errorf("Failed to confirm default workspace creation: %w", err)) - // return nil, diags - // } - // if v != "yes" { - // diags = diags.Append(errors.New("Failed to create default workspace")) - // return nil, diags - // } - - // TODO: Confirm if defaulting to creation on first use (rather than error) is a good idea - // Make the default workspace. All other workspaces are user-created via the workspace commands. - m.createDefaultWorkspace(c, b) + // Should we create the default state after prompting the user, or not? + if m.Input() { + // If input is enabled, we prompt the user before creating the default workspace. + input := m.UIInput() + desc := fmt.Sprintf("Terraform will create the %q workspace via state store %q.\n"+ + "Only 'yes' will be accepted to approve.", backend.DefaultStateName, c.Type) + v, err := input.Input(context.Background(), &terraform.InputOpts{ + Id: "approve", + Query: fmt.Sprintf("Workspace the %s workspace does not exit, would you like to create it?", backend.DefaultStateName), + Description: desc, + }) + if err != nil { + diags = diags.Append(fmt.Errorf("Failed to confirm %s workspace creation: %w", backend.DefaultStateName, err)) + return nil, diags + } + if v != "yes" { + diags = diags.Append(fmt.Errorf("Cancelled creation of the %s workspace", backend.DefaultStateName)) + return nil, diags + } + m.createDefaultWorkspace(c, b) + } else { + // If input is disabled, we don't prompt before creating the default workspace. + // However this can be blocked with other flags present. + if opts.CreateDefaultWorkspace { + m.createDefaultWorkspace(c, b) + } else { + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagWarning, + Summary: "The default workspace does not exist", + Detail: "Terraform has been configured to skip creation of the default workspace in the state store. This may cause issues in subsequent Terraform operations", + }) + } + } default: diags = diags.Append(err) return nil, diags } - // TODO: handle if input is not enabled } } if diags.HasErrors() { From 7d07c772b7a473d57e85fefe82ff4a05b2a591c9 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 15 Aug 2025 15:24:07 +0100 Subject: [PATCH 31/60] WIP - updating tests, currently blocked --- internal/command/meta_backend_test.go | 75 +++++++++++++++++---- internal/providers/testing/provider_mock.go | 23 +++++-- 2 files changed, 79 insertions(+), 19 deletions(-) diff --git a/internal/command/meta_backend_test.go b/internal/command/meta_backend_test.go index decc92447f80..6709ba9e3beb 100644 --- a/internal/command/meta_backend_test.go +++ b/internal/command/meta_backend_test.go @@ -2073,18 +2073,41 @@ func Test_determineInitReason(t *testing.T) { // Newly configured state store // Working directory has state_store in config but no preexisting backend state file func TestMetaBackend_configureNewStateStore(t *testing.T) { - cases := map[string]struct { + // setup isInitCommand bool - expectedError string + + inputEnabled bool + inputText string + + createDefaultWorkspace bool + // assertions + expectedError string + expectDefaultWorkspaceExists bool }{ - "during an init command, the working directory is initialized and a backend state file is created": { - isInitCommand: true, + "an init command prompts users for input when the default workspace needs to be created": { + inputEnabled: true, + createDefaultWorkspace: true, + inputText: "yes", + isInitCommand: true, + expectDefaultWorkspaceExists: true, + }, + "an init command with input disabled will create the default workspace automatically": { + inputEnabled: false, + createDefaultWorkspace: true, + isInitCommand: true, + expectDefaultWorkspaceExists: true, }, - "during a non-init command, the command ends in with an error telling the user to run an init command": { - isInitCommand: false, - expectedError: "State store initialization required, please run \"terraform init\": Reason: Initial configuration of the requested state_store \"foo_bar\" in provider foo (\"registry.terraform.io/my-org/foo\")", + "an init command with input disabled and the flag -create-default-workspace=false will not make the default workspace": { + inputEnabled: false, + createDefaultWorkspace: false, + isInitCommand: true, + expectDefaultWorkspaceExists: false, }, + // "during a non-init command, the command ends in with an error telling the user to run an init command": { + // isInitCommand: false, + // expectedError: "State store initialization required, please run \"terraform init\": Reason: Initial configuration of the requested state_store \"foo_bar\" in provider foo (\"registry.terraform.io/my-org/foo\")", + // }, } for tn, tc := range cases { @@ -2096,6 +2119,10 @@ func TestMetaBackend_configureNewStateStore(t *testing.T) { // Setup the meta m := testMetaBackend(t, nil) m.AllowExperimentalFeatures = true + m.input = tc.inputEnabled + if tc.inputEnabled { + defer testInteractiveInput(t, []string{tc.inputText})() + } // Get the state store's config mod, loadDiags := m.loadSingleModule(td) @@ -2133,6 +2160,8 @@ func TestMetaBackend_configureNewStateStore(t *testing.T) { }, }, }, + // TODO: Add logic that makes the call to WriteState create a workspace within + // the mock } factory := func() (providers.Interface, error) { return mock, nil @@ -2154,15 +2183,16 @@ func TestMetaBackend_configureNewStateStore(t *testing.T) { ) // Act - get the operations backend - _, beDiags := m.Backend(&BackendOpts{ - Init: tc.isInitCommand, // Changes with test case - StateStoreConfig: mod.StateStore, - ProviderFactory: factory, - Locks: locks, + b, beDiags := m.Backend(&BackendOpts{ + Init: tc.isInitCommand, // Changes with test case + StateStoreConfig: mod.StateStore, + ProviderFactory: factory, + Locks: locks, + CreateDefaultWorkspace: tc.createDefaultWorkspace, }) if beDiags.HasErrors() { if tc.expectedError == "" { - t.Fatalf("unexpected error: %s", err) + t.Fatalf("unexpected error: %s", beDiags.Err()) } if !strings.Contains(cleanString(beDiags.Err().Error()), tc.expectedError) { t.Fatalf("expected error to contain %s, but instead got: %s", tc.expectedError, cleanString(beDiags.Err().Error())) @@ -2196,6 +2226,25 @@ func TestMetaBackend_configureNewStateStore(t *testing.T) { if cleanString(string(s.StateStore.ConfigRaw)) != expectedStoreConfig { t.Fatalf("backend state file contains unexpected raw config data for the state store, want %q, got %q", expectedStoreConfig, cleanString(string(s.StateStore.ConfigRaw))) } + + w, err := b.Workspaces() + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if len(w) == 0 { + if tc.expectDefaultWorkspaceExists { + t.Fatal("expected the default workspace to exist, but there are no workspaces") + } + return + } + if len(w) > 0 { + if tc.expectDefaultWorkspaceExists { + if len(w) == 1 && w[0] != "default" { + t.Fatalf("expected the default workspace to exist, but instead got: %v", w) + } + } + t.Fatalf("expected the default workspace to be the only existing workspace, but instead got: %v", w) + } }) } } diff --git a/internal/providers/testing/provider_mock.go b/internal/providers/testing/provider_mock.go index c54760ee14d2..bc0b50017abe 100644 --- a/internal/providers/testing/provider_mock.go +++ b/internal/providers/testing/provider_mock.go @@ -5,12 +5,15 @@ package testing import ( "fmt" + "maps" + "slices" "sync" "github.com/zclconf/go-cty/cty" ctyjson "github.com/zclconf/go-cty/cty/json" "github.com/zclconf/go-cty/cty/msgpack" + "github.com/hashicorp/hcl/v2" "github.com/hashicorp/terraform/internal/configs/hcl2shim" "github.com/hashicorp/terraform/internal/providers" ) @@ -151,6 +154,9 @@ type MockProvider struct { WriteStateBytesFn func(providers.WriteStateBytesRequest) providers.WriteStateBytesResponse WriteStateBytesResponse providers.WriteStateBytesResponse + // states is an internal field that tracks which workspaces have been created in a test + states map[string]interface{} + GetStatesCalled bool GetStatesResponse *providers.GetStatesResponse GetStatesRequest providers.GetStatesRequest @@ -1013,11 +1019,8 @@ func (p *MockProvider) GetStates(r providers.GetStatesRequest) (resp providers.G return p.GetStatesFn(r) } - // If the mock has no further inputs, return an empty list. - // The state store should be reporting a minimum of the default workspace usually, - // but this should be achieved by querying data storage and identifying the artifact - // for that workspace, and reporting that the workspace exists. - resp.States = []string{} + // If the mock has no further inputs, return the internal states list + resp.States = slices.Sorted(maps.Keys(p.states)) return resp } @@ -1044,7 +1047,15 @@ func (p *MockProvider) DeleteState(r providers.DeleteStateRequest) (resp provide return p.DeleteStateFn(r) } - // There's no logic we can include here in the absence of other fields on the mock. + if _, match := p.states[r.StateId]; match { + delete(p.states, r.StateId) + } else { + resp.Diagnostics.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Workspace cannot be deleted", + Detail: fmt.Sprintf("The workspace %q does not exist, so cannot be deleted", r.StateId), + }) + } // If the response contains no diagnostics then the deletion is assumed to be successful. return resp From 66395f29f53a293f66c1303abbd52ee201aa8418 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Mon, 18 Aug 2025 12:05:02 +0100 Subject: [PATCH 32/60] Fix problem from refactoring --- internal/command/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/command/init.go b/internal/command/init.go index 1c7128bbff8e..de3133b6e96d 100644 --- a/internal/command/init.go +++ b/internal/command/init.go @@ -51,7 +51,7 @@ func (c *InitCommand) Run(args []string) int { args = c.Meta.process(args) initArgs, initDiags := arguments.ParseInit(args) - view := views.NewInit(viewType, c.View) + view := views.NewInit(initArgs.ViewType, c.View) if initDiags.HasErrors() { diags = diags.Append(initDiags) From e7aa81d9edce17b60d03c514dad3c59af8e8b509 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Mon, 18 Aug 2025 12:37:07 +0100 Subject: [PATCH 33/60] Update tests with new flag --- internal/command/arguments/init_test.go | 31 ++++++++++++++----------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/internal/command/arguments/init_test.go b/internal/command/arguments/init_test.go index 93e13b7b6281..2c78e97254fb 100644 --- a/internal/command/arguments/init_test.go +++ b/internal/command/arguments/init_test.go @@ -40,10 +40,11 @@ func TestParseInit_basicValid(t *testing.T) { FlagName: "-backend-config", Items: &flagNameValue, }, - Vars: &Vars{}, - InputEnabled: true, - CompactWarnings: false, - TargetFlags: nil, + Vars: &Vars{}, + InputEnabled: true, + CompactWarnings: false, + TargetFlags: nil, + CreateDefaultWorkspace: true, }, }, "setting multiple options": { @@ -72,11 +73,12 @@ func TestParseInit_basicValid(t *testing.T) { FlagName: "-backend-config", Items: &flagNameValue, }, - Vars: &Vars{}, - InputEnabled: true, - Args: []string{}, - CompactWarnings: true, - TargetFlags: nil, + Vars: &Vars{}, + InputEnabled: true, + Args: []string{}, + CompactWarnings: true, + TargetFlags: nil, + CreateDefaultWorkspace: true, }, }, "with cloud option": { @@ -101,11 +103,12 @@ func TestParseInit_basicValid(t *testing.T) { FlagName: "-backend-config", Items: &[]FlagNameValue{{Name: "-backend-config", Value: "backend.config"}}, }, - Vars: &Vars{}, - InputEnabled: false, - Args: []string{}, - CompactWarnings: false, - TargetFlags: []string{"foo_bar.baz"}, + Vars: &Vars{}, + InputEnabled: false, + Args: []string{}, + CompactWarnings: false, + TargetFlags: []string{"foo_bar.baz"}, + CreateDefaultWorkspace: true, }, }, } From 352aeeb3c05f454cd6f309d8c1fe093845952605 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Mon, 18 Aug 2025 12:37:39 +0100 Subject: [PATCH 34/60] Pivot to creating the default workspace without prompts This change was following discussions with Product --- internal/command/meta_backend.go | 37 +++++---------------------- internal/command/meta_backend_test.go | 31 +++++----------------- 2 files changed, 14 insertions(+), 54 deletions(-) diff --git a/internal/command/meta_backend.go b/internal/command/meta_backend.go index 9f0aa8c96831..e84867cef3c8 100644 --- a/internal/command/meta_backend.go +++ b/internal/command/meta_backend.go @@ -1670,38 +1670,15 @@ func (m *Meta) stateStore_C_s(c *configs.StateStore, cHash int, backendSMgr *cli return nil, diags case ws == backend.DefaultStateName: - // Should we create the default state after prompting the user, or not? - if m.Input() { - // If input is enabled, we prompt the user before creating the default workspace. - input := m.UIInput() - desc := fmt.Sprintf("Terraform will create the %q workspace via state store %q.\n"+ - "Only 'yes' will be accepted to approve.", backend.DefaultStateName, c.Type) - v, err := input.Input(context.Background(), &terraform.InputOpts{ - Id: "approve", - Query: fmt.Sprintf("Workspace the %s workspace does not exit, would you like to create it?", backend.DefaultStateName), - Description: desc, - }) - if err != nil { - diags = diags.Append(fmt.Errorf("Failed to confirm %s workspace creation: %w", backend.DefaultStateName, err)) - return nil, diags - } - if v != "yes" { - diags = diags.Append(fmt.Errorf("Cancelled creation of the %s workspace", backend.DefaultStateName)) - return nil, diags - } + // Users control if the default workspace is created through the -create-default-workspace flag (defaults to true) + if opts.CreateDefaultWorkspace { m.createDefaultWorkspace(c, b) } else { - // If input is disabled, we don't prompt before creating the default workspace. - // However this can be blocked with other flags present. - if opts.CreateDefaultWorkspace { - m.createDefaultWorkspace(c, b) - } else { - diags = diags.Append(&hcl.Diagnostic{ - Severity: hcl.DiagWarning, - Summary: "The default workspace does not exist", - Detail: "Terraform has been configured to skip creation of the default workspace in the state store. This may cause issues in subsequent Terraform operations", - }) - } + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagWarning, + Summary: "The default workspace does not exist", + Detail: "Terraform has been configured to skip creation of the default workspace in the state store.", + }) } default: diags = diags.Append(err) diff --git a/internal/command/meta_backend_test.go b/internal/command/meta_backend_test.go index 6709ba9e3beb..eae1111ce092 100644 --- a/internal/command/meta_backend_test.go +++ b/internal/command/meta_backend_test.go @@ -2075,39 +2075,26 @@ func Test_determineInitReason(t *testing.T) { func TestMetaBackend_configureNewStateStore(t *testing.T) { cases := map[string]struct { // setup - isInitCommand bool - - inputEnabled bool - inputText string - + isInitCommand bool createDefaultWorkspace bool // assertions expectedError string expectDefaultWorkspaceExists bool }{ - "an init command prompts users for input when the default workspace needs to be created": { - inputEnabled: true, - createDefaultWorkspace: true, - inputText: "yes", - isInitCommand: true, - expectDefaultWorkspaceExists: true, - }, - "an init command with input disabled will create the default workspace automatically": { - inputEnabled: false, + "an init command creates the default workspace by default": { createDefaultWorkspace: true, isInitCommand: true, expectDefaultWorkspaceExists: true, }, - "an init command with input disabled and the flag -create-default-workspace=false will not make the default workspace": { - inputEnabled: false, + "an init command with the flag -create-default-workspace=false will not make the default workspace": { createDefaultWorkspace: false, isInitCommand: true, expectDefaultWorkspaceExists: false, }, - // "during a non-init command, the command ends in with an error telling the user to run an init command": { - // isInitCommand: false, - // expectedError: "State store initialization required, please run \"terraform init\": Reason: Initial configuration of the requested state_store \"foo_bar\" in provider foo (\"registry.terraform.io/my-org/foo\")", - // }, + "during a non-init command, the command ends in with an error telling the user to run an init command": { + isInitCommand: false, + expectedError: "State store initialization required, please run \"terraform init\": Reason: Initial configuration of the requested state_store \"foo_bar\" in provider foo (\"registry.terraform.io/my-org/foo\")", + }, } for tn, tc := range cases { @@ -2119,10 +2106,6 @@ func TestMetaBackend_configureNewStateStore(t *testing.T) { // Setup the meta m := testMetaBackend(t, nil) m.AllowExperimentalFeatures = true - m.input = tc.inputEnabled - if tc.inputEnabled { - defer testInteractiveInput(t, []string{tc.inputText})() - } // Get the state store's config mod, loadDiags := m.loadSingleModule(td) From 44d5dbad11b9fb54214355aeecb5b3aae9923544 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Tue, 26 Aug 2025 16:45:58 +0100 Subject: [PATCH 35/60] Fix: Make sure diags from attempting to create the default workspace are returned --- internal/command/meta_backend.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/command/meta_backend.go b/internal/command/meta_backend.go index e84867cef3c8..51ad25c03183 100644 --- a/internal/command/meta_backend.go +++ b/internal/command/meta_backend.go @@ -1672,7 +1672,7 @@ func (m *Meta) stateStore_C_s(c *configs.StateStore, cHash int, backendSMgr *cli case ws == backend.DefaultStateName: // Users control if the default workspace is created through the -create-default-workspace flag (defaults to true) if opts.CreateDefaultWorkspace { - m.createDefaultWorkspace(c, b) + diags = diags.Append(m.createDefaultWorkspace(c, b)) } else { diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagWarning, From eebc38c515a3207562ebf9316a2a3b7fb39e2d6f Mon Sep 17 00:00:00 2001 From: Sarah French Date: Tue, 26 Aug 2025 16:46:58 +0100 Subject: [PATCH 36/60] Add rough implementation of Get and Put methods on remote gRPC state client --- internal/states/remote/remote_grpc.go | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/internal/states/remote/remote_grpc.go b/internal/states/remote/remote_grpc.go index 996cda988735..5d762f8ba53b 100644 --- a/internal/states/remote/remote_grpc.go +++ b/internal/states/remote/remote_grpc.go @@ -53,7 +53,23 @@ type grpcClient struct { // // Implementation of remote.Client func (g *grpcClient) Get() (*Payload, error) { - panic("not implemented yet") + // TODO - replace with method implementation added to main branch + req := providers.ReadStateBytesRequest{ + TypeName: g.typeName, + StateId: g.stateId, + } + resp := g.provider.ReadStateBytes(req) + + if len(resp.Bytes) == 0 { + // No state to return + return nil, resp.Diagnostics.Err() + } + + payload := &Payload{ + Data: resp.Bytes, + MD5: []byte("foobar"), + } + return payload, resp.Diagnostics.Err() } // Put invokes the WriteStateBytes gRPC method in the plugin protocol @@ -61,7 +77,14 @@ func (g *grpcClient) Get() (*Payload, error) { // // Implementation of remote.Client func (g *grpcClient) Put(state []byte) error { - panic("not implemented yet") + // TODO - replace with method implementation added to main branch + req := providers.WriteStateBytesRequest{ + TypeName: g.typeName, + StateId: g.stateId, + } + resp := g.provider.WriteStateBytes(req) + + return resp.Diagnostics.Err() } // Delete invokes the DeleteState gRPC method in the plugin protocol From b84fd8ea3e1a472c17321cefe185c4d12026cb47 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Tue, 26 Aug 2025 16:47:26 +0100 Subject: [PATCH 37/60] Update tests and mock; this is unblocked now Get and Put are implemented --- internal/command/meta_backend_test.go | 36 ++++++++++++++++++--- internal/providers/testing/provider_mock.go | 11 ++++--- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/internal/command/meta_backend_test.go b/internal/command/meta_backend_test.go index eae1111ce092..a6b32ee72173 100644 --- a/internal/command/meta_backend_test.go +++ b/internal/command/meta_backend_test.go @@ -2143,8 +2143,32 @@ func TestMetaBackend_configureNewStateStore(t *testing.T) { }, }, }, - // TODO: Add logic that makes the call to WriteState create a workspace within - // the mock + } + mock.WriteStateBytesFn = func(req providers.WriteStateBytesRequest) providers.WriteStateBytesResponse { + // Workspaces exist once the artefact representing it is written + if _, exist := mock.MockStates[req.StateId]; !exist { + // Ensure non-nil map + if mock.MockStates == nil { + mock.MockStates = make(map[string]interface{}) + } + + mock.MockStates[req.StateId] = req.Bytes + } + return providers.WriteStateBytesResponse{ + Diagnostics: nil, // success + } + } + mock.ReadStateBytesFn = func(req providers.ReadStateBytesRequest) providers.ReadStateBytesResponse { + state := []byte{} + if v, exist := mock.MockStates[req.StateId]; exist { + if s, ok := v.([]byte); ok { + state = s + } + } + return providers.ReadStateBytesResponse{ + Bytes: state, + Diagnostics: nil, // success + } } factory := func() (providers.Interface, error) { return mock, nil @@ -2222,11 +2246,13 @@ func TestMetaBackend_configureNewStateStore(t *testing.T) { } if len(w) > 0 { if tc.expectDefaultWorkspaceExists { - if len(w) == 1 && w[0] != "default" { - t.Fatalf("expected the default workspace to exist, but instead got: %v", w) + if len(w) != 1 || w[0] != "default" { + t.Fatalf("expected only the default workspace to exist, but instead got: %v", w) } + return // we've got the expected default workspace } - t.Fatalf("expected the default workspace to be the only existing workspace, but instead got: %v", w) + + t.Fatalf("got unexpected workspaces: %v", w) } }) } diff --git a/internal/providers/testing/provider_mock.go b/internal/providers/testing/provider_mock.go index bc0b50017abe..760ca1c3bd14 100644 --- a/internal/providers/testing/provider_mock.go +++ b/internal/providers/testing/provider_mock.go @@ -154,8 +154,9 @@ type MockProvider struct { WriteStateBytesFn func(providers.WriteStateBytesRequest) providers.WriteStateBytesResponse WriteStateBytesResponse providers.WriteStateBytesResponse - // states is an internal field that tracks which workspaces have been created in a test - states map[string]interface{} + // MockStates is an internal field that tracks which workspaces have been created in a test + // The map keys are state ids (workspaces) and the value depends on the test. + MockStates map[string]interface{} GetStatesCalled bool GetStatesResponse *providers.GetStatesResponse @@ -1020,7 +1021,7 @@ func (p *MockProvider) GetStates(r providers.GetStatesRequest) (resp providers.G } // If the mock has no further inputs, return the internal states list - resp.States = slices.Sorted(maps.Keys(p.states)) + resp.States = slices.Sorted(maps.Keys(p.MockStates)) return resp } @@ -1047,8 +1048,8 @@ func (p *MockProvider) DeleteState(r providers.DeleteStateRequest) (resp provide return p.DeleteStateFn(r) } - if _, match := p.states[r.StateId]; match { - delete(p.states, r.StateId) + if _, match := p.MockStates[r.StateId]; match { + delete(p.MockStates, r.StateId) } else { resp.Diagnostics.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, From 39c209c0d6fb42621c53e091d1ab6984f2ca4983 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 29 Aug 2025 11:47:42 +0100 Subject: [PATCH 38/60] Move init command test cases up to the 'command testing' level. This is currently only possible for testing init commands interacting with the PSS command --- internal/command/init_test.go | 168 +++++++++++ internal/command/meta_backend_test.go | 262 +++++++----------- .../testdata/init-with-state-store/main.tf | 8 +- 3 files changed, 265 insertions(+), 173 deletions(-) diff --git a/internal/command/init_test.go b/internal/command/init_test.go index 9de0c76f8e43..26bf04b13568 100644 --- a/internal/command/init_test.go +++ b/internal/command/init_test.go @@ -20,6 +20,7 @@ import ( "github.com/zclconf/go-cty/cty" "github.com/hashicorp/terraform/internal/addrs" + "github.com/hashicorp/terraform/internal/backend" "github.com/hashicorp/terraform/internal/command/arguments" "github.com/hashicorp/terraform/internal/command/views" "github.com/hashicorp/terraform/internal/configs" @@ -3257,6 +3258,111 @@ func TestInit_stateStoreBlockIsExperimental(t *testing.T) { } } +// Testing init's behaviors when run in an empty working directory +func TestInit_stateStore_newWorkingDir(t *testing.T) { + t.Run("an init command creates the default workspace by default", func(t *testing.T) { + // Create a temporary, uninitialized working directory with configuration including a state store + td := t.TempDir() + testCopyDir(t, testFixturePath("init-with-state-store"), td) + t.Chdir(td) + + mockProvider := mockPluggableStateStorageProvider() + mockProviderAddress := addrs.NewDefaultProvider("test") + providerSource, close := newMockProviderSource(t, map[string][]string{ + "hashicorp/test": {"1.0.0"}, + }) + defer close() + + ui := new(cli.MockUi) + view, done := testView(t) + c := &InitCommand{ + Meta: Meta{ + Ui: ui, + View: view, + AllowExperimentalFeatures: true, + testingOverrides: &testingOverrides{ + Providers: map[addrs.Provider]providers.Factory{ + mockProviderAddress: providers.FactoryFixed(mockProvider), + }, + }, + ProviderSource: providerSource, + }, + } + + args := []string{"-enable-pluggable-state-storage-experiment=true"} + code := c.Run(args) + testOutput := done(t) + if code != 0 { + t.Fatalf("expected code 0 exit code, got %d, output: \n%s", code, testOutput.All()) + } + + // Check output + output := testOutput.All() + expectedOutput := `Initializing the state store...` + if !strings.Contains(output, expectedOutput) { + t.Fatalf("expected output to include %q, but got':\n %s", expectedOutput, output) + } + + // Assert the default workspace was created + if _, exists := mockProvider.MockStates[backend.DefaultStateName]; !exists { + t.Fatal("expected the default workspace to be created during init, but it is missing") + } + }) + + t.Run("an init command with the flag -create-default-workspace=false will not make the default workspace", func(t *testing.T) { + // Create a temporary, uninitialized working directory with configuration including a state store + td := t.TempDir() + testCopyDir(t, testFixturePath("init-with-state-store"), td) + t.Chdir(td) + + mockProvider := mockPluggableStateStorageProvider() + mockProviderAddress := addrs.NewDefaultProvider("test") + providerSource, close := newMockProviderSource(t, map[string][]string{ + "hashicorp/test": {"1.0.0"}, + }) + defer close() + + ui := new(cli.MockUi) + view, done := testView(t) + c := &InitCommand{ + Meta: Meta{ + Ui: ui, + View: view, + AllowExperimentalFeatures: true, + testingOverrides: &testingOverrides{ + Providers: map[addrs.Provider]providers.Factory{ + mockProviderAddress: providers.FactoryFixed(mockProvider), + }, + }, + ProviderSource: providerSource, + }, + } + + args := []string{"-enable-pluggable-state-storage-experiment=true", "-create-default-workspace=false"} + code := c.Run(args) + testOutput := done(t) + if code != 0 { + t.Fatalf("expected code 0 exit code, got %d, output: \n%s", code, testOutput.All()) + } + + // Check output + output := testOutput.All() + expectedOutput := `Initializing the state store...` + if !strings.Contains(output, expectedOutput) { + t.Fatalf("expected output to include %q, but got':\n %s", expectedOutput, output) + } + + // Assert the default workspace was created + if _, exists := mockProvider.MockStates[backend.DefaultStateName]; exists { + t.Fatal("expected Terraform to skip creating the default workspace, but it has been created") + } + }) + + // TODO: Add test cases below once PSS feature isn't experimental. + // Currently these tests are handled at a lower level in `internal/command/meta_backend_test.go`: + // > "during a non-init command, the command ends in with an error telling the user to run an init command" +} + // newMockProviderSource is a helper to succinctly construct a mock provider // source that contains a set of packages matching the given provider versions // that are available for installation (from temporary local files). @@ -3397,3 +3503,65 @@ func expectedPackageInstallPath(name, version string, exe bool) string { baseDir, fmt.Sprintf("registry.terraform.io/hashicorp/%s/%s/%s", name, version, platform), )) } + +func mockPluggableStateStorageProvider() *testing_provider.MockProvider { + // Create a mock provider to use for PSS + // Get mock provider factory to be used during init + // + // This imagines a provider called `test` that contains + // a pluggable state store implementation called `store`. + pssName := "test_store" + mock := testing_provider.MockProvider{ + GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ + Provider: providers.Schema{ + Body: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "region": {Type: cty.String, Optional: true}, + }, + }, + }, + DataSources: map[string]providers.Schema{}, + ResourceTypes: map[string]providers.Schema{}, + ListResourceTypes: map[string]providers.Schema{}, + StateStores: map[string]providers.Schema{ + pssName: { + Body: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "bar": { + Type: cty.String, + Required: true, + }, + }, + }, + }, + }, + }, + } + mock.WriteStateBytesFn = func(req providers.WriteStateBytesRequest) providers.WriteStateBytesResponse { + // Workspaces exist once the artefact representing it is written + if _, exist := mock.MockStates[req.StateId]; !exist { + // Ensure non-nil map + if mock.MockStates == nil { + mock.MockStates = make(map[string]interface{}) + } + + mock.MockStates[req.StateId] = req.Bytes + } + return providers.WriteStateBytesResponse{ + Diagnostics: nil, // success + } + } + mock.ReadStateBytesFn = func(req providers.ReadStateBytesRequest) providers.ReadStateBytesResponse { + state := []byte{} + if v, exist := mock.MockStates[req.StateId]; exist { + if s, ok := v.([]byte); ok { + state = s + } + } + return providers.ReadStateBytesResponse{ + Bytes: state, + Diagnostics: nil, // success + } + } + return &mock +} diff --git a/internal/command/meta_backend_test.go b/internal/command/meta_backend_test.go index a6b32ee72173..f73ff2d5166e 100644 --- a/internal/command/meta_backend_test.go +++ b/internal/command/meta_backend_test.go @@ -2072,189 +2072,113 @@ func Test_determineInitReason(t *testing.T) { // Newly configured state store // Working directory has state_store in config but no preexisting backend state file -func TestMetaBackend_configureNewStateStore(t *testing.T) { - cases := map[string]struct { - // setup - isInitCommand bool - createDefaultWorkspace bool - // assertions - expectedError string - expectDefaultWorkspaceExists bool - }{ - "an init command creates the default workspace by default": { - createDefaultWorkspace: true, - isInitCommand: true, - expectDefaultWorkspaceExists: true, - }, - "an init command with the flag -create-default-workspace=false will not make the default workspace": { - createDefaultWorkspace: false, - isInitCommand: true, - expectDefaultWorkspaceExists: false, - }, - "during a non-init command, the command ends in with an error telling the user to run an init command": { - isInitCommand: false, - expectedError: "State store initialization required, please run \"terraform init\": Reason: Initial configuration of the requested state_store \"foo_bar\" in provider foo (\"registry.terraform.io/my-org/foo\")", - }, - } +// +// Tests that, during a non-init command, the command ends in with an error telling the user to run an init command +func TestMetaBackend_nonInitCommandInterrupted_uninitializedStateStore(t *testing.T) { - for tn, tc := range cases { - t.Run(tn, func(t *testing.T) { - td := t.TempDir() - testCopyDir(t, testFixturePath("state-store-new"), td) - t.Chdir(td) + td := t.TempDir() + testCopyDir(t, testFixturePath("state-store-new"), td) + t.Chdir(td) - // Setup the meta - m := testMetaBackend(t, nil) - m.AllowExperimentalFeatures = true + // Setup the meta + m := testMetaBackend(t, nil) + m.AllowExperimentalFeatures = true - // Get the state store's config - mod, loadDiags := m.loadSingleModule(td) - if loadDiags.HasErrors() { - t.Fatalf("unexpected error when loading test config: %s", loadDiags.Err()) - } + // Get the state store's config + mod, loadDiags := m.loadSingleModule(td) + if loadDiags.HasErrors() { + t.Fatalf("unexpected error when loading test config: %s", loadDiags.Err()) + } - // Get mock provider factory to be used during init - // - // This imagines a provider called foo that contains - // a pluggable state store implementation called bar. - pssName := "foo_bar" - mock := &testing_provider.MockProvider{ - GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ - Provider: providers.Schema{ - Body: &configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "region": {Type: cty.String, Optional: true}, - }, - }, + // Get mock provider factory to be used during init + // + // This imagines a provider called foo that contains + // a pluggable state store implementation called bar. + pssName := "foo_bar" + mock := &testing_provider.MockProvider{ + GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ + Provider: providers.Schema{ + Body: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "region": {Type: cty.String, Optional: true}, }, - DataSources: map[string]providers.Schema{}, - ResourceTypes: map[string]providers.Schema{}, - ListResourceTypes: map[string]providers.Schema{}, - StateStores: map[string]providers.Schema{ - pssName: { - Body: &configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "bar": { - Type: cty.String, - Required: true, - }, - }, + }, + }, + DataSources: map[string]providers.Schema{}, + ResourceTypes: map[string]providers.Schema{}, + ListResourceTypes: map[string]providers.Schema{}, + StateStores: map[string]providers.Schema{ + pssName: { + Body: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "bar": { + Type: cty.String, + Required: true, }, }, }, }, - } - mock.WriteStateBytesFn = func(req providers.WriteStateBytesRequest) providers.WriteStateBytesResponse { - // Workspaces exist once the artefact representing it is written - if _, exist := mock.MockStates[req.StateId]; !exist { - // Ensure non-nil map - if mock.MockStates == nil { - mock.MockStates = make(map[string]interface{}) - } - - mock.MockStates[req.StateId] = req.Bytes - } - return providers.WriteStateBytesResponse{ - Diagnostics: nil, // success - } - } - mock.ReadStateBytesFn = func(req providers.ReadStateBytesRequest) providers.ReadStateBytesResponse { - state := []byte{} - if v, exist := mock.MockStates[req.StateId]; exist { - if s, ok := v.([]byte); ok { - state = s - } - } - return providers.ReadStateBytesResponse{ - Bytes: state, - Diagnostics: nil, // success - } - } - factory := func() (providers.Interface, error) { - return mock, nil - } - - // Create locks - these would normally be the locks derived from config - locks := depsfile.NewLocks() - constraint, err := providerreqs.ParseVersionConstraints(">9.0.0") - if err != nil { - t.Fatalf("test setup failed when making constraint: %s", err) - } - expectedVersionString := "9.9.9" - expectedProviderSource := "registry.terraform.io/my-org/foo" - locks.SetProvider( - addrs.MustParseProviderSourceString(expectedProviderSource), - versions.MustParseVersion(expectedVersionString), - constraint, - []providerreqs.Hash{"h1:foo"}, - ) - - // Act - get the operations backend - b, beDiags := m.Backend(&BackendOpts{ - Init: tc.isInitCommand, // Changes with test case - StateStoreConfig: mod.StateStore, - ProviderFactory: factory, - Locks: locks, - CreateDefaultWorkspace: tc.createDefaultWorkspace, - }) - if beDiags.HasErrors() { - if tc.expectedError == "" { - t.Fatalf("unexpected error: %s", beDiags.Err()) - } - if !strings.Contains(cleanString(beDiags.Err().Error()), tc.expectedError) { - t.Fatalf("expected error to contain %s, but instead got: %s", tc.expectedError, cleanString(beDiags.Err().Error())) - } - return // error is as expected - } - if tc.expectedError != "" && !beDiags.HasErrors() { - t.Fatal("expected error missing") + }, + }, + } + mock.WriteStateBytesFn = func(req providers.WriteStateBytesRequest) providers.WriteStateBytesResponse { + // Workspaces exist once the artefact representing it is written + if _, exist := mock.MockStates[req.StateId]; !exist { + // Ensure non-nil map + if mock.MockStates == nil { + mock.MockStates = make(map[string]interface{}) } - // Check the backend state file exists & assert its contents - s := testDataStateRead(t, filepath.Join(DefaultDataDir, backendLocal.DefaultStateFilename)) - if s == nil { - t.Fatal("expected backend state file to be created, but it was missing") + mock.MockStates[req.StateId] = req.Bytes + } + return providers.WriteStateBytesResponse{ + Diagnostics: nil, // success + } + } + mock.ReadStateBytesFn = func(req providers.ReadStateBytesRequest) providers.ReadStateBytesResponse { + state := []byte{} + if v, exist := mock.MockStates[req.StateId]; exist { + if s, ok := v.([]byte); ok { + state = s } + } + return providers.ReadStateBytesResponse{ + Bytes: state, + Diagnostics: nil, // success + } + } + factory := func() (providers.Interface, error) { + return mock, nil + } - if s.StateStore.Type != pssName { - t.Fatalf("backend state file contains unexpected state store type, want %q, got %q", pssName, s.StateStore.Type) - } - if s.StateStore.Provider.Version.String() != expectedVersionString { - t.Fatalf("backend state file contains unexpected version, want %q, got %q", expectedVersionString, s.StateStore.Provider.Version) - } - if s.StateStore.Provider.Source.String() != expectedProviderSource { - t.Fatalf("backend state file contains unexpected source, want %q, got %q", expectedProviderSource, s.StateStore.Provider.Source) - } - expectedProviderConfig := "{ \"region\": \"mars\" }" - expectedStoreConfig := "{ \"bar\": \"foobar\" }" - if cleanString(string(s.StateStore.Provider.ConfigRaw)) != expectedProviderConfig { - t.Fatalf("backend state file contains unexpected raw config data for the provider, want %q, got %q", expectedProviderConfig, cleanString(string(s.StateStore.Provider.ConfigRaw))) - } - if cleanString(string(s.StateStore.ConfigRaw)) != expectedStoreConfig { - t.Fatalf("backend state file contains unexpected raw config data for the state store, want %q, got %q", expectedStoreConfig, cleanString(string(s.StateStore.ConfigRaw))) - } + // Create locks - these would normally be the locks derived from config + locks := depsfile.NewLocks() + constraint, err := providerreqs.ParseVersionConstraints(">9.0.0") + if err != nil { + t.Fatalf("test setup failed when making constraint: %s", err) + } + expectedVersionString := "9.9.9" + expectedProviderSource := "registry.terraform.io/my-org/foo" + locks.SetProvider( + addrs.MustParseProviderSourceString(expectedProviderSource), + versions.MustParseVersion(expectedVersionString), + constraint, + []providerreqs.Hash{"h1:foo"}, + ) - w, err := b.Workspaces() - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - if len(w) == 0 { - if tc.expectDefaultWorkspaceExists { - t.Fatal("expected the default workspace to exist, but there are no workspaces") - } - return - } - if len(w) > 0 { - if tc.expectDefaultWorkspaceExists { - if len(w) != 1 || w[0] != "default" { - t.Fatalf("expected only the default workspace to exist, but instead got: %v", w) - } - return // we've got the expected default workspace - } - - t.Fatalf("got unexpected workspaces: %v", w) - } - }) + // Act - get the operations backend + _, beDiags := m.Backend(&BackendOpts{ + Init: false, + StateStoreConfig: mod.StateStore, + ProviderFactory: factory, + Locks: locks, + }) + if !beDiags.HasErrors() { + t.Fatal("expected an error but got none") + } + expectedError := "State store initialization required, please run \"terraform init\": Reason: Initial configuration of the requested state_store \"foo_bar\" in provider foo (\"registry.terraform.io/my-org/foo\")" + if !strings.Contains(cleanString(beDiags.Err().Error()), expectedError) { + t.Fatalf("expected error to contain %s, but instead got: %s", expectedError, cleanString(beDiags.Err().Error())) } } diff --git a/internal/command/testdata/init-with-state-store/main.tf b/internal/command/testdata/init-with-state-store/main.tf index 9939e9dece2b..604feb44fed7 100644 --- a/internal/command/testdata/init-with-state-store/main.tf +++ b/internal/command/testdata/init-with-state-store/main.tf @@ -1,11 +1,11 @@ terraform { required_providers { - foo = { - source = "my-org/foo" + test = { + source = "hashicorp/test" } } - state_store "foo_foo" { - provider "foo" {} + state_store "test_store" { + provider "test" {} } } From 60cb7733857aa85be98661bbd561789326a6ce7f Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 29 Aug 2025 14:36:31 +0100 Subject: [PATCH 39/60] Update Read/WriteStateBytes RPCs to match https://github.com/hashicorp/terraform-plugin-go/pull/531 --- docs/plugin-protocol/tfplugin6.proto | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/plugin-protocol/tfplugin6.proto b/docs/plugin-protocol/tfplugin6.proto index 4d3b8c365900..364dd6e7ac3a 100644 --- a/docs/plugin-protocol/tfplugin6.proto +++ b/docs/plugin-protocol/tfplugin6.proto @@ -426,7 +426,7 @@ service Provider { rpc ConfigureStateStore(ConfigureStateStore.Request) returns (ConfigureStateStore.Response); // ReadStateBytes streams byte chunks of a given state file from a state store - rpc ReadStateBytes(ReadStateBytes.Request) returns (stream ReadStateBytes.ResponseChunk); + rpc ReadStateBytes(ReadStateBytes.Request) returns (stream ReadStateBytes.Response); // WriteStateBytes streams byte chunks of a given state file into a state store rpc WriteStateBytes(stream WriteStateBytes.RequestChunk) returns (WriteStateBytes.Response); @@ -923,7 +923,6 @@ message ValidateListResourceConfig { } } - message ValidateStateStore { message Request { string type_name = 1; @@ -949,7 +948,7 @@ message ReadStateBytes { string type_name = 1; string state_id = 2; } - message ResponseChunk { + message Response { bytes bytes = 1; int64 total_length = 2; StateRange range = 3; @@ -959,9 +958,11 @@ message ReadStateBytes { message WriteStateBytes { message RequestChunk { + // TODO: Can we decouple this outside of the stream? string type_name = 1; - bytes bytes = 2; string state_id = 3; + + bytes bytes = 2; int64 total_length = 4; StateRange range = 5; } From 1a8002640f3e15fd6540f11fa7f226590cca7a7c Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 29 Aug 2025 14:40:54 +0100 Subject: [PATCH 40/60] Run `make protobuf` --- internal/tfplugin6/tfplugin6.pb.go | 85 +++++++++++++++--------------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/internal/tfplugin6/tfplugin6.pb.go b/internal/tfplugin6/tfplugin6.pb.go index 555abba49689..efa033e2f22b 100644 --- a/internal/tfplugin6/tfplugin6.pb.go +++ b/internal/tfplugin6/tfplugin6.pb.go @@ -7010,7 +7010,7 @@ func (x *ReadStateBytes_Request) GetStateId() string { return "" } -type ReadStateBytes_ResponseChunk struct { +type ReadStateBytes_Response struct { state protoimpl.MessageState `protogen:"open.v1"` Bytes []byte `protobuf:"bytes,1,opt,name=bytes,proto3" json:"bytes,omitempty"` TotalLength int64 `protobuf:"varint,2,opt,name=total_length,json=totalLength,proto3" json:"total_length,omitempty"` @@ -7020,20 +7020,20 @@ type ReadStateBytes_ResponseChunk struct { sizeCache protoimpl.SizeCache } -func (x *ReadStateBytes_ResponseChunk) Reset() { - *x = ReadStateBytes_ResponseChunk{} +func (x *ReadStateBytes_Response) Reset() { + *x = ReadStateBytes_Response{} mi := &file_tfplugin6_proto_msgTypes[131] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *ReadStateBytes_ResponseChunk) String() string { +func (x *ReadStateBytes_Response) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ReadStateBytes_ResponseChunk) ProtoMessage() {} +func (*ReadStateBytes_Response) ProtoMessage() {} -func (x *ReadStateBytes_ResponseChunk) ProtoReflect() protoreflect.Message { +func (x *ReadStateBytes_Response) ProtoReflect() protoreflect.Message { mi := &file_tfplugin6_proto_msgTypes[131] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -7045,33 +7045,33 @@ func (x *ReadStateBytes_ResponseChunk) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ReadStateBytes_ResponseChunk.ProtoReflect.Descriptor instead. -func (*ReadStateBytes_ResponseChunk) Descriptor() ([]byte, []int) { +// Deprecated: Use ReadStateBytes_Response.ProtoReflect.Descriptor instead. +func (*ReadStateBytes_Response) Descriptor() ([]byte, []int) { return file_tfplugin6_proto_rawDescGZIP(), []int{39, 1} } -func (x *ReadStateBytes_ResponseChunk) GetBytes() []byte { +func (x *ReadStateBytes_Response) GetBytes() []byte { if x != nil { return x.Bytes } return nil } -func (x *ReadStateBytes_ResponseChunk) GetTotalLength() int64 { +func (x *ReadStateBytes_Response) GetTotalLength() int64 { if x != nil { return x.TotalLength } return 0 } -func (x *ReadStateBytes_ResponseChunk) GetRange() *StateRange { +func (x *ReadStateBytes_Response) GetRange() *StateRange { if x != nil { return x.Range } return nil } -func (x *ReadStateBytes_ResponseChunk) GetDiagnostics() []*Diagnostic { +func (x *ReadStateBytes_Response) GetDiagnostics() []*Diagnostic { if x != nil { return x.Diagnostics } @@ -7079,12 +7079,13 @@ func (x *ReadStateBytes_ResponseChunk) GetDiagnostics() []*Diagnostic { } type WriteStateBytes_RequestChunk struct { - state protoimpl.MessageState `protogen:"open.v1"` - TypeName string `protobuf:"bytes,1,opt,name=type_name,json=typeName,proto3" json:"type_name,omitempty"` - Bytes []byte `protobuf:"bytes,2,opt,name=bytes,proto3" json:"bytes,omitempty"` - StateId string `protobuf:"bytes,3,opt,name=state_id,json=stateId,proto3" json:"state_id,omitempty"` - TotalLength int64 `protobuf:"varint,4,opt,name=total_length,json=totalLength,proto3" json:"total_length,omitempty"` - Range *StateRange `protobuf:"bytes,5,opt,name=range,proto3" json:"range,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + // TODO: Can we decouple this outside of the stream? + TypeName string `protobuf:"bytes,1,opt,name=type_name,json=typeName,proto3" json:"type_name,omitempty"` + StateId string `protobuf:"bytes,3,opt,name=state_id,json=stateId,proto3" json:"state_id,omitempty"` + Bytes []byte `protobuf:"bytes,2,opt,name=bytes,proto3" json:"bytes,omitempty"` + TotalLength int64 `protobuf:"varint,4,opt,name=total_length,json=totalLength,proto3" json:"total_length,omitempty"` + Range *StateRange `protobuf:"bytes,5,opt,name=range,proto3" json:"range,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -7126,18 +7127,18 @@ func (x *WriteStateBytes_RequestChunk) GetTypeName() string { return "" } -func (x *WriteStateBytes_RequestChunk) GetBytes() []byte { +func (x *WriteStateBytes_RequestChunk) GetStateId() string { if x != nil { - return x.Bytes + return x.StateId } - return nil + return "" } -func (x *WriteStateBytes_RequestChunk) GetStateId() string { +func (x *WriteStateBytes_RequestChunk) GetBytes() []byte { if x != nil { - return x.StateId + return x.Bytes } - return "" + return nil } func (x *WriteStateBytes_RequestChunk) GetTotalLength() int64 { @@ -8587,21 +8588,21 @@ const file_tfplugin6_proto_rawDesc = "" + "\ttype_name\x18\x01 \x01(\tR\btypeName\x12/\n" + "\x06config\x18\x02 \x01(\v2\x17.tfplugin6.DynamicValueR\x06config\x1aC\n" + "\bResponse\x127\n" + - "\vdiagnostics\x18\x01 \x03(\v2\x15.tfplugin6.DiagnosticR\vdiagnostics\"\x84\x02\n" + + "\vdiagnostics\x18\x01 \x03(\v2\x15.tfplugin6.DiagnosticR\vdiagnostics\"\xff\x01\n" + "\x0eReadStateBytes\x1aA\n" + "\aRequest\x12\x1b\n" + "\ttype_name\x18\x01 \x01(\tR\btypeName\x12\x19\n" + - "\bstate_id\x18\x02 \x01(\tR\astateId\x1a\xae\x01\n" + - "\rResponseChunk\x12\x14\n" + + "\bstate_id\x18\x02 \x01(\tR\astateId\x1a\xa9\x01\n" + + "\bResponse\x12\x14\n" + "\x05bytes\x18\x01 \x01(\fR\x05bytes\x12!\n" + "\ftotal_length\x18\x02 \x01(\x03R\vtotalLength\x12+\n" + "\x05range\x18\x03 \x01(\v2\x15.tfplugin6.StateRangeR\x05range\x127\n" + "\vdiagnostics\x18\x04 \x03(\v2\x15.tfplugin6.DiagnosticR\vdiagnostics\"\x85\x02\n" + "\x0fWriteStateBytes\x1a\xac\x01\n" + "\fRequestChunk\x12\x1b\n" + - "\ttype_name\x18\x01 \x01(\tR\btypeName\x12\x14\n" + - "\x05bytes\x18\x02 \x01(\fR\x05bytes\x12\x19\n" + - "\bstate_id\x18\x03 \x01(\tR\astateId\x12!\n" + + "\ttype_name\x18\x01 \x01(\tR\btypeName\x12\x19\n" + + "\bstate_id\x18\x03 \x01(\tR\astateId\x12\x14\n" + + "\x05bytes\x18\x02 \x01(\fR\x05bytes\x12!\n" + "\ftotal_length\x18\x04 \x01(\x03R\vtotalLength\x12+\n" + "\x05range\x18\x05 \x01(\v2\x15.tfplugin6.StateRangeR\x05range\x1aC\n" + "\bResponse\x127\n" + @@ -8682,7 +8683,7 @@ const file_tfplugin6_proto_rawDesc = "" + "\n" + "StringKind\x12\t\n" + "\x05PLAIN\x10\x00\x12\f\n" + - "\bMARKDOWN\x10\x012\xfb\x19\n" + + "\bMARKDOWN\x10\x012\xf6\x19\n" + "\bProvider\x12N\n" + "\vGetMetadata\x12\x1e.tfplugin6.GetMetadata.Request\x1a\x1f.tfplugin6.GetMetadata.Response\x12`\n" + "\x11GetProviderSchema\x12$.tfplugin6.GetProviderSchema.Request\x1a%.tfplugin6.GetProviderSchema.Response\x12o\n" + @@ -8708,8 +8709,8 @@ const file_tfplugin6_proto_rawDesc = "" + "\fGetFunctions\x12\x1f.tfplugin6.GetFunctions.Request\x1a .tfplugin6.GetFunctions.Response\x12Q\n" + "\fCallFunction\x12\x1f.tfplugin6.CallFunction.Request\x1a .tfplugin6.CallFunction.Response\x12i\n" + "\x18ValidateStateStoreConfig\x12%.tfplugin6.ValidateStateStore.Request\x1a&.tfplugin6.ValidateStateStore.Response\x12f\n" + - "\x13ConfigureStateStore\x12&.tfplugin6.ConfigureStateStore.Request\x1a'.tfplugin6.ConfigureStateStore.Response\x12^\n" + - "\x0eReadStateBytes\x12!.tfplugin6.ReadStateBytes.Request\x1a'.tfplugin6.ReadStateBytes.ResponseChunk0\x01\x12a\n" + + "\x13ConfigureStateStore\x12&.tfplugin6.ConfigureStateStore.Request\x1a'.tfplugin6.ConfigureStateStore.Response\x12Y\n" + + "\x0eReadStateBytes\x12!.tfplugin6.ReadStateBytes.Request\x1a\".tfplugin6.ReadStateBytes.Response0\x01\x12a\n" + "\x0fWriteStateBytes\x12'.tfplugin6.WriteStateBytes.RequestChunk\x1a#.tfplugin6.WriteStateBytes.Response(\x01\x12H\n" + "\tGetStates\x12\x1c.tfplugin6.GetStates.Request\x1a\x1d.tfplugin6.GetStates.Response\x12N\n" + "\vDeleteState\x12\x1e.tfplugin6.DeleteState.Request\x1a\x1f.tfplugin6.DeleteState.Response\x12K\n" + @@ -8871,7 +8872,7 @@ var file_tfplugin6_proto_goTypes = []any{ (*ConfigureStateStore_Request)(nil), // 134: tfplugin6.ConfigureStateStore.Request (*ConfigureStateStore_Response)(nil), // 135: tfplugin6.ConfigureStateStore.Response (*ReadStateBytes_Request)(nil), // 136: tfplugin6.ReadStateBytes.Request - (*ReadStateBytes_ResponseChunk)(nil), // 137: tfplugin6.ReadStateBytes.ResponseChunk + (*ReadStateBytes_Response)(nil), // 137: tfplugin6.ReadStateBytes.Response (*WriteStateBytes_RequestChunk)(nil), // 138: tfplugin6.WriteStateBytes.RequestChunk (*WriteStateBytes_Response)(nil), // 139: tfplugin6.WriteStateBytes.Response (*GetStates_Request)(nil), // 140: tfplugin6.GetStates.Request @@ -9043,8 +9044,8 @@ var file_tfplugin6_proto_depIdxs = []int32{ 7, // 147: tfplugin6.ValidateStateStore.Response.diagnostics:type_name -> tfplugin6.Diagnostic 6, // 148: tfplugin6.ConfigureStateStore.Request.config:type_name -> tfplugin6.DynamicValue 7, // 149: tfplugin6.ConfigureStateStore.Response.diagnostics:type_name -> tfplugin6.Diagnostic - 47, // 150: tfplugin6.ReadStateBytes.ResponseChunk.range:type_name -> tfplugin6.StateRange - 7, // 151: tfplugin6.ReadStateBytes.ResponseChunk.diagnostics:type_name -> tfplugin6.Diagnostic + 47, // 150: tfplugin6.ReadStateBytes.Response.range:type_name -> tfplugin6.StateRange + 7, // 151: tfplugin6.ReadStateBytes.Response.diagnostics:type_name -> tfplugin6.Diagnostic 47, // 152: tfplugin6.WriteStateBytes.RequestChunk.range:type_name -> tfplugin6.StateRange 7, // 153: tfplugin6.WriteStateBytes.Response.diagnostics:type_name -> tfplugin6.Diagnostic 7, // 154: tfplugin6.GetStates.Response.diagnostics:type_name -> tfplugin6.Diagnostic @@ -9135,7 +9136,7 @@ var file_tfplugin6_proto_depIdxs = []int32{ 127, // 239: tfplugin6.Provider.CallFunction:output_type -> tfplugin6.CallFunction.Response 133, // 240: tfplugin6.Provider.ValidateStateStoreConfig:output_type -> tfplugin6.ValidateStateStore.Response 135, // 241: tfplugin6.Provider.ConfigureStateStore:output_type -> tfplugin6.ConfigureStateStore.Response - 137, // 242: tfplugin6.Provider.ReadStateBytes:output_type -> tfplugin6.ReadStateBytes.ResponseChunk + 137, // 242: tfplugin6.Provider.ReadStateBytes:output_type -> tfplugin6.ReadStateBytes.Response 139, // 243: tfplugin6.Provider.WriteStateBytes:output_type -> tfplugin6.WriteStateBytes.Response 141, // 244: tfplugin6.Provider.GetStates:output_type -> tfplugin6.GetStates.Response 143, // 245: tfplugin6.Provider.DeleteState:output_type -> tfplugin6.DeleteState.Response @@ -9539,7 +9540,7 @@ func (c *providerClient) ReadStateBytes(ctx context.Context, in *ReadStateBytes_ } type Provider_ReadStateBytesClient interface { - Recv() (*ReadStateBytes_ResponseChunk, error) + Recv() (*ReadStateBytes_Response, error) grpc.ClientStream } @@ -9547,8 +9548,8 @@ type providerReadStateBytesClient struct { grpc.ClientStream } -func (x *providerReadStateBytesClient) Recv() (*ReadStateBytes_ResponseChunk, error) { - m := new(ReadStateBytes_ResponseChunk) +func (x *providerReadStateBytesClient) Recv() (*ReadStateBytes_Response, error) { + m := new(ReadStateBytes_Response) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } @@ -10298,7 +10299,7 @@ func _Provider_ReadStateBytes_Handler(srv interface{}, stream grpc.ServerStream) } type Provider_ReadStateBytesServer interface { - Send(*ReadStateBytes_ResponseChunk) error + Send(*ReadStateBytes_Response) error grpc.ServerStream } @@ -10306,7 +10307,7 @@ type providerReadStateBytesServer struct { grpc.ServerStream } -func (x *providerReadStateBytesServer) Send(m *ReadStateBytes_ResponseChunk) error { +func (x *providerReadStateBytesServer) Send(m *ReadStateBytes_Response) error { return x.ServerStream.SendMsg(m) } From 167ea42e3a39509a83cc83be529643e34e447755 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 29 Aug 2025 14:41:06 +0100 Subject: [PATCH 41/60] Run `make generate` --- internal/plugin6/mock_proto/mock.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/plugin6/mock_proto/mock.go b/internal/plugin6/mock_proto/mock.go index b4c9250177a7..a9046ca1bba9 100644 --- a/internal/plugin6/mock_proto/mock.go +++ b/internal/plugin6/mock_proto/mock.go @@ -892,10 +892,10 @@ func (mr *MockProvider_ReadStateBytesClientMockRecorder) Header() *gomock.Call { } // Recv mocks base method. -func (m *MockProvider_ReadStateBytesClient) Recv() (*tfplugin6.ReadStateBytes_ResponseChunk, error) { +func (m *MockProvider_ReadStateBytesClient) Recv() (*tfplugin6.ReadStateBytes_Response, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Recv") - ret0, _ := ret[0].(*tfplugin6.ReadStateBytes_ResponseChunk) + ret0, _ := ret[0].(*tfplugin6.ReadStateBytes_Response) ret1, _ := ret[1].(error) return ret0, ret1 } From 7f987b625bd825b44b595e9b2f3b256ff3545096 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 29 Aug 2025 15:12:05 +0100 Subject: [PATCH 42/60] Update use of `proto.ReadStateBytes_ResponseChunk` in tests --- internal/plugin6/grpc_provider_test.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/internal/plugin6/grpc_provider_test.go b/internal/plugin6/grpc_provider_test.go index 2271b61dc73b..6730900e0edd 100644 --- a/internal/plugin6/grpc_provider_test.go +++ b/internal/plugin6/grpc_provider_test.go @@ -3519,11 +3519,11 @@ func TestGRPCProvider_ReadStateBytes(t *testing.T) { chunks := []string{"hello", "world"} totalLength := len(chunks[0]) + len(chunks[1]) mockResp := map[int]struct { - resp *proto.ReadStateBytes_ResponseChunk + resp *proto.ReadStateBytes_Response err error }{ 0: { - resp: &proto.ReadStateBytes_ResponseChunk{ + resp: &proto.ReadStateBytes_Response{ Bytes: []byte(chunks[0]), TotalLength: int64(totalLength), Range: &proto.StateRange{ @@ -3534,7 +3534,7 @@ func TestGRPCProvider_ReadStateBytes(t *testing.T) { err: nil, }, 1: { - resp: &proto.ReadStateBytes_ResponseChunk{ + resp: &proto.ReadStateBytes_Response{ Bytes: []byte(chunks[1]), TotalLength: int64(totalLength), Range: &proto.StateRange{ @@ -3550,7 +3550,7 @@ func TestGRPCProvider_ReadStateBytes(t *testing.T) { }, } var count int - mockReadBytesClient.EXPECT().Recv().DoAndReturn(func() (*proto.ReadStateBytes_ResponseChunk, error) { + mockReadBytesClient.EXPECT().Recv().DoAndReturn(func() (*proto.ReadStateBytes_Response, error) { ret := mockResp[count] count++ return ret.resp, ret.err @@ -3595,11 +3595,11 @@ func TestGRPCProvider_ReadStateBytes(t *testing.T) { var incorrectLength int64 = 999 correctLength := len(chunks[0]) + len(chunks[1]) mockResp := map[int]struct { - resp *proto.ReadStateBytes_ResponseChunk + resp *proto.ReadStateBytes_Response err error }{ 0: { - resp: &proto.ReadStateBytes_ResponseChunk{ + resp: &proto.ReadStateBytes_Response{ Bytes: []byte(chunks[0]), TotalLength: incorrectLength, Range: &proto.StateRange{ @@ -3610,7 +3610,7 @@ func TestGRPCProvider_ReadStateBytes(t *testing.T) { err: nil, }, 1: { - resp: &proto.ReadStateBytes_ResponseChunk{ + resp: &proto.ReadStateBytes_Response{ Bytes: []byte(chunks[1]), TotalLength: incorrectLength, Range: &proto.StateRange{ @@ -3626,7 +3626,7 @@ func TestGRPCProvider_ReadStateBytes(t *testing.T) { }, } var count int - mockReadBytesClient.EXPECT().Recv().DoAndReturn(func() (*proto.ReadStateBytes_ResponseChunk, error) { + mockReadBytesClient.EXPECT().Recv().DoAndReturn(func() (*proto.ReadStateBytes_Response, error) { ret := mockResp[count] count++ return ret.resp, ret.err @@ -3702,7 +3702,7 @@ func TestGRPCProvider_ReadStateBytes(t *testing.T) { ).Return(mockReadBytesClient, nil) // Define what will be returned by each call to Recv - mockReadBytesClient.EXPECT().Recv().Return(&proto.ReadStateBytes_ResponseChunk{ + mockReadBytesClient.EXPECT().Recv().Return(&proto.ReadStateBytes_Response{ Diagnostics: []*proto.Diagnostic{ &proto.Diagnostic{ Severity: proto.Diagnostic_ERROR, @@ -3752,7 +3752,7 @@ func TestGRPCProvider_ReadStateBytes(t *testing.T) { ).Return(mockReadBytesClient, nil) // Define what will be returned by each call to Recv - mockReadBytesClient.EXPECT().Recv().Return(&proto.ReadStateBytes_ResponseChunk{ + mockReadBytesClient.EXPECT().Recv().Return(&proto.ReadStateBytes_Response{ Diagnostics: []*proto.Diagnostic{ &proto.Diagnostic{ Severity: proto.Diagnostic_WARNING, @@ -3801,7 +3801,7 @@ func TestGRPCProvider_ReadStateBytes(t *testing.T) { ).Return(mockClient, nil) mockError := errors.New("grpc error forced in test") - mockClient.EXPECT().Recv().Return(&proto.ReadStateBytes_ResponseChunk{}, mockError) + mockClient.EXPECT().Recv().Return(&proto.ReadStateBytes_Response{}, mockError) // Act request := providers.ReadStateBytesRequest{ From f705ad7fca69f007da264724a2d772232add6b03 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 29 Aug 2025 17:40:09 +0100 Subject: [PATCH 43/60] Fix how diagnostics are handled alongside EOF error, update ReadStateBytes test --- internal/plugin6/grpc_provider.go | 1 + internal/plugin6/grpc_provider_test.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/plugin6/grpc_provider.go b/internal/plugin6/grpc_provider.go index 61c8083e7690..8c54a9260817 100644 --- a/internal/plugin6/grpc_provider.go +++ b/internal/plugin6/grpc_provider.go @@ -1516,6 +1516,7 @@ func (p *GRPCProvider) ReadStateBytes(r providers.ReadStateBytesRequest) (resp p chunk, err := client.Recv() if err == io.EOF { // End of stream, we're done + resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(chunk.Diagnostics)) break } if err != nil { diff --git a/internal/plugin6/grpc_provider_test.go b/internal/plugin6/grpc_provider_test.go index 6730900e0edd..496f345c2c67 100644 --- a/internal/plugin6/grpc_provider_test.go +++ b/internal/plugin6/grpc_provider_test.go @@ -3760,7 +3760,7 @@ func TestGRPCProvider_ReadStateBytes(t *testing.T) { Detail: "This warning is forced by the test case", }, }, - }, nil) + }, io.EOF) // Act request := providers.ReadStateBytesRequest{ From 485237404f76a79208d1425cfbf1617c9e659de3 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 29 Aug 2025 18:20:24 +0100 Subject: [PATCH 44/60] More fixes - test setup was incorrect I think? I assume that a response would be returned full of zero-values when EOF is encountered. --- internal/plugin6/grpc_provider_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/plugin6/grpc_provider_test.go b/internal/plugin6/grpc_provider_test.go index 496f345c2c67..ab029d4d3773 100644 --- a/internal/plugin6/grpc_provider_test.go +++ b/internal/plugin6/grpc_provider_test.go @@ -3545,7 +3545,7 @@ func TestGRPCProvider_ReadStateBytes(t *testing.T) { err: nil, }, 2: { - resp: nil, + resp: &proto.ReadStateBytes_Response{}, err: io.EOF, }, } @@ -3621,7 +3621,7 @@ func TestGRPCProvider_ReadStateBytes(t *testing.T) { err: nil, }, 2: { - resp: nil, + resp: &proto.ReadStateBytes_Response{}, err: io.EOF, }, } From 972f3b35cc441dd325e34cb37332402a98195ed5 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 29 Aug 2025 18:40:45 +0100 Subject: [PATCH 45/60] Pull code for obtaining provider factory into a separate method --- internal/command/init.go | 31 +++------------------------- internal/command/meta_backend.go | 35 ++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 28 deletions(-) diff --git a/internal/command/init.go b/internal/command/init.go index de3133b6e96d..4921847090bb 100644 --- a/internal/command/init.go +++ b/internal/command/init.go @@ -191,34 +191,9 @@ func (c *InitCommand) initBackend(ctx context.Context, root *configs.Module, ini return nil, true, diags case root.StateStore != nil: // state_store config present - // Access provider factories - ctxOpts, err := c.contextOpts() - if err != nil { - diags = diags.Append(err) - return nil, true, diags - } - - if root.StateStore.ProviderAddr.IsZero() { - // This should not happen; this data is populated when parsing config, - // even for builtin providers - panic(fmt.Sprintf("unknown provider while beginning to initialize state store %q from provider %q", - root.StateStore.Type, - root.StateStore.Provider.Name)) - } - - var exists bool - factory, exists := ctxOpts.Providers[root.StateStore.ProviderAddr] - if !exists { - diags = diags.Append(&hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Provider unavailable", - Detail: fmt.Sprintf("The provider %s (%q) is required to initialize the %q state store, but the matching provider factory is missing. This is a bug in Terraform and should be reported.", - root.StateStore.Provider.Name, - root.StateStore.ProviderAddr, - root.StateStore.Type, - ), - Subject: &root.StateStore.TypeRange, - }) + factory, fDiags := c.Meta.getStateStoreProviderFactory(root.StateStore) + diags = diags.Append(fDiags) + if fDiags.HasErrors() { return nil, true, diags } diff --git a/internal/command/meta_backend.go b/internal/command/meta_backend.go index 51ad25c03183..d81336135997 100644 --- a/internal/command/meta_backend.go +++ b/internal/command/meta_backend.go @@ -2202,6 +2202,41 @@ func (m *Meta) assertSupportedCloudInitOptions(mode cloud.ConfigChangeMode) tfdi return diags } +func (m *Meta) getStateStoreProviderFactory(config *configs.StateStore) (providers.Factory, tfdiags.Diagnostics) { + var diags tfdiags.Diagnostics + + if config.ProviderAddr.IsZero() { + // This should not happen; this data is populated when parsing config, + // even for builtin providers + panic(fmt.Sprintf("unknown provider while beginning to initialize state store %q from provider %q", + config.Type, + config.Provider.Name)) + } + + ctxOpts, err := m.contextOpts() + if err != nil { + diags = diags.Append(err) + return nil, diags + } + + factory, exists := ctxOpts.Providers[config.ProviderAddr] + if !exists { + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Provider unavailable", + Detail: fmt.Sprintf("The provider %s (%q) is required to initialize the %q state store, but the matching provider factory is missing. This is a bug in Terraform and should be reported.", + config.Provider.Name, + config.ProviderAddr, + config.Type, + ), + Subject: &config.TypeRange, + }) + return nil, diags + } + + return factory, diags +} + //------------------------------------------------------------------- // Output constants and initialization code //------------------------------------------------------------------- From 46aa16e00ad051f9257f0c75580a47706af5409f Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 29 Aug 2025 18:56:08 +0100 Subject: [PATCH 46/60] Make Backend method return diagnostics, so warnings aren't swallowed. --- internal/command/meta_backend.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/command/meta_backend.go b/internal/command/meta_backend.go index d81336135997..1c347627ba0f 100644 --- a/internal/command/meta_backend.go +++ b/internal/command/meta_backend.go @@ -240,7 +240,7 @@ func (m *Meta) Backend(opts *BackendOpts) (backendrun.OperationsBackend, tfdiags } } - return local, nil + return local, diags } // selectWorkspace gets a list of existing workspaces and then checks From d33419997ae5057859caf1834913cf9febf022af Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 29 Aug 2025 18:56:13 +0100 Subject: [PATCH 47/60] Make test assert that warning about skipping workspace creation is included. --- internal/command/init_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/command/init_test.go b/internal/command/init_test.go index 26bf04b13568..c142f39b6baf 100644 --- a/internal/command/init_test.go +++ b/internal/command/init_test.go @@ -3347,7 +3347,7 @@ func TestInit_stateStore_newWorkingDir(t *testing.T) { // Check output output := testOutput.All() - expectedOutput := `Initializing the state store...` + expectedOutput := `Terraform has been configured to skip creation of the default workspace` if !strings.Contains(output, expectedOutput) { t.Fatalf("expected output to include %q, but got':\n %s", expectedOutput, output) } From 526e66f88d410ec28ad725472f609bd460f67e2b Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 29 Aug 2025 18:56:38 +0100 Subject: [PATCH 48/60] Update warning detail to include calls to action --- internal/command/meta_backend.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/command/meta_backend.go b/internal/command/meta_backend.go index 1c347627ba0f..95ac92ba5ab5 100644 --- a/internal/command/meta_backend.go +++ b/internal/command/meta_backend.go @@ -1677,7 +1677,7 @@ func (m *Meta) stateStore_C_s(c *configs.StateStore, cHash int, backendSMgr *cli diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagWarning, Summary: "The default workspace does not exist", - Detail: "Terraform has been configured to skip creation of the default workspace in the state store.", + Detail: "Terraform has been configured to skip creation of the default workspace in the state store. To create it, either run an 'init' command without `-create-default-workspace=true`, or create it using a 'workspace new' command", }) } default: From ffcc2c7bb3d531d1532a051f185bcde354e8c11c Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 5 Sep 2025 15:16:00 +0100 Subject: [PATCH 49/60] Fix use of hashes when initializing a new workingdir with a state store --- internal/command/meta_backend.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/internal/command/meta_backend.go b/internal/command/meta_backend.go index 95ac92ba5ab5..9a2be3c153f4 100644 --- a/internal/command/meta_backend.go +++ b/internal/command/meta_backend.go @@ -646,11 +646,13 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Di // Get the local 'backend' or 'state_store' configuration. var backendConfig *configs.Backend var stateStoreConfig *configs.StateStore - var cHash int + var cHash int // backend hash + var stateStoreHash int + var stateStoreProviderHash int if opts.StateStoreConfig != nil { // state store has been parsed from config and is included in opts var ssDiags tfdiags.Diagnostics - stateStoreConfig, cHash, _, ssDiags = m.stateStoreConfig(opts) + stateStoreConfig, stateStoreHash, stateStoreProviderHash, ssDiags = m.stateStoreConfig(opts) diags = diags.Append(ssDiags) if ssDiags.HasErrors() { return nil, diags @@ -817,7 +819,7 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Di return nil, diags } - return m.stateStore_C_s(stateStoreConfig, cHash, sMgr, opts) + return m.stateStore_C_s(stateStoreConfig, stateStoreHash, stateStoreProviderHash, sMgr, opts) // Migration from state store to backend case backendConfig != nil && s.Backend.Empty() && @@ -1494,7 +1496,7 @@ func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, backendSMgr //------------------------------------------------------------------- // Configuring a state_store for the first time. -func (m *Meta) stateStore_C_s(c *configs.StateStore, cHash int, backendSMgr *clistate.LocalState, opts *BackendOpts) (backend.Backend, tfdiags.Diagnostics) { +func (m *Meta) stateStore_C_s(c *configs.StateStore, stateStoreHash int, providerHash int, backendSMgr *clistate.LocalState, opts *BackendOpts) (backend.Backend, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics vt := arguments.ViewJSON @@ -1580,7 +1582,7 @@ func (m *Meta) stateStore_C_s(c *configs.StateStore, cHash int, backendSMgr *cli if m.stateLock { view := views.NewStateLocker(vt, m.View) stateLocker := clistate.NewLocker(m.stateLockTimeout, view) - if err := stateLocker.Lock(backendSMgr, "state_store from plan"); err != nil { + if err := stateLocker.Lock(backendSMgr, "init is initializing state_store first time"); err != nil { diags = diags.Append(fmt.Errorf("Error locking state: %s", err)) return nil, diags } @@ -1628,10 +1630,11 @@ func (m *Meta) stateStore_C_s(c *configs.StateStore, cHash int, backendSMgr *cli } s.StateStore = &workdir.StateStoreConfigState{ Type: c.Type, - Hash: uint64(cHash), + Hash: uint64(stateStoreHash), Provider: &workdir.ProviderConfigState{ Source: &c.ProviderAddr, Version: pVersion, + Hash: uint64(providerHash), }, } s.StateStore.SetConfig(storeConfigVal, b.ConfigSchema()) From 6a0132442971ad5802075b47bea63812eee9a68c Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 5 Sep 2025 15:54:35 +0100 Subject: [PATCH 50/60] Update test to assert backend state file contents --- internal/command/init_test.go | 56 ++++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/internal/command/init_test.go b/internal/command/init_test.go index c142f39b6baf..f52940387637 100644 --- a/internal/command/init_test.go +++ b/internal/command/init_test.go @@ -17,12 +17,15 @@ import ( "github.com/google/go-cmp/cmp" "github.com/hashicorp/cli" version "github.com/hashicorp/go-version" + tfaddr "github.com/hashicorp/terraform-registry-address" "github.com/zclconf/go-cty/cty" "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/backend" "github.com/hashicorp/terraform/internal/command/arguments" + "github.com/hashicorp/terraform/internal/command/clistate" "github.com/hashicorp/terraform/internal/command/views" + "github.com/hashicorp/terraform/internal/command/workdir" "github.com/hashicorp/terraform/internal/configs" "github.com/hashicorp/terraform/internal/configs/configschema" "github.com/hashicorp/terraform/internal/depsfile" @@ -3260,7 +3263,7 @@ func TestInit_stateStoreBlockIsExperimental(t *testing.T) { // Testing init's behaviors when run in an empty working directory func TestInit_stateStore_newWorkingDir(t *testing.T) { - t.Run("an init command creates the default workspace by default", func(t *testing.T) { + t.Run("the init command creates a backend state file, and creates the default workspace by default", func(t *testing.T) { // Create a temporary, uninitialized working directory with configuration including a state store td := t.TempDir() testCopyDir(t, testFixturePath("init-with-state-store"), td) @@ -3275,18 +3278,19 @@ func TestInit_stateStore_newWorkingDir(t *testing.T) { ui := new(cli.MockUi) view, done := testView(t) - c := &InitCommand{ - Meta: Meta{ - Ui: ui, - View: view, - AllowExperimentalFeatures: true, - testingOverrides: &testingOverrides{ - Providers: map[addrs.Provider]providers.Factory{ - mockProviderAddress: providers.FactoryFixed(mockProvider), - }, + meta := Meta{ + Ui: ui, + View: view, + AllowExperimentalFeatures: true, + testingOverrides: &testingOverrides{ + Providers: map[addrs.Provider]providers.Factory{ + mockProviderAddress: providers.FactoryFixed(mockProvider), }, - ProviderSource: providerSource, }, + ProviderSource: providerSource, + } + c := &InitCommand{ + Meta: meta, } args := []string{"-enable-pluggable-state-storage-experiment=true"} @@ -3307,6 +3311,36 @@ func TestInit_stateStore_newWorkingDir(t *testing.T) { if _, exists := mockProvider.MockStates[backend.DefaultStateName]; !exists { t.Fatal("expected the default workspace to be created during init, but it is missing") } + + // Assert contents of the backend state file + statePath := filepath.Join(meta.DataDir(), DefaultStateFilename) + sMgr := &clistate.LocalState{Path: statePath} + if err := sMgr.RefreshState(); err != nil { + t.Fatal("Failed to load state:", err) + } + s := sMgr.State() + if s == nil { + t.Fatal("expected backend state file to be created, but there isn't one") + } + v1_0_0, _ := version.NewVersion("1.0.0") + expectedState := &workdir.StateStoreConfigState{ + Type: "test_store", + ConfigRaw: []byte("{\n \"bar\": null\n }"), + Hash: uint64(3976463117), // Hash of empty config + Provider: &workdir.ProviderConfigState{ + Version: v1_0_0, + Source: &tfaddr.Provider{ + Hostname: tfaddr.DefaultProviderRegistryHost, + Namespace: "hashicorp", + Type: "test", + }, + ConfigRaw: []byte("{\n \"region\": null\n }"), + Hash: uint64(3976463117), // Hash of empty config + }, + } + if diff := cmp.Diff(s.StateStore, expectedState); diff != "" { + t.Fatalf("unexpected diff in backend state file's description of state store:\n%s", diff) + } }) t.Run("an init command with the flag -create-default-workspace=false will not make the default workspace", func(t *testing.T) { From 265d370276a61c9312d16251b4587a21e2773dd6 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 5 Sep 2025 17:57:25 +0100 Subject: [PATCH 51/60] Replace test for reconfiguring a state_store when the configuration has changed --- internal/command/init_test.go | 95 ++++++++++++++++++- internal/command/meta_backend_test.go | 94 ------------------ .../.terraform/terraform.tfstate | 2 +- .../testdata/state-store-reconfigure/main.tf | 10 +- 4 files changed, 100 insertions(+), 101 deletions(-) diff --git a/internal/command/init_test.go b/internal/command/init_test.go index f52940387637..3569d80aadc3 100644 --- a/internal/command/init_test.go +++ b/internal/command/init_test.go @@ -3397,6 +3397,99 @@ func TestInit_stateStore_newWorkingDir(t *testing.T) { // > "during a non-init command, the command ends in with an error telling the user to run an init command" } +func TestInit_stateStore_configChanges(t *testing.T) { + t.Run("the -reconfigure flag makes Terraform ignore the backend state file during initialization", func(t *testing.T) { + // Create a temporary working directory with state store configuration + // that doesn't match the backend state file + td := t.TempDir() + testCopyDir(t, testFixturePath("state-store-reconfigure"), td) + t.Chdir(td) + + mockProvider := mockPluggableStateStorageProvider() + mockProviderAddress := addrs.NewDefaultProvider("test") + providerSource, close := newMockProviderSource(t, map[string][]string{ + "hashicorp/test": {"1.0.0"}, + }) + defer close() + + ui := new(cli.MockUi) + view, done := testView(t) + meta := Meta{ + Ui: ui, + View: view, + AllowExperimentalFeatures: true, + testingOverrides: &testingOverrides{ + Providers: map[addrs.Provider]providers.Factory{ + mockProviderAddress: providers.FactoryFixed(mockProvider), + }, + }, + ProviderSource: providerSource, + } + c := &InitCommand{ + Meta: meta, + } + + args := []string{ + "-enable-pluggable-state-storage-experiment=true", + "-reconfigure", + } + code := c.Run(args) + testOutput := done(t) + if code != 0 { + t.Fatalf("expected code 0 exit code, got %d, output: \n%s", code, testOutput.All()) + } + + // Check output + output := testOutput.All() + expectedOutputs := []string{ + "Initializing the state store...", + "Terraform has been successfully initialized!", + } + for _, expected := range expectedOutputs { + if !strings.Contains(output, expected) { + t.Fatalf("expected output to include %q, but got':\n %s", expected, output) + } + } + + // Assert the default workspace was created + if _, exists := mockProvider.MockStates[backend.DefaultStateName]; !exists { + t.Fatal("expected the default workspace to be created during init, but it is missing") + } + + // Assert contents of the backend state file + statePath := filepath.Join(meta.DataDir(), DefaultStateFilename) + sMgr := &clistate.LocalState{Path: statePath} + if err := sMgr.RefreshState(); err != nil { + t.Fatal("Failed to load state:", err) + } + s := sMgr.State() + if s == nil { + t.Fatal("expected backend state file to be created, but there isn't one") + } + v1_0_0, _ := version.NewVersion("1.0.0") + expectedState := &workdir.StateStoreConfigState{ + Type: "test_store", + ConfigRaw: []byte("{\n \"value\": \"changed-value\"\n }"), + Hash: uint64(1417640992), // Hash affected by config + Provider: &workdir.ProviderConfigState{ + Version: v1_0_0, + Source: &tfaddr.Provider{ + Hostname: tfaddr.DefaultProviderRegistryHost, + Namespace: "hashicorp", + Type: "test", + }, + ConfigRaw: []byte("{\n \"region\": null\n }"), + Hash: uint64(3976463117), // Hash of empty config + }, + } + if diff := cmp.Diff(s.StateStore, expectedState); diff != "" { + t.Fatalf("unexpected diff in backend state file's description of state store:\n%s", diff) + } + }) + + // TODO - add more test cases, e.g. these context changes in the context of updating providers +} + // newMockProviderSource is a helper to succinctly construct a mock provider // source that contains a set of packages matching the given provider versions // that are available for installation (from temporary local files). @@ -3561,7 +3654,7 @@ func mockPluggableStateStorageProvider() *testing_provider.MockProvider { pssName: { Body: &configschema.Block{ Attributes: map[string]*configschema.Attribute{ - "bar": { + "value": { Type: cty.String, Required: true, }, diff --git a/internal/command/meta_backend_test.go b/internal/command/meta_backend_test.go index f73ff2d5166e..a53a6bcbb9b8 100644 --- a/internal/command/meta_backend_test.go +++ b/internal/command/meta_backend_test.go @@ -2220,100 +2220,6 @@ func TestMetaBackend_configuredStateStoreUnset(t *testing.T) { } } -// Reconfiguring with an already configured state store. -// This should ignore the existing state_store config, and configure the new -// state store is if this is the first time. -// -// TODO(SarahFrench/radeksimko): currently this test only confirms that we're hitting the switch -// case for this scenario, and will need to be updated when that init feature is implemented. -func TestMetaBackend_reconfigureStateStoreChange(t *testing.T) { - td := t.TempDir() - testCopyDir(t, testFixturePath("state-store-reconfigure"), td) - t.Chdir(td) - - // Setup the meta - m := testMetaBackend(t, nil) - m.AllowExperimentalFeatures = true - - // this should not ask for input - m.input = false - - // cli flag -reconfigure - m.reconfigure = true - - // Get the state store's config - mod, loadDiags := m.loadSingleModule(td) - if loadDiags.HasErrors() { - t.Fatalf("unexpected error when loading test config: %s", loadDiags.Err()) - } - - // Get mock provider factory to be used during init - // - // This imagines a provider called foo that contains - // a pluggable state store implementation called bar. - mock := &testing_provider.MockProvider{ - GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ - Provider: providers.Schema{ - Body: &configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "region": {Type: cty.String, Optional: true}, - }, - }, - }, - DataSources: map[string]providers.Schema{}, - ResourceTypes: map[string]providers.Schema{}, - ListResourceTypes: map[string]providers.Schema{}, - StateStores: map[string]providers.Schema{ - "foo_bar": { - Body: &configschema.Block{ - Attributes: map[string]*configschema.Attribute{ - "bar": { - Type: cty.String, - Required: true, - }, - }, - }, - }, - }, - }, - } - factory := func() (providers.Interface, error) { - return mock, nil - } - - // Create locks - these would normally be the locks derived from config - locks := depsfile.NewLocks() - constraint, err := providerreqs.ParseVersionConstraints(">9.0.0") - if err != nil { - t.Fatalf("test setup failed when making constraint: %s", err) - } - expectedVersionString := "9.9.9" - expectedProviderSource := "registry.terraform.io/my-org/foo" - locks.SetProvider( - addrs.MustParseProviderSourceString(expectedProviderSource), - versions.MustParseVersion(expectedVersionString), - constraint, - []providerreqs.Hash{"h1:foo"}, - ) - - // Get the operations backend - _, beDiags := m.Backend(&BackendOpts{ - Init: true, - StateStoreConfig: mod.StateStore, - ProviderFactory: factory, - Locks: locks, - }) - - if !beDiags.HasErrors() { - t.Fatal("expected an error to be returned during partial implementation of PSS") - } - wantErr := "Configuring a state store for the first time is not implemented yet" - if !strings.Contains(beDiags.Err().Error(), wantErr) { - t.Fatalf("expected the returned error to contain %q, but got: %s", wantErr, beDiags.Err()) - } - -} - // Changing a configured state store // // TODO(SarahFrench/radeksimko): currently this test only confirms that we're hitting the switch diff --git a/internal/command/testdata/state-store-reconfigure/.terraform/terraform.tfstate b/internal/command/testdata/state-store-reconfigure/.terraform/terraform.tfstate index a59e231faa03..365916938654 100644 --- a/internal/command/testdata/state-store-reconfigure/.terraform/terraform.tfstate +++ b/internal/command/testdata/state-store-reconfigure/.terraform/terraform.tfstate @@ -5,7 +5,7 @@ "state_store": { "type": "foo_bar", "config": { - "bar": "old-value" + "value": "old-value" }, "provider": { "version": "1.2.3", diff --git a/internal/command/testdata/state-store-reconfigure/main.tf b/internal/command/testdata/state-store-reconfigure/main.tf index 49184a175b6d..085c2f36d3f1 100644 --- a/internal/command/testdata/state-store-reconfigure/main.tf +++ b/internal/command/testdata/state-store-reconfigure/main.tf @@ -1,12 +1,12 @@ terraform { required_providers { - foo = { - source = "my-org/foo" + test = { + source = "hashicorp/test" } } - state_store "foo_bar" { - provider "foo" {} + state_store "test_store" { + provider "test" {} - bar = "changed-value" # changed versus backend state file + value = "changed-value" # changed versus backend state file } } From 8531320af62f7fe2af3dcf863f875bd112631120 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 5 Sep 2025 17:57:36 +0100 Subject: [PATCH 52/60] Fix test, add more assertions about output --- internal/command/init_test.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/internal/command/init_test.go b/internal/command/init_test.go index 3569d80aadc3..8e5e3c32dbbe 100644 --- a/internal/command/init_test.go +++ b/internal/command/init_test.go @@ -3302,9 +3302,14 @@ func TestInit_stateStore_newWorkingDir(t *testing.T) { // Check output output := testOutput.All() - expectedOutput := `Initializing the state store...` - if !strings.Contains(output, expectedOutput) { - t.Fatalf("expected output to include %q, but got':\n %s", expectedOutput, output) + expectedOutputs := []string{ + "Initializing the state store...", + "Terraform has been successfully initialized!", + } + for _, expected := range expectedOutputs { + if !strings.Contains(output, expected) { + t.Fatalf("expected output to include %q, but got':\n %s", expected, output) + } } // Assert the default workspace was created @@ -3325,7 +3330,7 @@ func TestInit_stateStore_newWorkingDir(t *testing.T) { v1_0_0, _ := version.NewVersion("1.0.0") expectedState := &workdir.StateStoreConfigState{ Type: "test_store", - ConfigRaw: []byte("{\n \"bar\": null\n }"), + ConfigRaw: []byte("{\n \"value\": null\n }"), Hash: uint64(3976463117), // Hash of empty config Provider: &workdir.ProviderConfigState{ Version: v1_0_0, From d3dc8421caa04b2b0be48b9ba8840069e6998f55 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Mon, 8 Sep 2025 10:05:10 +0100 Subject: [PATCH 53/60] Update comments on tests --- internal/command/init_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/command/init_test.go b/internal/command/init_test.go index 8e5e3c32dbbe..d9ce8d9c748d 100644 --- a/internal/command/init_test.go +++ b/internal/command/init_test.go @@ -3261,7 +3261,7 @@ func TestInit_stateStoreBlockIsExperimental(t *testing.T) { } } -// Testing init's behaviors when run in an empty working directory +// Testing init's behaviors when run in an empty working directory. func TestInit_stateStore_newWorkingDir(t *testing.T) { t.Run("the init command creates a backend state file, and creates the default workspace by default", func(t *testing.T) { // Create a temporary, uninitialized working directory with configuration including a state store @@ -3402,6 +3402,8 @@ func TestInit_stateStore_newWorkingDir(t *testing.T) { // > "during a non-init command, the command ends in with an error telling the user to run an init command" } +// Testing init's behaviors when run in a working directory where the configuration +// doesn't match the backend state file. func TestInit_stateStore_configChanges(t *testing.T) { t.Run("the -reconfigure flag makes Terraform ignore the backend state file during initialization", func(t *testing.T) { // Create a temporary working directory with state store configuration From f8c3dbbda7078e91f2a68b63fb329b406faab4a3 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Mon, 15 Sep 2025 17:12:34 +0100 Subject: [PATCH 54/60] Fix diagnostic using an error unnecessarily --- internal/command/meta_backend.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/command/meta_backend.go b/internal/command/meta_backend.go index 9a2be3c153f4..24856bcf2813 100644 --- a/internal/command/meta_backend.go +++ b/internal/command/meta_backend.go @@ -1610,11 +1610,10 @@ func (m *Meta) stateStore_C_s(c *configs.StateStore, stateStoreHash int, provide } else { pLock := opts.Locks.Provider(c.ProviderAddr) if pLock == nil { - diags = diags.Append(fmt.Errorf("The provider %s (%q) is not present in the lockfile, despite being used for state store %q. This is a bug in Terraform and should be reported: %w", + diags = diags.Append(fmt.Errorf("The provider %s (%q) is not present in the lockfile, despite being used for state store %q. This is a bug in Terraform and should be reported.", c.Provider.Name, c.ProviderAddr, - c.Type, - err)) + c.Type)) return nil, diags } var err error From d439604ffdaa8fff4e56ba9775314293a7963075 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Mon, 15 Sep 2025 20:34:48 +0100 Subject: [PATCH 55/60] Fix - make sure all errors are handled, and avoid nil error causing a panic --- internal/command/meta_backend.go | 77 +++++++++++++++++--------------- 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/internal/command/meta_backend.go b/internal/command/meta_backend.go index 24856bcf2813..2ff44b7612a1 100644 --- a/internal/command/meta_backend.go +++ b/internal/command/meta_backend.go @@ -1646,45 +1646,50 @@ func (m *Meta) stateStore_C_s(c *configs.StateStore, stateStoreHash int, provide // Verify that selected workspace exists in the state store. if opts.Init && b != nil { err := m.selectWorkspace(b) - if strings.Contains(err.Error(), "No existing workspaces") { - // If there are no workspaces, Terraform either needs to create the default workspace here, - // or instruct the user to run a `terraform workspace new` command. - ws, err := m.Workspace() - if err != nil { - diags = diags.Append(fmt.Errorf("Failed to check current workspace: %w", err)) - return nil, diags - } + if err != nil { + if strings.Contains(err.Error(), "No existing workspaces") { + // If there are no workspaces, Terraform either needs to create the default workspace here, + // or instruct the user to run a `terraform workspace new` command. + ws, err := m.Workspace() + if err != nil { + diags = diags.Append(fmt.Errorf("Failed to check current workspace: %w", err)) + return nil, diags + } - switch { - case ws != backend.DefaultStateName: - // User needs to run a `terraform workspace new` command. - diags = append(diags, tfdiags.Sourceless( - tfdiags.Error, - fmt.Sprintf("Workspace %q has not been created yet", ws), - fmt.Sprintf("State store %q in provider %s (%q) reports that no workspaces currently exist. To create the custom workspace %q use the command `terraform workspace new %s`.", - c.Type, - c.Provider.Name, - c.ProviderAddr, - ws, - ws, - ), - )) - return nil, diags + switch { + case ws != backend.DefaultStateName: + // User needs to run a `terraform workspace new` command. + diags = append(diags, tfdiags.Sourceless( + tfdiags.Error, + fmt.Sprintf("Workspace %q has not been created yet", ws), + fmt.Sprintf("State store %q in provider %s (%q) reports that no workspaces currently exist. To create the custom workspace %q use the command `terraform workspace new %s`.", + c.Type, + c.Provider.Name, + c.ProviderAddr, + ws, + ws, + ), + )) + return nil, diags - case ws == backend.DefaultStateName: - // Users control if the default workspace is created through the -create-default-workspace flag (defaults to true) - if opts.CreateDefaultWorkspace { - diags = diags.Append(m.createDefaultWorkspace(c, b)) - } else { - diags = diags.Append(&hcl.Diagnostic{ - Severity: hcl.DiagWarning, - Summary: "The default workspace does not exist", - Detail: "Terraform has been configured to skip creation of the default workspace in the state store. To create it, either run an 'init' command without `-create-default-workspace=true`, or create it using a 'workspace new' command", - }) + case ws == backend.DefaultStateName: + // Users control if the default workspace is created through the -create-default-workspace flag (defaults to true) + if opts.CreateDefaultWorkspace { + diags = diags.Append(m.createDefaultWorkspace(c, b)) + } else { + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagWarning, + Summary: "The default workspace does not exist", + Detail: "Terraform has been configured to skip creation of the default workspace in the state store. To create it, either run an 'init' command without `-create-default-workspace=true`, or create it using a 'workspace new' command", + }) + } + default: + diags = diags.Append(err) + return nil, diags } - default: - diags = diags.Append(err) - return nil, diags + } else { + // For all other errors, report via diagnostics + diags = diags.Append(fmt.Errorf("Failed to select a workspace: %w", err)) } } } From e84e15bb51d1e9e9ca2a73a92be5fbe4d77e4b13 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Mon, 15 Sep 2025 20:35:40 +0100 Subject: [PATCH 56/60] Allow init commands to succeed if using PSS and a reattached provider. --- internal/command/meta_backend.go | 84 +++++++++++++++++++++++++------- 1 file changed, 67 insertions(+), 17 deletions(-) diff --git a/internal/command/meta_backend.go b/internal/command/meta_backend.go index 2ff44b7612a1..6d92f4759061 100644 --- a/internal/command/meta_backend.go +++ b/internal/command/meta_backend.go @@ -14,6 +14,7 @@ import ( "fmt" "log" "maps" + "os" "path/filepath" "slices" "strconv" @@ -1600,32 +1601,58 @@ func (m *Meta) stateStore_C_s(c *configs.StateStore, stateStoreHash int, provide // If we're handling the builtin "terraform" provider then there's no version information to store in the dependency lock file, so don't access it. // We must record a value into the backend state file, and we cannot include a value that changes (e.g. the Terraform core binary version) as migration // is impossible with builtin providers. - // So, we use a hardcoded version number of 42. - pVersion, err = version.NewVersion("0.42.0") + // So, we use an arbitrary stand-in version. + standInVersion, err := version.NewVersion("0.0.1") if err != nil { - diags = diags.Append(fmt.Errorf("Error when creating a backend state file containing a builtin provider. This is a bug in Terraform and should be reported: %w", + diags = diags.Append(fmt.Errorf("Error when creating a backend state file. This is a bug in Terraform and should be reported: %w", err)) return nil, diags } + pVersion = standInVersion } else { - pLock := opts.Locks.Provider(c.ProviderAddr) - if pLock == nil { - diags = diags.Append(fmt.Errorf("The provider %s (%q) is not present in the lockfile, despite being used for state store %q. This is a bug in Terraform and should be reported.", - c.Provider.Name, - c.ProviderAddr, - c.Type)) - return nil, diags - } - var err error - pVersion, err = providerreqs.GoVersionFromVersion(pLock.Version()) + isReattached, err := isProviderReattached(c.ProviderAddr) if err != nil { - diags = diags.Append(fmt.Errorf("Failed obtain the in-use version of provider %s (%q) when recording backend state for state store %q. This is a bug in Terraform and should be reported: %w", - c.Provider.Name, - c.ProviderAddr, - c.Type, + diags = diags.Append(fmt.Errorf("Error determining if the state storage provider is reattached or not. This is a bug in Terraform and should be reported: %w", err)) return nil, diags } + if isReattached { + // If the provider is unmanaged then it won't be in the locks. + // If there are no locks then there's no version information to for us to access and use when creating the backend state file. + // So, we use an arbitrary stand-in version. + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagWarning, + Summary: "State storage provider is not managed by Terraform", + Detail: "Terraform is using a provider supplied via TF_REATTACH_PROVIDERS for initializing state storage. This will affect Terraform's ability to detect when state migrations are required.", + }) + standInVersion, err := version.NewVersion("0.0.1") + if err != nil { + diags = diags.Append(fmt.Errorf("Error when creating a backend state file. This is a bug in Terraform and should be reported: %w", + err)) + return nil, diags + } + pVersion = standInVersion + } else { + // The provider is not built in and is being managed by Terraform + // This is the most common scenario, by far. + pLock := opts.Locks.Provider(c.ProviderAddr) + if pLock == nil { + diags = diags.Append(fmt.Errorf("The provider %s (%q) is not present in the lockfile, despite being used for state store %q. This is a bug in Terraform and should be reported.", + c.Provider.Name, + c.ProviderAddr, + c.Type)) + return nil, diags + } + pVersion, err = providerreqs.GoVersionFromVersion(pLock.Version()) + if err != nil { + diags = diags.Append(fmt.Errorf("Failed obtain the in-use version of provider %s (%q) when recording backend state for state store %q. This is a bug in Terraform and should be reported: %w", + c.Provider.Name, + c.ProviderAddr, + c.Type, + err)) + return nil, diags + } + } } s.StateStore = &workdir.StateStoreConfigState{ Type: c.Type, @@ -1710,6 +1737,29 @@ func (m *Meta) stateStore_C_s(c *configs.StateStore, stateStoreHash int, provide return b, diags } +// isProviderReattached determines if a given provider is being supplied to Terraform via the TF_REATTACH_PROVIDERS +// environment variable. +func isProviderReattached(provider addrs.Provider) (bool, error) { + in := os.Getenv("TF_REATTACH_PROVIDERS") + if in != "" { + var m map[string]any + err := json.Unmarshal([]byte(in), &m) + if err != nil { + return false, fmt.Errorf("Invalid format for TF_REATTACH_PROVIDERS: %w", err) + } + for p, _ := range m { + a, diags := addrs.ParseProviderSourceString(p) + if diags.HasErrors() { + return false, fmt.Errorf("Error parsing %q as a provider address: %w", a, diags.Err()) + } + if a.Equals(provider) { + return true, nil + } + } + } + return false, nil +} + // createDefaultWorkspace receives a backend made using a pluggable state store, and details about that store's config, // and persists an empty state file in the default workspace. By creating this artifact we ensure that the default // workspace is created and usable by Terraform in later operations. From 9b7a29243514747cd7ea0337981070be0f0afd4b Mon Sep 17 00:00:00 2001 From: Sarah French Date: Tue, 16 Sep 2025 12:17:36 +0100 Subject: [PATCH 57/60] Fix: Pass state bytes into WriteStateBytesRequest Otherwise the provider never receives any state data to write! --- internal/states/remote/remote_grpc.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/states/remote/remote_grpc.go b/internal/states/remote/remote_grpc.go index 5d762f8ba53b..448b030d9ce7 100644 --- a/internal/states/remote/remote_grpc.go +++ b/internal/states/remote/remote_grpc.go @@ -81,6 +81,7 @@ func (g *grpcClient) Put(state []byte) error { req := providers.WriteStateBytesRequest{ TypeName: g.typeName, StateId: g.stateId, + Bytes: state, } resp := g.provider.WriteStateBytes(req) From 1a72b50e90b014f0de537949e5faa5b8dd932cce Mon Sep 17 00:00:00 2001 From: Sarah French Date: Wed, 17 Sep 2025 10:18:28 +0100 Subject: [PATCH 58/60] Don't try to access chunk when processing an EOF --- internal/plugin6/grpc_provider.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/plugin6/grpc_provider.go b/internal/plugin6/grpc_provider.go index 8c54a9260817..71e2c62310b5 100644 --- a/internal/plugin6/grpc_provider.go +++ b/internal/plugin6/grpc_provider.go @@ -1515,8 +1515,8 @@ func (p *GRPCProvider) ReadStateBytes(r providers.ReadStateBytesRequest) (resp p for { chunk, err := client.Recv() if err == io.EOF { - // End of stream, we're done - resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(chunk.Diagnostics)) + // No chunk is returned alongside an EOF. + // And as EOF is a sentinel error it isn't wrapped as a diagnostic. break } if err != nil { From cae76197daac35d0172f9c3356fbb08751dbdbd1 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Wed, 17 Sep 2025 10:19:49 +0100 Subject: [PATCH 59/60] Add more logging in ReadStateBytes, make sure stream is closed by core after all state is received. --- internal/plugin6/grpc_provider.go | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/internal/plugin6/grpc_provider.go b/internal/plugin6/grpc_provider.go index 71e2c62310b5..61ca88d8863c 100644 --- a/internal/plugin6/grpc_provider.go +++ b/internal/plugin6/grpc_provider.go @@ -1542,13 +1542,29 @@ func (p *GRPCProvider) ReadStateBytes(r providers.ReadStateBytesRequest) (resp p logger.Trace("GRPCProvider.v6: ReadStateBytes: read bytes of a chunk", n) } - logger.Trace("GRPCProvider.v6: ReadStateBytes: received all chunks", buf.Len()) + if resp.Diagnostics.HasErrors() { + logger.Trace("GRPCProvider.v6: ReadStateBytes: experienced an error when receiving state data from the provider", resp.Diagnostics.Err()) + return resp + } + if buf.Len() != expectedTotalLength { + logger.Trace("GRPCProvider.v6: ReadStateBytes: received %d bytes but expected the total bytes to be %d.", buf.Len(), expectedTotalLength) + err = fmt.Errorf("expected state file of total %d bytes, received %d bytes", expectedTotalLength, buf.Len()) resp.Diagnostics = resp.Diagnostics.Append(err) return resp } + + // We're done, so close the stream + logger.Trace("GRPCProvider.v6: ReadStateBytes: received all chunks, total bytes: ", buf.Len()) + err = client.CloseSend() + if err != nil { + resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err)) + return resp + } + + // Add the state data in the response once we know there are no errors resp.Bytes = buf.Bytes() return resp From 0e4fb67b5f05eed78c7ae4e63f44ae9f38f29547 Mon Sep 17 00:00:00 2001 From: Sarah French Date: Fri, 19 Sep 2025 12:41:52 +0100 Subject: [PATCH 60/60] Make error message more specific --- internal/command/meta_backend.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/command/meta_backend.go b/internal/command/meta_backend.go index 6d92f4759061..baa7bbc4f896 100644 --- a/internal/command/meta_backend.go +++ b/internal/command/meta_backend.go @@ -692,7 +692,7 @@ func (m *Meta) backendFromConfig(opts *BackendOpts) (backend.Backend, tfdiags.Di statePath := filepath.Join(m.DataDir(), DefaultStateFilename) sMgr := &clistate.LocalState{Path: statePath} if err := sMgr.RefreshState(); err != nil { - diags = diags.Append(fmt.Errorf("Failed to load state: %s", err)) + diags = diags.Append(fmt.Errorf("Failed to load the backend state file: %s", err)) return nil, diags }