Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions internal/command/query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,9 @@ func queryFixtureProvider() *testing_provider.MockProvider {
},
},
},
Nesting: configschema.NestingSingle,
Nesting: configschema.NestingSingle,
MinItems: 1,
MaxItems: 1,
},
},
}
Expand All @@ -306,7 +308,9 @@ func queryFixtureProvider() *testing_provider.MockProvider {
},
},
},
Nesting: configschema.NestingSingle,
Nesting: configschema.NestingSingle,
MinItems: 1,
MaxItems: 1,
},
},
}
Expand Down
40 changes: 40 additions & 0 deletions internal/plugin/convert/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/providers"
proto "github.com/hashicorp/terraform/internal/tfplugin5"
"github.com/zclconf/go-cty/cty"
)

// ConfigSchemaToProto takes a *configschema.Block and converts it to a
Expand Down Expand Up @@ -111,6 +112,45 @@ func ProtoToActionSchema(s *proto.ActionSchema) providers.ActionSchema {
}
}

func ProtoToListSchema(s *proto.Schema) providers.Schema {
listSchema := ProtoToProviderSchema(s, nil)
itemCount := 0
// check if the provider has set some attributes/blocks as required.
// When yes, then we set minItem = 1, which
// validates that the configuration contains a "config" block.
for _, attrS := range listSchema.Body.Attributes {
if attrS.Required {
itemCount = 1
break
}
}
for _, block := range listSchema.Body.BlockTypes {
if block.MinItems > 0 {
itemCount = 1
break
}
}
return providers.Schema{
Version: s.Version,
Body: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"data": {
Type: cty.DynamicPseudoType,
Computed: true,
},
},
BlockTypes: map[string]*configschema.NestedBlock{
"config": {
Block: *listSchema.Body,
Nesting: configschema.NestingSingle,
MinItems: itemCount,
MaxItems: itemCount,
},
},
},
}
}

// ProtoToConfigSchema takes the GetSchcema_Block from a grpc response and converts it
// to a terraform *configschema.Block.
func ProtoToConfigSchema(b *proto.Schema_Block) *configschema.Block {
Expand Down
36 changes: 11 additions & 25 deletions internal/plugin/grpc_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import (
"google.golang.org/grpc/status"

"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/logging"
"github.com/hashicorp/terraform/internal/plugin/convert"
"github.com/hashicorp/terraform/internal/providers"
Expand Down Expand Up @@ -171,24 +170,7 @@ func (p *GRPCProvider) GetProviderSchema() providers.GetProviderSchemaResponse {
}

for name, list := range protoResp.ListResourceSchemas {
ret := convert.ProtoToProviderSchema(list, nil)
resp.ListResourceTypes[name] = providers.Schema{
Version: ret.Version,
Body: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"data": {
Type: cty.DynamicPseudoType,
Computed: true,
},
},
BlockTypes: map[string]*configschema.NestedBlock{
"config": {
Block: *ret.Body,
Nesting: configschema.NestingSingle,
},
},
},
}
resp.ListResourceTypes[name] = convert.ProtoToListSchema(list)
}

for name, action := range protoResp.ActionSchemas {
Expand Down Expand Up @@ -381,10 +363,12 @@ func (p *GRPCProvider) ValidateListResourceConfig(r providers.ValidateListResour
}

configSchema := listResourceSchema.Body.BlockTypes["config"]
config := cty.NullVal(configSchema.ImpliedType())
if r.Config.Type().HasAttribute("config") {
config = r.Config.GetAttr("config")
if !r.Config.Type().HasAttribute("config") {
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("missing required attribute \"config\"; this is a bug in Terraform - please report it"))
return resp
}

config := r.Config.GetAttr("config")
mp, err := msgpack.Marshal(config, configSchema.ImpliedType())
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(err)
Expand Down Expand Up @@ -1342,10 +1326,12 @@ func (p *GRPCProvider) ListResource(r providers.ListResourceRequest) providers.L
}

configSchema := listResourceSchema.Body.BlockTypes["config"]
config := cty.NullVal(configSchema.ImpliedType())
if r.Config.Type().HasAttribute("config") {
config = r.Config.GetAttr("config")
if !r.Config.Type().HasAttribute("config") {
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("missing required attribute \"config\"; this is a bug in Terraform - please report it"))
return resp
}

config := r.Config.GetAttr("config")
mp, err := msgpack.Marshal(config, configSchema.ImpliedType())
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(err)
Expand Down
79 changes: 73 additions & 6 deletions internal/plugin/grpc_provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,23 @@ func providerProtoSchema() *proto.GetProviderSchema_Response {
Required: true,
},
},
BlockTypes: []*proto.Schema_NestedBlock{
{
TypeName: "nested_filter",
Nesting: proto.Schema_NestedBlock_SINGLE,
Block: &proto.Schema_Block{
Attributes: []*proto.Schema_Attribute{
{
Name: "nested_attr",
Type: []byte(`"string"`),
Required: false,
},
},
},
MinItems: 1,
MaxItems: 1,
},
},
},
},
},
Expand Down Expand Up @@ -466,7 +483,7 @@ func TestGRPCProvider_ValidateListResourceConfig(t *testing.T) {
gomock.Any(),
).Return(&proto.ValidateListResourceConfig_Response{}, nil)

cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{"config": map[string]interface{}{"filter_attr": "value"}})
cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{"config": map[string]interface{}{"filter_attr": "value", "nested_filter": map[string]interface{}{"nested_attr": "value"}}})
resp := p.ValidateListResourceConfig(providers.ValidateListResourceConfigRequest{
TypeName: "list",
Config: cfg,
Expand All @@ -478,8 +495,18 @@ func TestGRPCProvider_ValidateListResourceConfig_OptionalCfg(t *testing.T) {
ctrl := gomock.NewController(t)
client := mockproto.NewMockProviderClient(ctrl)
sch := providerProtoSchema()
sch.ListResourceSchemas["list"].Block.Attributes[0].Optional = true
sch.ListResourceSchemas["list"].Block.Attributes[0].Required = false

// mock the schema in a way that makes the config attributes optional
listSchema := sch.ListResourceSchemas["list"].Block
// filter_attr is optional
listSchema.Attributes[0].Optional = true
listSchema.Attributes[0].Required = false

// nested_filter is optional
listSchema.BlockTypes[0].MinItems = 0
listSchema.BlockTypes[0].MaxItems = 0

sch.ListResourceSchemas["list"].Block = listSchema
// we always need a GetSchema method
client.EXPECT().GetSchema(
gomock.Any(),
Expand All @@ -502,10 +529,15 @@ func TestGRPCProvider_ValidateListResourceConfig_OptionalCfg(t *testing.T) {
gomock.Any(),
).Return(&proto.ValidateListResourceConfig_Response{}, nil)

cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{})
converted := convert.ProtoToListSchema(sch.ListResourceSchemas["list"])
cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]any{})
coercedCfg, err := converted.Body.CoerceValue(cfg)
if err != nil {
t.Fatalf("failed to coerce config: %v", err)
}
resp := p.ValidateListResourceConfig(providers.ValidateListResourceConfigRequest{
TypeName: "list",
Config: cfg,
Config: coercedCfg,
})
checkDiags(t, resp.Diagnostics)
}
Expand Down Expand Up @@ -1438,8 +1470,25 @@ func TestGRPCProvider_GetSchema_ListResourceTypes(t *testing.T) {
Required: true,
},
},
BlockTypes: map[string]*configschema.NestedBlock{
"nested_filter": {
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"nested_attr": {
Type: cty.String,
Required: false,
},
},
},
Nesting: configschema.NestingSingle,
MinItems: 1,
MaxItems: 1,
},
},
},
Nesting: configschema.NestingSingle,
Nesting: configschema.NestingSingle,
MinItems: 1,
MaxItems: 1,
},
},
},
Expand Down Expand Up @@ -1485,6 +1534,9 @@ func TestGRPCProvider_Encode(t *testing.T) {
Before: cty.NullVal(cty.Object(map[string]cty.Type{
"config": cty.Object(map[string]cty.Type{
"filter_attr": cty.String,
"nested_filter": cty.Object(map[string]cty.Type{
"nested_attr": cty.String,
}),
}),
"data": cty.List(cty.Object(map[string]cty.Type{
"state": cty.Object(map[string]cty.Type{
Expand All @@ -1498,6 +1550,9 @@ func TestGRPCProvider_Encode(t *testing.T) {
After: cty.ObjectVal(map[string]cty.Value{
"config": cty.ObjectVal(map[string]cty.Value{
"filter_attr": cty.StringVal("value"),
"nested_filter": cty.ObjectVal(map[string]cty.Value{
"nested_attr": cty.StringVal("value"),
}),
}),
"data": cty.ListVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
Expand Down Expand Up @@ -1649,6 +1704,9 @@ func TestGRPCProvider_ListResource(t *testing.T) {
configVal := cty.ObjectVal(map[string]cty.Value{
"config": cty.ObjectVal(map[string]cty.Value{
"filter_attr": cty.StringVal("filter-value"),
"nested_filter": cty.ObjectVal(map[string]cty.Value{
"nested_attr": cty.StringVal("value"),
}),
}),
})
request := providers.ListResourceRequest{
Expand Down Expand Up @@ -1731,6 +1789,9 @@ func TestGRPCProvider_ListResource_Error(t *testing.T) {
configVal := cty.ObjectVal(map[string]cty.Value{
"config": cty.ObjectVal(map[string]cty.Value{
"filter_attr": cty.StringVal("filter-value"),
"nested_filter": cty.ObjectVal(map[string]cty.Value{
"nested_attr": cty.StringVal("value"),
}),
}),
})
request := providers.ListResourceRequest{
Expand All @@ -1746,6 +1807,9 @@ func TestGRPCProvider_ListResource_Diagnostics(t *testing.T) {
configVal := cty.ObjectVal(map[string]cty.Value{
"config": cty.ObjectVal(map[string]cty.Value{
"filter_attr": cty.StringVal("filter-value"),
"nested_filter": cty.ObjectVal(map[string]cty.Value{
"nested_attr": cty.StringVal("value"),
}),
}),
})
request := providers.ListResourceRequest{
Expand Down Expand Up @@ -2009,6 +2073,9 @@ func TestGRPCProvider_ListResource_Limit(t *testing.T) {
configVal := cty.ObjectVal(map[string]cty.Value{
"config": cty.ObjectVal(map[string]cty.Value{
"filter_attr": cty.StringVal("filter-value"),
"nested_filter": cty.ObjectVal(map[string]cty.Value{
"nested_attr": cty.StringVal("value"),
}),
}),
})
request := providers.ListResourceRequest{
Expand Down
39 changes: 39 additions & 0 deletions internal/plugin6/convert/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,45 @@ func ProtoToActionSchema(s *proto.ActionSchema) providers.ActionSchema {
}
}

func ProtoToListSchema(s *proto.Schema) providers.Schema {
listSchema := ProtoToProviderSchema(s, nil)
itemCount := 0
// check if the provider has set some attributes/blocks as required.
// When yes, then we set minItem = 1, which
// validates that the configuration contains a "config" block.
for _, attrS := range listSchema.Body.Attributes {
if attrS.Required {
itemCount = 1
break
}
}
for _, block := range listSchema.Body.BlockTypes {
if block.MinItems > 0 {
itemCount = 1
break
}
}
return providers.Schema{
Version: listSchema.Version,
Body: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"data": {
Type: cty.DynamicPseudoType,
Computed: true,
},
},
BlockTypes: map[string]*configschema.NestedBlock{
"config": {
Block: *listSchema.Body,
Nesting: configschema.NestingSingle,
MinItems: itemCount,
MaxItems: itemCount,
},
},
},
}
}

func ProtoToIdentitySchema(attributes []*proto.ResourceIdentitySchema_IdentityAttribute) *configschema.Object {
obj := &configschema.Object{
Attributes: make(map[string]*configschema.Attribute),
Expand Down
Loading