Update Plugin Protocol for List and Implement `ValidateListResourceConfig` (#37007)

* Minor auto-formatting changes

* Add list RPC and schema protobuf definitions

* make protobuf

* make generate

* Add ValidateListResourceConfig implementation
pull/37086/head
Daniel Banck 9 months ago committed by GitHub
parent 64dfd572e8
commit 44ae7da18e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -1,14 +1,14 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
// Terraform Plugin RPC protocol version 5.9
// Terraform Plugin RPC protocol version 5.10
//
// This file defines version 5.9 of the RPC protocol. To implement a plugin
// This file defines version 5.10 of the RPC protocol. To implement a plugin
// against this protocol, copy this definition into your own codebase and
// use protoc to generate stubs for your target language.
//
// Any minor versions of protocol 5 to follow should modify this file while
// maintaining backwards compatibility. Breaking changes, if any are required,
// maintaining backwards compatibility. Breaking changes, if any are required,
// will come in a subsequent major version with its own separate proto definition.
//
// Note that only the proto files included in a release tag of Terraform are
@ -287,24 +287,23 @@ message Function {
// Deferred is a message that indicates that change is deferred for a reason.
message Deferred {
// Reason is the reason for deferring the change.
enum Reason {
// UNKNOWN is the default value, and should not be used.
UNKNOWN = 0;
// RESOURCE_CONFIG_UNKNOWN is used when the config is partially unknown and the real
// values need to be known before the change can be planned.
RESOURCE_CONFIG_UNKNOWN = 1;
// PROVIDER_CONFIG_UNKNOWN is used when parts of the provider configuration
// are unknown, e.g. the provider configuration is only known after the apply is done.
PROVIDER_CONFIG_UNKNOWN = 2;
// ABSENT_PREREQ is used when a hard dependency has not been satisfied.
ABSENT_PREREQ = 3;
}
enum Reason {
// UNKNOWN is the default value, and should not be used.
UNKNOWN = 0;
// RESOURCE_CONFIG_UNKNOWN is used when the config is partially unknown and the real
// values need to be known before the change can be planned.
RESOURCE_CONFIG_UNKNOWN = 1;
// PROVIDER_CONFIG_UNKNOWN is used when parts of the provider configuration
// are unknown, e.g. the provider configuration is only known after the apply is done.
PROVIDER_CONFIG_UNKNOWN = 2;
// ABSENT_PREREQ is used when a hard dependency has not been satisfied.
ABSENT_PREREQ = 3;
}
// reason is the reason for deferring the change.
Reason reason = 1;
// reason is the reason for deferring the change.
Reason reason = 1;
}
service Provider {
//////// Information about what a provider supports/expects
@ -347,6 +346,10 @@ service Provider {
rpc RenewEphemeralResource(RenewEphemeralResource.Request) returns (RenewEphemeralResource.Response);
rpc CloseEphemeralResource(CloseEphemeralResource.Request) returns (CloseEphemeralResource.Response);
/////// List
rpc ListResource(ListResource.Request) returns (stream ListResource.Event);
rpc ValidateListResourceConfig(ValidateListResourceConfig.Request) returns (ValidateListResourceConfig.Response);
// GetFunctions returns the definitions of all functions.
rpc GetFunctions(GetFunctions.Request) returns (GetFunctions.Response);
@ -369,6 +372,7 @@ message GetMetadata {
// functions returns metadata for any functions.
repeated FunctionMetadata functions = 5;
repeated EphemeralMetadata ephemeral_resources = 6;
repeated ListResourceMetadata list_resources = 7;
}
message EphemeralMetadata {
@ -387,6 +391,10 @@ message GetMetadata {
message ResourceMetadata {
string type_name = 1;
}
message ListResourceMetadata {
string type_name = 1;
}
}
message GetProviderSchema {
@ -398,6 +406,7 @@ message GetProviderSchema {
map<string, Schema> data_source_schemas = 3;
map<string, Function> functions = 7;
map<string, Schema> ephemeral_resource_schemas = 8;
map<string, Schema> list_resource_schemas = 9;
repeated Diagnostic diagnostics = 4;
Schema provider_meta = 5;
ServerCapabilities server_capabilities = 6;
@ -575,7 +584,6 @@ message PlanResourceChange {
bytes planned_private = 3;
repeated Diagnostic diagnostics = 4;
// This may be set only by the helper/schema "SDK" in the main Terraform
// repository, to request that Terraform Core >=0.12 permit additional
// inconsistencies that can result from the legacy SDK type system
@ -747,7 +755,7 @@ message ProvisionResource {
DynamicValue connection = 2;
}
message Response {
string output = 1;
string output = 1;
repeated Diagnostic diagnostics = 2;
}
}
@ -811,3 +819,41 @@ message CallFunction {
FunctionError error = 2;
}
}
message ListResource {
message Request {
// type_name is the list resource type name.
string type_name = 1;
// configuration is the list ConfigSchema-based configuration data.
DynamicValue config = 2;
// when include_resource_object is set to true, the provider should
// include the full resource object for each result
bool include_resource_object = 3;
}
message Event {
// identity is the resource identity data of the resource instance.
ResourceIdentityData identity = 1;
// display_name can be displayed in a UI to make it easier for humans to identify a resource
string display_name = 2;
// optional resource object which can be useful when combining list blocks in configuration
optional DynamicValue resource_object = 3;
// A warning or error diagnostics for this event
repeated Diagnostic diagnostic = 4;
}
}
message ValidateListResourceConfig {
message Request {
string type_name = 1;
DynamicValue config = 2;
}
message Response {
repeated Diagnostic diagnostics = 1;
}
}

@ -1,14 +1,14 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
// Terraform Plugin RPC protocol version 6.9
// Terraform Plugin RPC protocol version 6.10
//
// This file defines version 6.9 of the RPC protocol. To implement a plugin
// This file defines version 6.10 of the RPC protocol. To implement a plugin
// against this protocol, copy this definition into your own codebase and
// use protoc to generate stubs for your target language.
//
// Any minor versions of protocol 6 to follow should modify this file while
// maintaining backwards compatibility. Breaking changes, if any are required,
// maintaining backwards compatibility. Breaking changes, if any are required,
// will come in a subsequent major version with its own separate proto definition.
//
// Note that only the proto files included in a release tag of Terraform are
@ -200,8 +200,8 @@ message Schema {
// MinItems and MaxItems were never used in the protocol, and have no
// effect on validation.
int64 min_items = 4 [deprecated = true];
int64 max_items = 5 [deprecated = true];
int64 min_items = 4 [ deprecated = true ];
int64 max_items = 5 [ deprecated = true ];
}
// The version of the schema.
@ -306,21 +306,21 @@ message ClientCapabilities {
// Deferred is a message that indicates that change is deferred for a reason.
message Deferred {
// Reason is the reason for deferring the change.
enum Reason {
// UNKNOWN is the default value, and should not be used.
UNKNOWN = 0;
// RESOURCE_CONFIG_UNKNOWN is used when the config is partially unknown and the real
// values need to be known before the change can be planned.
RESOURCE_CONFIG_UNKNOWN = 1;
// PROVIDER_CONFIG_UNKNOWN is used when parts of the provider configuration
// are unknown, e.g. the provider configuration is only known after the apply is done.
PROVIDER_CONFIG_UNKNOWN = 2;
// ABSENT_PREREQ is used when a hard dependency has not been satisfied.
ABSENT_PREREQ = 3;
}
// reason is the reason for deferring the change.
Reason reason = 1;
enum Reason {
// UNKNOWN is the default value, and should not be used.
UNKNOWN = 0;
// RESOURCE_CONFIG_UNKNOWN is used when the config is partially unknown and the real
// values need to be known before the change can be planned.
RESOURCE_CONFIG_UNKNOWN = 1;
// PROVIDER_CONFIG_UNKNOWN is used when parts of the provider configuration
// are unknown, e.g. the provider configuration is only known after the apply is done.
PROVIDER_CONFIG_UNKNOWN = 2;
// ABSENT_PREREQ is used when a hard dependency has not been satisfied.
ABSENT_PREREQ = 3;
}
// reason is the reason for deferring the change.
Reason reason = 1;
}
service Provider {
@ -365,6 +365,10 @@ service Provider {
rpc RenewEphemeralResource(RenewEphemeralResource.Request) returns (RenewEphemeralResource.Response);
rpc CloseEphemeralResource(CloseEphemeralResource.Request) returns (CloseEphemeralResource.Response);
/////// List
rpc ListResource(ListResource.Request) returns (stream ListResource.Event);
rpc ValidateListResourceConfig(ValidateListResourceConfig.Request) returns (ValidateListResourceConfig.Response);
// GetFunctions returns the definitions of all functions.
rpc GetFunctions(GetFunctions.Request) returns (GetFunctions.Response);
@ -387,6 +391,7 @@ message GetMetadata {
// functions returns metadata for any functions.
repeated FunctionMetadata functions = 5;
repeated EphemeralMetadata ephemeral_resources = 6;
repeated ListResourceMetadata list_resources = 7;
}
message EphemeralMetadata {
@ -405,6 +410,10 @@ message GetMetadata {
message ResourceMetadata {
string type_name = 1;
}
message ListResourceMetadata {
string type_name = 1;
}
}
message GetProviderSchema {
@ -416,6 +425,7 @@ message GetProviderSchema {
map<string, Schema> data_source_schemas = 3;
map<string, Function> functions = 7;
map<string, Schema> ephemeral_resource_schemas = 8;
map<string, Schema> list_resource_schemas = 9;
repeated Diagnostic diagnostics = 4;
Schema provider_meta = 5;
ServerCapabilities server_capabilities = 6;
@ -791,3 +801,41 @@ message CallFunction {
FunctionError error = 2;
}
}
message ListResource {
message Request {
// type_name is the list resource type name.
string type_name = 1;
// configuration is the list ConfigSchema-based configuration data.
DynamicValue config = 2;
// when include_resource_object is set to true, the provider should
// include the full resource object for each result
bool include_resource_object = 3;
}
message Event {
// identity is the resource identity data of the resource instance.
ResourceIdentityData identity = 1;
// display_name can be displayed in a UI to make it easier for humans to identify a resource
string display_name = 2;
// optional resource object which can be useful when combining list blocks in configuration
optional DynamicValue resource_object = 3;
// A warning or error diagnostics for this event
repeated Diagnostic diagnostic = 4;
}
}
message ValidateListResourceConfig {
message Request {
string type_name = 1;
DynamicValue config = 2;
}
message Response {
repeated Diagnostic diagnostics = 1;
}
}

@ -35,6 +35,7 @@ func (p *Provider) GetProviderSchema() providers.GetProviderSchemaResponse {
"terraform_data": dataStoreResourceSchema(),
},
EphemeralResourceTypes: map[string]providers.Schema{},
ListResourceTypes: map[string]providers.Schema{},
Functions: map[string]providers.FunctionDecl{
"encode_tfvars": {
Summary: "Produce a string representation of an object using the same syntax as for `.tfvars` files",
@ -113,6 +114,12 @@ func (p *Provider) ValidateDataResourceConfig(req providers.ValidateDataResource
return res
}
func (p *Provider) ValidateListResourceConfig(req providers.ValidateListResourceConfigRequest) providers.ValidateListResourceConfigResponse {
var resp providers.ValidateListResourceConfigResponse
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unsupported list resource type %q", req.TypeName))
return resp
}
// Configure configures and initializes the provider.
func (p *Provider) ConfigureProvider(providers.ConfigureProviderRequest) providers.ConfigureProviderResponse {
// At this moment there is nothing to configure for the terraform provider,

@ -45,6 +45,7 @@ func (p *provider) GetSchema(_ context.Context, req *tfplugin5.GetProviderSchema
ResourceSchemas: make(map[string]*tfplugin5.Schema),
DataSourceSchemas: make(map[string]*tfplugin5.Schema),
EphemeralResourceSchemas: make(map[string]*tfplugin5.Schema),
ListResourceSchemas: make(map[string]*tfplugin5.Schema),
}
resp.Provider = &tfplugin5.Schema{
@ -79,6 +80,12 @@ func (p *provider) GetSchema(_ context.Context, req *tfplugin5.GetProviderSchema
Block: convert.ConfigSchemaToProto(dat.Body),
}
}
for typ, dat := range p.schema.ListResourceTypes {
resp.ListResourceSchemas[typ] = &tfplugin5.Schema{
Version: int64(dat.Version),
Block: convert.ConfigSchemaToProto(dat.Body),
}
}
if decls, err := convert.FunctionDeclsToProto(p.schema.Functions); err == nil {
resp.Functions = decls
} else {
@ -178,6 +185,25 @@ func (p *provider) ValidateEphemeralResourceConfig(_ context.Context, req *tfplu
return resp, nil
}
func (p *provider) ValidateListResourceConfig(_ context.Context, req *tfplugin5.ValidateListResourceConfig_Request) (*tfplugin5.ValidateListResourceConfig_Response, error) {
resp := &tfplugin5.ValidateListResourceConfig_Response{}
ty := p.schema.ListResourceTypes[req.TypeName].Body.ImpliedType()
configVal, err := decodeDynamicValue(req.Config, ty)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
validateResp := p.provider.ValidateListResourceConfig(providers.ValidateListResourceConfigRequest{
TypeName: req.TypeName,
Config: configVal,
})
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, validateResp.Diagnostics)
return resp, nil
}
func (p *provider) UpgradeResourceState(_ context.Context, req *tfplugin5.UpgradeResourceState_Request) (*tfplugin5.UpgradeResourceState_Response, error) {
resp := &tfplugin5.UpgradeResourceState_Response{}
ty := p.schema.ResourceTypes[req.TypeName].Body.ImpliedType()
@ -736,6 +762,10 @@ func (p *provider) UpgradeResourceIdentity(_ context.Context, req *tfplugin5.Upg
return resp, nil
}
func (p *provider) ListResource(*tfplugin5.ListResource_Request, tfplugin5.Provider_ListResourceServer) error {
panic("not implemented")
}
func (p *provider) Stop(context.Context, *tfplugin5.Stop_Request) (*tfplugin5.Stop_Response, error) {
resp := &tfplugin5.Stop_Response{}
err := p.provider.Stop()

@ -47,6 +47,7 @@ func (p *provider6) GetProviderSchema(_ context.Context, req *tfplugin6.GetProvi
DataSourceSchemas: make(map[string]*tfplugin6.Schema),
EphemeralResourceSchemas: make(map[string]*tfplugin6.Schema),
Functions: make(map[string]*tfplugin6.Function),
ListResourceSchemas: make(map[string]*tfplugin6.Schema),
}
resp.Provider = &tfplugin6.Schema{
@ -81,6 +82,12 @@ func (p *provider6) GetProviderSchema(_ context.Context, req *tfplugin6.GetProvi
Block: convert.ConfigSchemaToProto(dat.Body),
}
}
for typ, dat := range p.schema.ListResourceTypes {
resp.ListResourceSchemas[typ] = &tfplugin6.Schema{
Version: int64(dat.Version),
Block: convert.ConfigSchemaToProto(dat.Body),
}
}
if decls, err := convert.FunctionDeclsToProto(p.schema.Functions); err == nil {
resp.Functions = decls
} else {
@ -159,7 +166,7 @@ func (p *provider6) ValidateDataResourceConfig(_ context.Context, req *tfplugin6
func (p *provider6) ValidateEphemeralResourceConfig(_ context.Context, req *tfplugin6.ValidateEphemeralResourceConfig_Request) (*tfplugin6.ValidateEphemeralResourceConfig_Response, error) {
resp := &tfplugin6.ValidateEphemeralResourceConfig_Response{}
ty := p.schema.DataSources[req.TypeName].Body.ImpliedType()
ty := p.schema.EphemeralResourceTypes[req.TypeName].Body.ImpliedType()
configVal, err := decodeDynamicValue6(req.Config, ty)
if err != nil {
@ -176,6 +183,25 @@ func (p *provider6) ValidateEphemeralResourceConfig(_ context.Context, req *tfpl
return resp, nil
}
func (p *provider6) ValidateListResourceConfig(_ context.Context, req *tfplugin6.ValidateListResourceConfig_Request) (*tfplugin6.ValidateListResourceConfig_Response, error) {
resp := &tfplugin6.ValidateListResourceConfig_Response{}
ty := p.schema.ListResourceTypes[req.TypeName].Body.ImpliedType()
configVal, err := decodeDynamicValue6(req.Config, ty)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
validateResp := p.provider.ValidateListResourceConfig(providers.ValidateListResourceConfigRequest{
TypeName: req.TypeName,
Config: configVal,
})
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, validateResp.Diagnostics)
return resp, nil
}
func (p *provider6) UpgradeResourceState(_ context.Context, req *tfplugin6.UpgradeResourceState_Request) (*tfplugin6.UpgradeResourceState_Response, error) {
resp := &tfplugin6.UpgradeResourceState_Response{}
ty := p.schema.ResourceTypes[req.TypeName].Body.ImpliedType()
@ -784,6 +810,10 @@ func (p *provider6) UpgradeResourceIdentity(_ context.Context, req *tfplugin6.Up
return resp, nil
}
func (p *provider6) ListResource(*tfplugin6.ListResource_Request, tfplugin6.Provider_ListResourceServer) error {
panic("not implemented")
}
func (p *provider6) StopProvider(context.Context, *tfplugin6.StopProvider_Request) (*tfplugin6.StopProvider_Response, error) {
resp := &tfplugin6.StopProvider_Response{}
err := p.provider.Stop()

@ -101,6 +101,7 @@ func (p *GRPCProvider) GetProviderSchema() providers.GetProviderSchemaResponse {
resp.ResourceTypes = make(map[string]providers.Schema)
resp.DataSources = make(map[string]providers.Schema)
resp.EphemeralResourceTypes = make(map[string]providers.Schema)
resp.ListResourceTypes = make(map[string]providers.Schema)
// Some providers may generate quite large schemas, and the internal default
// grpc response size limit is 4MB. 64MB should cover most any use case, and
@ -167,6 +168,10 @@ func (p *GRPCProvider) GetProviderSchema() providers.GetProviderSchemaResponse {
resp.EphemeralResourceTypes[name] = convert.ProtoToProviderSchema(ephem, nil)
}
for name, list := range protoResp.ListResourceSchemas {
resp.ListResourceTypes[name] = convert.ProtoToProviderSchema(list, nil)
}
if decls, err := convert.FunctionDeclsFromProto(protoResp.Functions); err == nil {
resp.Functions = decls
} else {
@ -336,6 +341,42 @@ func (p *GRPCProvider) ValidateDataResourceConfig(r providers.ValidateDataResour
return resp
}
func (p *GRPCProvider) ValidateListResourceConfig(r providers.ValidateListResourceConfigRequest) (resp providers.ValidateListResourceConfigResponse) {
logger.Trace("GRPCProvider: ValidateListResourceConfig")
schema := p.GetProviderSchema()
if schema.Diagnostics.HasErrors() {
resp.Diagnostics = schema.Diagnostics
return resp
}
listResourceSchema, ok := schema.ListResourceTypes[r.TypeName]
if !ok {
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unknown list resource type %q", r.TypeName))
return resp
}
mp, err := msgpack.Marshal(r.Config, listResourceSchema.Body.ImpliedType())
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(err)
return resp
}
protoReq := &proto.ValidateListResourceConfig_Request{
TypeName: r.TypeName,
Config: &proto.DynamicValue{Msgpack: mp},
}
protoResp, err := p.client.ValidateListResourceConfig(p.ctx, protoReq)
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err))
return resp
}
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
return resp
}
func (p *GRPCProvider) UpgradeResourceState(r providers.UpgradeResourceStateRequest) (resp providers.UpgradeResourceStateResponse) {
logger.Trace("GRPCProvider: UpgradeResourceState")
@ -1210,6 +1251,10 @@ func (p *GRPCProvider) CallFunction(r providers.CallFunctionRequest) (resp provi
return resp
}
func (p *GRPCProvider) ListResource(r providers.ListResourceRequest) error {
panic("not implemented")
}
// closing the grpc connection is final, and terraform will call it at the end of every phase.
func (p *GRPCProvider) Close() error {
logger.Trace("GRPCProvider: Close")

@ -117,6 +117,20 @@ func providerProtoSchema() *proto.GetProviderSchema_Response {
},
},
},
ListResourceSchemas: map[string]*proto.Schema{
"list": &proto.Schema{
Version: 1,
Block: &proto.Schema_Block{
Attributes: []*proto.Schema_Attribute{
{
Name: "attr",
Type: []byte(`"string"`),
Required: true,
},
},
},
},
},
ServerCapabilities: &proto.ServerCapabilities{
GetProviderSchemaOptional: true,
},
@ -398,6 +412,25 @@ func TestGRPCProvider_ValidateDataSourceConfig(t *testing.T) {
checkDiags(t, resp.Diagnostics)
}
func TestGRPCProvider_ValidateListResourceConfig(t *testing.T) {
client := mockProviderClient(t)
p := &GRPCProvider{
client: client,
}
client.EXPECT().ValidateListResourceConfig(
gomock.Any(),
gomock.Any(),
).Return(&proto.ValidateListResourceConfig_Response{}, nil)
cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{"attr": "value"})
resp := p.ValidateListResourceConfig(providers.ValidateListResourceConfigRequest{
TypeName: "list",
Config: cfg,
})
checkDiags(t, resp.Diagnostics)
}
func TestGRPCProvider_UpgradeResourceState(t *testing.T) {
client := mockProviderClient(t)
p := &GRPCProvider{

@ -222,6 +222,26 @@ func (mr *MockProviderClientMockRecorder) ImportResourceState(arg0, arg1 any, ar
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ImportResourceState", reflect.TypeOf((*MockProviderClient)(nil).ImportResourceState), varargs...)
}
// ListResource mocks base method.
func (m *MockProviderClient) ListResource(arg0 context.Context, arg1 *tfplugin5.ListResource_Request, arg2 ...grpc.CallOption) (tfplugin5.Provider_ListResourceClient, error) {
m.ctrl.T.Helper()
varargs := []any{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "ListResource", varargs...)
ret0, _ := ret[0].(tfplugin5.Provider_ListResourceClient)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ListResource indicates an expected call of ListResource.
func (mr *MockProviderClientMockRecorder) ListResource(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, "ListResource", reflect.TypeOf((*MockProviderClient)(nil).ListResource), varargs...)
}
// MoveResourceState mocks base method.
func (m *MockProviderClient) MoveResourceState(arg0 context.Context, arg1 *tfplugin5.MoveResourceState_Request, arg2 ...grpc.CallOption) (*tfplugin5.MoveResourceState_Response, error) {
m.ctrl.T.Helper()
@ -462,6 +482,26 @@ func (mr *MockProviderClientMockRecorder) ValidateEphemeralResourceConfig(arg0,
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateEphemeralResourceConfig", reflect.TypeOf((*MockProviderClient)(nil).ValidateEphemeralResourceConfig), varargs...)
}
// ValidateListResourceConfig mocks base method.
func (m *MockProviderClient) ValidateListResourceConfig(arg0 context.Context, arg1 *tfplugin5.ValidateListResourceConfig_Request, arg2 ...grpc.CallOption) (*tfplugin5.ValidateListResourceConfig_Response, error) {
m.ctrl.T.Helper()
varargs := []any{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "ValidateListResourceConfig", varargs...)
ret0, _ := ret[0].(*tfplugin5.ValidateListResourceConfig_Response)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ValidateListResourceConfig indicates an expected call of ValidateListResourceConfig.
func (mr *MockProviderClientMockRecorder) ValidateListResourceConfig(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, "ValidateListResourceConfig", reflect.TypeOf((*MockProviderClient)(nil).ValidateListResourceConfig), varargs...)
}
// ValidateResourceTypeConfig mocks base method.
func (m *MockProviderClient) ValidateResourceTypeConfig(arg0 context.Context, arg1 *tfplugin5.ValidateResourceTypeConfig_Request, arg2 ...grpc.CallOption) (*tfplugin5.ValidateResourceTypeConfig_Response, error) {
m.ctrl.T.Helper()

@ -101,6 +101,7 @@ func (p *GRPCProvider) GetProviderSchema() providers.GetProviderSchemaResponse {
resp.ResourceTypes = make(map[string]providers.Schema)
resp.DataSources = make(map[string]providers.Schema)
resp.EphemeralResourceTypes = make(map[string]providers.Schema)
resp.ListResourceTypes = make(map[string]providers.Schema)
// Some providers may generate quite large schemas, and the internal default
// grpc response size limit is 4MB. 64MB should cover most any use case, and
@ -167,6 +168,10 @@ func (p *GRPCProvider) GetProviderSchema() providers.GetProviderSchemaResponse {
resp.EphemeralResourceTypes[name] = convert.ProtoToProviderSchema(ephem, nil)
}
for name, list := range protoResp.ListResourceSchemas {
resp.ListResourceTypes[name] = convert.ProtoToProviderSchema(list, nil)
}
if decls, err := convert.FunctionDeclsFromProto(protoResp.Functions); err == nil {
resp.Functions = decls
} else {
@ -329,6 +334,42 @@ func (p *GRPCProvider) ValidateDataResourceConfig(r providers.ValidateDataResour
return resp
}
func (p *GRPCProvider) ValidateListResourceConfig(r providers.ValidateListResourceConfigRequest) (resp providers.ValidateListResourceConfigResponse) {
logger.Trace("GRPCProvider.v6: ValidateListResourceConfig")
schema := p.GetProviderSchema()
if schema.Diagnostics.HasErrors() {
resp.Diagnostics = schema.Diagnostics
return resp
}
listResourceSchema, ok := schema.ListResourceTypes[r.TypeName]
if !ok {
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unknown list resource type %q", r.TypeName))
return resp
}
mp, err := msgpack.Marshal(r.Config, listResourceSchema.Body.ImpliedType())
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(err)
return resp
}
protoReq := &proto6.ValidateListResourceConfig_Request{
TypeName: r.TypeName,
Config: &proto6.DynamicValue{Msgpack: mp},
}
protoResp, err := p.client.ValidateListResourceConfig(p.ctx, protoReq)
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err))
return resp
}
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
return resp
}
func (p *GRPCProvider) UpgradeResourceState(r providers.UpgradeResourceStateRequest) (resp providers.UpgradeResourceStateResponse) {
logger.Trace("GRPCProvider.v6: UpgradeResourceState")
@ -1201,6 +1242,10 @@ func (p *GRPCProvider) CallFunction(r providers.CallFunctionRequest) (resp provi
return resp
}
func (p *GRPCProvider) ListResource(r providers.ListResourceRequest) error {
panic("not implemented")
}
// closing the grpc connection is final, and terraform will call it at the end of every phase.
func (p *GRPCProvider) Close() error {
logger.Trace("GRPCProvider.v6: Close")

@ -124,6 +124,20 @@ func providerProtoSchema() *proto.GetProviderSchema_Response {
},
},
},
ListResourceSchemas: map[string]*proto.Schema{
"list": &proto.Schema{
Version: 1,
Block: &proto.Schema_Block{
Attributes: []*proto.Schema_Attribute{
{
Name: "attr",
Type: []byte(`"string"`),
Required: true,
},
},
},
},
},
ServerCapabilities: &proto.ServerCapabilities{
GetProviderSchemaOptional: true,
},
@ -405,6 +419,25 @@ func TestGRPCProvider_ValidateDataResourceConfig(t *testing.T) {
checkDiags(t, resp.Diagnostics)
}
func TestGRPCProvider_ValidateListResourceConfig(t *testing.T) {
client := mockProviderClient(t)
p := &GRPCProvider{
client: client,
}
client.EXPECT().ValidateListResourceConfig(
gomock.Any(),
gomock.Any(),
).Return(&proto.ValidateListResourceConfig_Response{}, nil)
cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{"attr": "value"})
resp := p.ValidateListResourceConfig(providers.ValidateListResourceConfigRequest{
TypeName: "list",
Config: cfg,
})
checkDiags(t, resp.Diagnostics)
}
func TestGRPCProvider_UpgradeResourceState(t *testing.T) {
client := mockProviderClient(t)
p := &GRPCProvider{

@ -221,6 +221,26 @@ func (mr *MockProviderClientMockRecorder) ImportResourceState(arg0, arg1 any, ar
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ImportResourceState", reflect.TypeOf((*MockProviderClient)(nil).ImportResourceState), varargs...)
}
// ListResource mocks base method.
func (m *MockProviderClient) ListResource(arg0 context.Context, arg1 *tfplugin6.ListResource_Request, arg2 ...grpc.CallOption) (tfplugin6.Provider_ListResourceClient, error) {
m.ctrl.T.Helper()
varargs := []any{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "ListResource", varargs...)
ret0, _ := ret[0].(tfplugin6.Provider_ListResourceClient)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ListResource indicates an expected call of ListResource.
func (mr *MockProviderClientMockRecorder) ListResource(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, "ListResource", reflect.TypeOf((*MockProviderClient)(nil).ListResource), varargs...)
}
// MoveResourceState mocks base method.
func (m *MockProviderClient) MoveResourceState(arg0 context.Context, arg1 *tfplugin6.MoveResourceState_Request, arg2 ...grpc.CallOption) (*tfplugin6.MoveResourceState_Response, error) {
m.ctrl.T.Helper()
@ -441,6 +461,26 @@ func (mr *MockProviderClientMockRecorder) ValidateEphemeralResourceConfig(arg0,
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidateEphemeralResourceConfig", reflect.TypeOf((*MockProviderClient)(nil).ValidateEphemeralResourceConfig), varargs...)
}
// ValidateListResourceConfig mocks base method.
func (m *MockProviderClient) ValidateListResourceConfig(arg0 context.Context, arg1 *tfplugin6.ValidateListResourceConfig_Request, arg2 ...grpc.CallOption) (*tfplugin6.ValidateListResourceConfig_Response, error) {
m.ctrl.T.Helper()
varargs := []any{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "ValidateListResourceConfig", varargs...)
ret0, _ := ret[0].(*tfplugin6.ValidateListResourceConfig_Response)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ValidateListResourceConfig indicates an expected call of ValidateListResourceConfig.
func (mr *MockProviderClientMockRecorder) ValidateListResourceConfig(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, "ValidateListResourceConfig", reflect.TypeOf((*MockProviderClient)(nil).ValidateListResourceConfig), varargs...)
}
// ValidateProviderConfig mocks base method.
func (m *MockProviderClient) ValidateProviderConfig(arg0 context.Context, arg1 *tfplugin6.ValidateProviderConfig_Request, arg2 ...grpc.CallOption) (*tfplugin6.ValidateProviderConfig_Response, error) {
m.ctrl.T.Helper()

@ -111,6 +111,10 @@ func (s simple) ValidateDataResourceConfig(req providers.ValidateDataResourceCon
return resp
}
func (s simple) ValidateListResourceConfig(req providers.ValidateListResourceConfigRequest) (resp providers.ValidateListResourceConfigResponse) {
return resp
}
func (p simple) UpgradeResourceState(req providers.UpgradeResourceStateRequest) (resp providers.UpgradeResourceStateResponse) {
ty := p.schema.ResourceTypes[req.TypeName].Body.ImpliedType()
val, err := ctyjson.Unmarshal(req.RawStateJSON, ty)

@ -91,6 +91,10 @@ func (s simple) ValidateDataResourceConfig(req providers.ValidateDataResourceCon
return resp
}
func (s simple) ValidateListResourceConfig(req providers.ValidateListResourceConfigRequest) (resp providers.ValidateListResourceConfigResponse) {
return resp
}
func (p simple) UpgradeResourceState(req providers.UpgradeResourceStateRequest) (resp providers.UpgradeResourceStateResponse) {
ty := p.schema.ResourceTypes[req.TypeName].Body.ImpliedType()
val, err := ctyjson.Unmarshal(req.RawStateJSON, ty)

@ -100,6 +100,13 @@ func (m *Mock) ValidateDataResourceConfig(request ValidateDataResourceConfigRequ
return m.Provider.ValidateDataResourceConfig(request)
}
func (m *Mock) ValidateListResourceConfig(request ValidateListResourceConfigRequest) ValidateListResourceConfigResponse {
// We'll just pass this through to the underlying provider. The mock should
// support the same data source syntax as the original provider and we can
// call validate without needing to configure the provider first.
return m.Provider.ValidateListResourceConfig(request)
}
func (m *Mock) UpgradeResourceState(request UpgradeResourceStateRequest) (response UpgradeResourceStateResponse) {
// We can't do this from a mocked provider, so we just return whatever state
// is in the request back unchanged.

@ -39,6 +39,10 @@ type Interface interface {
// ephemeral resource configuration values.
ValidateEphemeralResourceConfig(ValidateEphemeralResourceConfigRequest) ValidateEphemeralResourceConfigResponse
// ValidateListResourceConfig allows the provider to validate the list
// resource configuration values.
ValidateListResourceConfig(ValidateListResourceConfigRequest) ValidateListResourceConfigResponse
// UpgradeResourceState is called when the state loader encounters an
// instance state whose schema version is less than the one reported by the
// currently-used version of the corresponding provider, and the upgraded
@ -126,6 +130,10 @@ type GetProviderSchemaResponse struct {
// to its schema.
EphemeralResourceTypes map[string]Schema
// ListResourceTypes maps the name of an ephemeral resource type to its
// schema.
ListResourceTypes map[string]Schema
// Functions maps from local function name (not including an namespace
// prefix) to the declaration of a function.
Functions map[string]FunctionDecl
@ -261,6 +269,20 @@ type ValidateEphemeralResourceConfigResponse struct {
Diagnostics tfdiags.Diagnostics
}
type ValidateListResourceConfigRequest struct {
// TypeName is the name of the list resource type to validate.
TypeName string
// Config is the configuration value to validate, which may contain unknown
// values.
Config cty.Value
}
type ValidateListResourceConfigResponse struct {
// Diagnostics contains any warnings or errors from the method call.
Diagnostics tfdiags.Diagnostics
}
type UpgradeResourceStateRequest struct {
// TypeName is the name of the resource type being upgraded
TypeName string
@ -687,3 +709,15 @@ type CallFunctionResponse struct {
// specific argument.
Err error
}
type ListResourceRequest struct {
// TypeName is the name of the resource type being read.
TypeName string
// Config is the block body for the list resource.
Config cty.Value
// IncludeResourceObject can be set to true when a provider should include
// the full resource object for each result
IncludeResourceObject bool
}

@ -50,6 +50,11 @@ type MockProvider struct {
ValidateDataResourceConfigRequest providers.ValidateDataResourceConfigRequest
ValidateDataResourceConfigFn func(providers.ValidateDataResourceConfigRequest) providers.ValidateDataResourceConfigResponse
ValidateListResourceConfigCalled bool
ValidateListResourceConfigResponse *providers.ValidateListResourceConfigResponse
ValidateListResourceConfigRequest providers.ValidateListResourceConfigRequest
ValidateListResourceConfigFn func(providers.ValidateListResourceConfigRequest) providers.ValidateListResourceConfigResponse
UpgradeResourceStateCalled bool
UpgradeResourceStateResponse *providers.UpgradeResourceStateResponse
UpgradeResourceStateRequest providers.UpgradeResourceStateRequest
@ -253,28 +258,58 @@ func (p *MockProvider) ValidateEphemeralResourceConfig(r providers.ValidateEphem
p.ValidateEphemeralResourceConfigRequest = r
// Marshall the value to replicate behavior by the GRPC protocol
dataSchema, ok := p.getProviderSchema().EphemeralResourceTypes[r.TypeName]
ephemeralSchema, ok := p.getProviderSchema().EphemeralResourceTypes[r.TypeName]
if !ok {
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("no schema found for %q", r.TypeName))
return resp
}
_, err := msgpack.Marshal(r.Config, dataSchema.Body.ImpliedType())
_, err := msgpack.Marshal(r.Config, ephemeralSchema.Body.ImpliedType())
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(err)
return resp
}
if p.ValidateDataResourceConfigFn != nil {
if p.ValidateEphemeralResourceConfigFn != nil {
return p.ValidateEphemeralResourceConfigFn(r)
}
if p.ValidateDataResourceConfigResponse != nil {
if p.ValidateEphemeralResourceConfigResponse != nil {
return *p.ValidateEphemeralResourceConfigResponse
}
return resp
}
func (p *MockProvider) ValidateListResourceConfig(r providers.ValidateListResourceConfigRequest) (resp providers.ValidateListResourceConfigResponse) {
p.Lock()
defer p.Unlock()
p.ValidateListResourceConfigCalled = true
p.ValidateListResourceConfigRequest = r
// Marshall the value to replicate behavior by the GRPC protocol
listSchema, ok := p.getProviderSchema().ListResourceTypes[r.TypeName]
if !ok {
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("no schema found for %q", r.TypeName))
return resp
}
_, err := msgpack.Marshal(r.Config, listSchema.Body.ImpliedType())
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(err)
return resp
}
if p.ValidateListResourceConfigFn != nil {
return p.ValidateListResourceConfigFn(r)
}
if p.ValidateListResourceConfigResponse != nil {
return *p.ValidateListResourceConfigResponse
}
return resp
}
// UpgradeResourceState mocks out the response from the provider during an UpgradeResourceState RPC
// The default logic will return the resource's state unchanged, unless other logic is defined on the mock (e.g. UpgradeResourceStateFn)
//

@ -45,6 +45,10 @@ func (provider *mockProvider) ValidateDataResourceConfig(providers.ValidateDataR
panic("not implemented in mock")
}
func (provider *mockProvider) ValidateListResourceConfig(providers.ValidateListResourceConfigRequest) providers.ValidateListResourceConfigResponse {
panic("not implemented in mock")
}
func (provider *mockProvider) UpgradeResourceState(providers.UpgradeResourceStateRequest) providers.UpgradeResourceStateResponse {
panic("not implemented in mock")
}

@ -237,3 +237,10 @@ func (p *erroredProvider) ValidateResourceConfig(providers.ValidateResourceConfi
Diagnostics: nil,
}
}
// ValidateListResourceConfig implements providers.Interface.
func (p *erroredProvider) ValidateListResourceConfig(providers.ValidateListResourceConfigRequest) providers.ValidateListResourceConfigResponse {
return providers.ValidateListResourceConfigResponse{
Diagnostics: nil,
}
}

@ -90,6 +90,20 @@ func (p *offlineProvider) ValidateEphemeralResourceConfig(providers.ValidateEphe
}
}
// ValidateListResourceConfig implements providers.Interface.
func (p *offlineProvider) ValidateListResourceConfig(providers.ValidateListResourceConfigRequest) providers.ValidateListResourceConfigResponse {
var diags tfdiags.Diagnostics
diags = diags.Append(tfdiags.AttributeValue(
tfdiags.Error,
"Called ValidateListResourceConfig on an unconfigured provider",
"Cannot validate this resource config 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.ValidateListResourceConfigResponse{
Diagnostics: diags,
}
}
func (o *offlineProvider) UpgradeResourceState(_ providers.UpgradeResourceStateRequest) providers.UpgradeResourceStateResponse {
var diags tfdiags.Diagnostics
diags = diags.Append(tfdiags.AttributeValue(

@ -62,6 +62,12 @@ func (u *unknownProvider) ValidateDataResourceConfig(request providers.ValidateD
return u.unconfiguredClient.ValidateDataResourceConfig(request)
}
func (u *unknownProvider) ValidateListResourceConfig(request providers.ValidateListResourceConfigRequest) providers.ValidateListResourceConfigResponse {
// This is offline functionality, so we can hand it off to the unconfigured
// client.
return u.unconfiguredClient.ValidateListResourceConfig(request)
}
// ValidateEphemeralResourceConfig implements providers.Interface.
func (p *unknownProvider) ValidateEphemeralResourceConfig(providers.ValidateEphemeralResourceConfigRequest) providers.ValidateEphemeralResourceConfigResponse {
return providers.ValidateEphemeralResourceConfigResponse{

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save