Skip to content

Initial Setup for ValidateListResourceConfig in List RPCs #514

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jun 2, 2025
Merged
9 changes: 9 additions & 0 deletions internal/logging/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,15 @@ func EphemeralResourceContext(ctx context.Context, ephemeralResource string) con
return ctx
}

// ListResourceContext injects the list resource type into logger contexts.
func ListResourceContext(ctx context.Context, listResource string) context.Context {
ctx = tfsdklog.SetField(ctx, KeyListResourceType, listResource)
ctx = tfsdklog.SubsystemSetField(ctx, SubsystemProto, KeyListResourceType, listResource)
ctx = tflog.SetField(ctx, KeyListResourceType, listResource)

return ctx
}

// RpcContext injects the RPC name into logger contexts.
func RpcContext(ctx context.Context, rpc string) context.Context {
ctx = tfsdklog.SetField(ctx, KeyRPC, rpc)
Expand Down
3 changes: 3 additions & 0 deletions internal/logging/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ const (
// The type of ephemeral resource being operated on, such as "random_password"
KeyEphemeralResourceType = "tf_ephemeral_resource_type"

// The type of list resource being operated on
KeyListResourceType = "tf_list_resource_type"

// Path to protocol data file, such as "/tmp/example.json"
KeyProtocolDataFile = "tf_proto_data_file"

Expand Down
20 changes: 20 additions & 0 deletions tfprotov5/internal/fromproto/list_resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package fromproto

import (
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-go/tfprotov5/internal/tfplugin5"
)

func ValidateListResourceConfigRequest(in *tfplugin5.ValidateListResourceConfig_Request) *tfprotov5.ValidateListResourceConfigRequest {
if in == nil {
return nil
}

return &tfprotov5.ValidateListResourceConfigRequest{
TypeName: in.TypeName,
Config: DynamicValue(in.Config),
}
}
60 changes: 60 additions & 0 deletions tfprotov5/internal/fromproto/list_resource_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package fromproto_test

import (
"testing"

"github.com/google/go-cmp/cmp"

"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-go/tfprotov5/internal/fromproto"
"github.com/hashicorp/terraform-plugin-go/tfprotov5/internal/tfplugin5"
)

func TestValidateListResourceConfigRequest(t *testing.T) {
t.Parallel()

testCases := map[string]struct {
in *tfplugin5.ValidateListResourceConfig_Request
expected *tfprotov5.ValidateListResourceConfigRequest
}{
"nil": {
in: nil,
expected: nil,
},
"zero": {
in: &tfplugin5.ValidateListResourceConfig_Request{},
expected: &tfprotov5.ValidateListResourceConfigRequest{},
},
"Config": {
in: &tfplugin5.ValidateListResourceConfig_Request{
Config: testTfplugin5DynamicValue(),
},
expected: &tfprotov5.ValidateListResourceConfigRequest{
Config: testTfprotov5DynamicValue(),
},
},
"TypeName": {
in: &tfplugin5.ValidateListResourceConfig_Request{
TypeName: "test",
},
expected: &tfprotov5.ValidateListResourceConfigRequest{
TypeName: "test",
},
},
}

for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
t.Parallel()

got := fromproto.ValidateListResourceConfigRequest(testCase.in)

if diff := cmp.Diff(got, testCase.expected); diff != "" {
t.Errorf("unexpected difference: %s", diff)
}
})
}
}
29 changes: 29 additions & 0 deletions tfprotov5/internal/toproto/list_resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package toproto

import (
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-go/tfprotov5/internal/tfplugin5"
)

func GetMetadata_ListResourceMetadata(in *tfprotov5.ListResourceMetadata) *tfplugin5.GetMetadata_ListResourceMetadata {
if in == nil {
return nil
}

return &tfplugin5.GetMetadata_ListResourceMetadata{
TypeName: in.TypeName,
}
}

func ValidateListResourceConfig_Response(in *tfprotov5.ValidateListResourceConfigResponse) *tfplugin5.ValidateListResourceConfig_Response {
if in == nil {
return nil
}

return &tfplugin5.ValidateListResourceConfig_Response{
Diagnostics: Diagnostics(in.Diagnostics),
}
}
113 changes: 113 additions & 0 deletions tfprotov5/internal/toproto/list_resource_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package toproto_test

import (
"testing"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-go/tfprotov5/internal/tfplugin5"
"github.com/hashicorp/terraform-plugin-go/tfprotov5/internal/toproto"
)

func TestGetMetadata_ListResourceMetadata(t *testing.T) {
t.Parallel()

testCases := map[string]struct {
in *tfprotov5.ListResourceMetadata
expected *tfplugin5.GetMetadata_ListResourceMetadata
}{
"nil": {
in: nil,
expected: nil,
},
"zero": {
in: &tfprotov5.ListResourceMetadata{},
expected: &tfplugin5.GetMetadata_ListResourceMetadata{},
},
"TypeName": {
in: &tfprotov5.ListResourceMetadata{
TypeName: "test",
},
expected: &tfplugin5.GetMetadata_ListResourceMetadata{
TypeName: "test",
},
},
}

for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
t.Parallel()

got := toproto.GetMetadata_ListResourceMetadata(testCase.in)

// Protocol Buffers generated types must have unexported fields
// ignored or cmp.Diff() will raise an error. This is easier than
// writing a custom Comparer for each type, which would have no
// benefits.
diffOpts := cmpopts.IgnoreUnexported(
tfplugin5.GetMetadata_ListResourceMetadata{},
)

if diff := cmp.Diff(got, testCase.expected, diffOpts); diff != "" {
t.Errorf("unexpected difference: %s", diff)
}
})
}
}

func TestValidateListResourceConfig_Response(t *testing.T) {
t.Parallel()

testCases := map[string]struct {
in *tfprotov5.ValidateListResourceConfigResponse
expected *tfplugin5.ValidateListResourceConfig_Response
}{
"nil": {
in: nil,
expected: nil,
},
"zero": {
in: &tfprotov5.ValidateListResourceConfigResponse{},
expected: &tfplugin5.ValidateListResourceConfig_Response{
Diagnostics: []*tfplugin5.Diagnostic{},
},
},
"Diagnostics": {
in: &tfprotov5.ValidateListResourceConfigResponse{
Diagnostics: []*tfprotov5.Diagnostic{
testTfprotov5Diagnostic,
},
},
expected: &tfplugin5.ValidateListResourceConfig_Response{
Diagnostics: []*tfplugin5.Diagnostic{
testTfplugin5Diagnostic,
},
},
},
}

for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
t.Parallel()

got := toproto.ValidateListResourceConfig_Response(testCase.in)

// Protocol Buffers generated types must have unexported fields
// ignored or cmp.Diff() will raise an error. This is easier than
// writing a custom Comparer for each type, which would have no
// benefits.
diffOpts := cmpopts.IgnoreUnexported(
tfplugin5.Diagnostic{},
tfplugin5.ValidateListResourceConfig_Response{},
)

if diff := cmp.Diff(got, testCase.expected, diffOpts); diff != "" {
t.Errorf("unexpected difference: %s", diff)
}
})
}
}
10 changes: 10 additions & 0 deletions tfprotov5/internal/toproto/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ func GetMetadata_Response(in *tfprotov5.GetMetadataResponse) *tfplugin5.GetMetad
DataSources: make([]*tfplugin5.GetMetadata_DataSourceMetadata, 0, len(in.DataSources)),
Diagnostics: Diagnostics(in.Diagnostics),
EphemeralResources: make([]*tfplugin5.GetMetadata_EphemeralResourceMetadata, 0, len(in.EphemeralResources)),
ListResources: make([]*tfplugin5.GetMetadata_ListResourceMetadata, 0, len(in.ListResources)),
Functions: make([]*tfplugin5.GetMetadata_FunctionMetadata, 0, len(in.Functions)),
Resources: make([]*tfplugin5.GetMetadata_ResourceMetadata, 0, len(in.Resources)),
ServerCapabilities: ServerCapabilities(in.ServerCapabilities),
Expand All @@ -30,6 +31,10 @@ func GetMetadata_Response(in *tfprotov5.GetMetadataResponse) *tfplugin5.GetMetad
resp.EphemeralResources = append(resp.EphemeralResources, GetMetadata_EphemeralResourceMetadata(&ephemeralResource))
}

for _, listResource := range in.ListResources {
resp.ListResources = append(resp.ListResources, GetMetadata_ListResourceMetadata(&listResource))
}

for _, function := range in.Functions {
resp.Functions = append(resp.Functions, GetMetadata_FunctionMetadata(&function))
}
Expand All @@ -50,6 +55,7 @@ func GetProviderSchema_Response(in *tfprotov5.GetProviderSchemaResponse) *tfplug
DataSourceSchemas: make(map[string]*tfplugin5.Schema, len(in.DataSourceSchemas)),
Diagnostics: Diagnostics(in.Diagnostics),
EphemeralResourceSchemas: make(map[string]*tfplugin5.Schema, len(in.EphemeralResourceSchemas)),
ListResourceSchemas: make(map[string]*tfplugin5.Schema, len(in.ListResourceSchemas)),
Functions: make(map[string]*tfplugin5.Function, len(in.Functions)),
Provider: Schema(in.Provider),
ProviderMeta: Schema(in.ProviderMeta),
Expand All @@ -61,6 +67,10 @@ func GetProviderSchema_Response(in *tfprotov5.GetProviderSchemaResponse) *tfplug
resp.EphemeralResourceSchemas[name] = Schema(schema)
}

for name, schema := range in.ListResourceSchemas {
resp.ListResourceSchemas[name] = Schema(schema)
}

for name, schema := range in.ResourceSchemas {
resp.ResourceSchemas[name] = Schema(schema)
}
Expand Down
Loading
Loading