diff --git a/go.mod b/go.mod index b6fd03e..026bca3 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.23.7 require ( github.com/google/go-cmp v0.7.0 - github.com/hashicorp/terraform-plugin-go v0.29.0-alpha.1.0.20250709165734-a8477a15f806 + github.com/hashicorp/terraform-plugin-go v0.29.0-alpha.1.0.20250717133739-e33a5336fb19 github.com/hashicorp/terraform-plugin-log v0.9.0 google.golang.org/grpc v1.73.0 ) diff --git a/go.sum b/go.sum index 58d2914..2b4b5e5 100644 --- a/go.sum +++ b/go.sum @@ -21,8 +21,8 @@ github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0U github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/terraform-plugin-go v0.29.0-alpha.1.0.20250709165734-a8477a15f806 h1:i3kA1sT/Fk8Ex+VVKdjf9sFOPwS7w3Q73pfbnxKwdjg= -github.com/hashicorp/terraform-plugin-go v0.29.0-alpha.1.0.20250709165734-a8477a15f806/go.mod h1:hL//wLEfYo0YVt0TC/VLzia/ADQQto3HEm4/jX2gkdY= +github.com/hashicorp/terraform-plugin-go v0.29.0-alpha.1.0.20250717133739-e33a5336fb19 h1:P/ZVGEGXt9xSiLz+CrP/JzV2V8rtlE7994AX4jzcGB8= +github.com/hashicorp/terraform-plugin-go v0.29.0-alpha.1.0.20250717133739-e33a5336fb19/go.mod h1:hL//wLEfYo0YVt0TC/VLzia/ADQQto3HEm4/jX2gkdY= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= github.com/hashicorp/terraform-registry-address v0.3.0 h1:HMpK3nqaGFPS9VmgRXrJL/dzHNdheGVKk5k7VlFxzCo= diff --git a/internal/tf5testserver/tf5testserver.go b/internal/tf5testserver/tf5testserver.go index fe90817..d0b2f6d 100644 --- a/internal/tf5testserver/tf5testserver.go +++ b/internal/tf5testserver/tf5testserver.go @@ -69,6 +69,8 @@ type TestServer struct { ListResourceCalled map[string]bool + ValidateActionConfigCalled map[string]bool + PlanActionCalled map[string]bool InvokeActionCalled map[string]bool @@ -300,6 +302,15 @@ func (s *TestServer) ListResource(_ context.Context, req *tfprotov5.ListResource return nil, nil } +func (s *TestServer) ValidateActionConfig(_ context.Context, req *tfprotov5.ValidateActionConfigRequest) (*tfprotov5.ValidateActionConfigResponse, error) { + if s.ValidateActionConfigCalled == nil { + s.ValidateActionConfigCalled = make(map[string]bool) + } + + s.ValidateActionConfigCalled[req.ActionType] = true + return nil, nil +} + func (s *TestServer) PlanAction(ctx context.Context, req *tfprotov5.PlanActionRequest) (*tfprotov5.PlanActionResponse, error) { if s.PlanActionCalled == nil { s.PlanActionCalled = make(map[string]bool) diff --git a/internal/tf6testserver/tf6testserver.go b/internal/tf6testserver/tf6testserver.go index fe61b07..b64a88d 100644 --- a/internal/tf6testserver/tf6testserver.go +++ b/internal/tf6testserver/tf6testserver.go @@ -69,6 +69,8 @@ type TestServer struct { ListResourceCalled map[string]bool + ValidateActionConfigCalled map[string]bool + PlanActionCalled map[string]bool InvokeActionCalled map[string]bool @@ -300,6 +302,15 @@ func (s *TestServer) ListResource(_ context.Context, req *tfprotov6.ListResource return nil, nil } +func (s *TestServer) ValidateActionConfig(_ context.Context, req *tfprotov6.ValidateActionConfigRequest) (*tfprotov6.ValidateActionConfigResponse, error) { + if s.ValidateActionConfigCalled == nil { + s.ValidateActionConfigCalled = make(map[string]bool) + } + + s.ValidateActionConfigCalled[req.ActionType] = true + return nil, nil +} + func (s *TestServer) PlanAction(ctx context.Context, req *tfprotov6.PlanActionRequest) (*tfprotov6.PlanActionResponse, error) { if s.PlanActionCalled == nil { s.PlanActionCalled = make(map[string]bool) diff --git a/internal/tfprotov5tov6/tfprotov5tov6.go b/internal/tfprotov5tov6/tfprotov5tov6.go index 28c7f42..b8250ea 100644 --- a/internal/tfprotov5tov6/tfprotov5tov6.go +++ b/internal/tfprotov5tov6/tfprotov5tov6.go @@ -1126,6 +1126,27 @@ func LinkedResourceSchema(in *tfprotov5.LinkedResourceSchema) *tfprotov6.LinkedR } } +func ValidateActionConfigRequest(in *tfprotov5.ValidateActionConfigRequest) *tfprotov6.ValidateActionConfigRequest { + if in == nil { + return nil + } + + return &tfprotov6.ValidateActionConfigRequest{ + Config: DynamicValue(in.Config), + ActionType: in.ActionType, + } +} + +func ValidateActionConfigResponse(in *tfprotov5.ValidateActionConfigResponse) *tfprotov6.ValidateActionConfigResponse { + if in == nil { + return nil + } + + return &tfprotov6.ValidateActionConfigResponse{ + Diagnostics: Diagnostics(in.Diagnostics), + } +} + func PlanActionRequest(in *tfprotov5.PlanActionRequest) *tfprotov6.PlanActionRequest { if in == nil { return nil diff --git a/internal/tfprotov5tov6/tfprotov5tov6_test.go b/internal/tfprotov5tov6/tfprotov5tov6_test.go index 5d9893d..d26f790 100644 --- a/internal/tfprotov5tov6/tfprotov5tov6_test.go +++ b/internal/tfprotov5tov6/tfprotov5tov6_test.go @@ -3243,6 +3243,78 @@ func TestActionSchema(t *testing.T) { } } +func TestValidateActionConfigRequest(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + in *tfprotov5.ValidateActionConfigRequest + expected *tfprotov6.ValidateActionConfigRequest + }{ + "nil": { + in: nil, + expected: nil, + }, + "all-valid-fields": { + in: &tfprotov5.ValidateActionConfigRequest{ + Config: &testTfprotov5DynamicValue, + ActionType: "test_action", + }, + expected: &tfprotov6.ValidateActionConfigRequest{ + Config: &testTfprotov6DynamicValue, + ActionType: "test_action", + }, + }, + } + + for name, testCase := range testCases { + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tfprotov5tov6.ValidateActionConfigRequest(testCase.in) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestValidateActionConfigResponse(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + in *tfprotov5.ValidateActionConfigResponse + expected *tfprotov6.ValidateActionConfigResponse + }{ + "nil": { + in: nil, + expected: nil, + }, + "all-valid-fields": { + in: &tfprotov5.ValidateActionConfigResponse{ + Diagnostics: testTfprotov5Diagnostics, + }, + expected: &tfprotov6.ValidateActionConfigResponse{ + Diagnostics: testTfprotov6Diagnostics, + }, + }, + } + + for name, testCase := range testCases { + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tfprotov5tov6.ValidateActionConfigResponse(testCase.in) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + func TestPlanActionRequest(t *testing.T) { t.Parallel() diff --git a/internal/tfprotov6tov5/tfprotov6tov5.go b/internal/tfprotov6tov5/tfprotov6tov5.go index e53ce78..7485298 100644 --- a/internal/tfprotov6tov5/tfprotov6tov5.go +++ b/internal/tfprotov6tov5/tfprotov6tov5.go @@ -1202,6 +1202,27 @@ func LinkedResourceSchema(in *tfprotov6.LinkedResourceSchema) *tfprotov5.LinkedR } } +func ValidateActionConfigRequest(in *tfprotov6.ValidateActionConfigRequest) *tfprotov5.ValidateActionConfigRequest { + if in == nil { + return nil + } + + return &tfprotov5.ValidateActionConfigRequest{ + Config: DynamicValue(in.Config), + ActionType: in.ActionType, + } +} + +func ValidateActionConfigResponse(in *tfprotov6.ValidateActionConfigResponse) *tfprotov5.ValidateActionConfigResponse { + if in == nil { + return nil + } + + return &tfprotov5.ValidateActionConfigResponse{ + Diagnostics: Diagnostics(in.Diagnostics), + } +} + func PlanActionRequest(in *tfprotov6.PlanActionRequest) *tfprotov5.PlanActionRequest { if in == nil { return nil diff --git a/internal/tfprotov6tov5/tfprotov6tov5_test.go b/internal/tfprotov6tov5/tfprotov6tov5_test.go index c2efdd5..b68f12d 100644 --- a/internal/tfprotov6tov5/tfprotov6tov5_test.go +++ b/internal/tfprotov6tov5/tfprotov6tov5_test.go @@ -3613,6 +3613,78 @@ func TestActionSchema(t *testing.T) { } } +func TestValidateActionConfigRequest(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + in *tfprotov6.ValidateActionConfigRequest + expected *tfprotov5.ValidateActionConfigRequest + }{ + "nil": { + in: nil, + expected: nil, + }, + "all-valid-fields": { + in: &tfprotov6.ValidateActionConfigRequest{ + Config: &testTfprotov6DynamicValue, + ActionType: "test_action", + }, + expected: &tfprotov5.ValidateActionConfigRequest{ + Config: &testTfprotov5DynamicValue, + ActionType: "test_action", + }, + }, + } + + for name, testCase := range testCases { + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tfprotov6tov5.ValidateActionConfigRequest(testCase.in) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestValidateActionConfigResponse(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + in *tfprotov6.ValidateActionConfigResponse + expected *tfprotov5.ValidateActionConfigResponse + }{ + "nil": { + in: nil, + expected: nil, + }, + "all-valid-fields": { + in: &tfprotov6.ValidateActionConfigResponse{ + Diagnostics: testTfprotov6Diagnostics, + }, + expected: &tfprotov5.ValidateActionConfigResponse{ + Diagnostics: testTfprotov5Diagnostics, + }, + }, + } + + for name, testCase := range testCases { + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tfprotov6tov5.ValidateActionConfigResponse(testCase.in) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + func TestPlanActionRequest(t *testing.T) { t.Parallel() diff --git a/tf5muxserver/mux_server_ValidateActionConfig.go b/tf5muxserver/mux_server_ValidateActionConfig.go new file mode 100644 index 0000000..23f9da5 --- /dev/null +++ b/tf5muxserver/mux_server_ValidateActionConfig.go @@ -0,0 +1,52 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tf5muxserver + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + + "github.com/hashicorp/terraform-plugin-mux/internal/logging" +) + +func (s *muxServer) ValidateActionConfig(ctx context.Context, req *tfprotov5.ValidateActionConfigRequest) (*tfprotov5.ValidateActionConfigResponse, error) { + rpc := "ValidateActionTypeConfig" + ctx = logging.InitContext(ctx) + ctx = logging.RpcContext(ctx, rpc) + + server, diags, err := s.getActionServer(ctx, req.ActionType) + + if err != nil { + return nil, err + } + + if diagnosticsHasError(diags) { + return &tfprotov5.ValidateActionConfigResponse{ + Diagnostics: diags, + }, nil + } + + // TODO: Remove and call server.ValidateActionConfig below directly once interface becomes required. + actionServer, ok := server.(tfprotov5.ActionServer) + if !ok { + resp := &tfprotov5.ValidateActionConfigResponse{ + Diagnostics: []*tfprotov5.Diagnostic{ + { + Severity: tfprotov5.DiagnosticSeverityError, + Summary: "ValidateActionConfig Not Implemented", + Detail: "A ValidateActionConfig call was received by the provider, however the provider does not implement ValidateActionConfig. " + + "Either upgrade the provider to a version that implements ValidateActionConfig or this is a bug in Terraform that should be reported to the Terraform maintainers.", + }, + }, + } + + return resp, nil + } + + ctx = logging.Tfprotov5ProviderServerContext(ctx, server) + logging.MuxTrace(ctx, "calling downstream server") + + return actionServer.ValidateActionConfig(ctx, req) +} diff --git a/tf5muxserver/mux_server_ValidateActionConfig_test.go b/tf5muxserver/mux_server_ValidateActionConfig_test.go new file mode 100644 index 0000000..5d280ad --- /dev/null +++ b/tf5muxserver/mux_server_ValidateActionConfig_test.go @@ -0,0 +1,78 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tf5muxserver_test + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + + "github.com/hashicorp/terraform-plugin-mux/internal/tf5testserver" + "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" +) + +func TestMuxServerValidateActionConfig(t *testing.T) { + t.Parallel() + + ctx := context.Background() + testServer1 := &tf5testserver.TestServer{ + GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{ + "test_action_server1": {}, + }, + }, + } + testServer2 := &tf5testserver.TestServer{ + GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{ + "test_action_server2": {}, + }, + }, + } + servers := []func() tfprotov5.ProviderServer{testServer1.ProviderServer, testServer2.ProviderServer} + muxServer, err := tf5muxserver.NewMuxServer(ctx, servers...) + + if err != nil { + t.Fatalf("unexpected error setting up factory: %s", err) + } + + //nolint:staticcheck // Intentionally verifying interface implementation + actionServer, ok := muxServer.ProviderServer().(tfprotov5.ProviderServerWithActions) + if !ok { + t.Fatal("muxServer should implement tfprotov5.ProviderServerWithActions") + } + + _, err = actionServer.ValidateActionConfig(ctx, &tfprotov5.ValidateActionConfigRequest{ + ActionType: "test_action_server1", + }) + + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + if !testServer1.ValidateActionConfigCalled["test_action_server1"] { + t.Errorf("expected test_action_server1 ValidateActionConfig to be called on server1") + } + + if testServer2.ValidateActionConfigCalled["test_action_server1"] { + t.Errorf("unexpected test_action_server1 ValidateActionConfig called on server2") + } + + _, err = actionServer.ValidateActionConfig(ctx, &tfprotov5.ValidateActionConfigRequest{ + ActionType: "test_action_server2", + }) + + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + if testServer1.ValidateActionConfigCalled["test_action_server2"] { + t.Errorf("unexpected test_action_server2 ValidateActionConfig called on server1") + } + + if !testServer2.ValidateActionConfigCalled["test_action_server2"] { + t.Errorf("expected test_action_server2 ValidateActionConfig to be called on server2") + } +} diff --git a/tf5to6server/tf5to6server.go b/tf5to6server/tf5to6server.go index 111735f..82c340c 100644 --- a/tf5to6server/tf5to6server.go +++ b/tf5to6server/tf5to6server.go @@ -342,6 +342,35 @@ func (s v5tov6Server) ListResource(ctx context.Context, req *tfprotov6.ListResou return tfprotov5tov6.ListResourceServerStream(v5Resp), nil } +func (s v5tov6Server) ValidateActionConfig(ctx context.Context, req *tfprotov6.ValidateActionConfigRequest) (*tfprotov6.ValidateActionConfigResponse, error) { + // TODO: Remove and call s.v5Server.ValidateActionConfig below directly once interface becomes required + actionServer, ok := s.v5Server.(tfprotov5.ActionServer) + if !ok { + v6Resp := &tfprotov6.ValidateActionConfigResponse{ + Diagnostics: []*tfprotov6.Diagnostic{ + { + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "ValidateActionConfig Not Implemented", + Detail: "A ValidateActionConfig call was received by the provider, however the provider does not implement the RPC. " + + "Either upgrade the provider to a version that implements ValidateActionConfig or this is a bug in Terraform that should be reported to the Terraform maintainers.", + }, + }, + } + + return v6Resp, nil + } + + v5Req := tfprotov6tov5.ValidateActionConfigRequest(req) + + // v5Resp, err := s.v5Server.ValidateActionConfig(ctx, v5Req) + v5Resp, err := actionServer.ValidateActionConfig(ctx, v5Req) + if err != nil { + return nil, err + } + + return tfprotov5tov6.ValidateActionConfigResponse(v5Resp), nil +} + func (s v5tov6Server) PlanAction(ctx context.Context, req *tfprotov6.PlanActionRequest) (*tfprotov6.PlanActionResponse, error) { // TODO: Remove and call s.v5Server.PlanAction below directly once interface becomes required actionServer, ok := s.v5Server.(tfprotov5.ActionServer) diff --git a/tf5to6server/tf5to6server_test.go b/tf5to6server/tf5to6server_test.go index 374b345..ff36ff1 100644 --- a/tf5to6server/tf5to6server_test.go +++ b/tf5to6server/tf5to6server_test.go @@ -879,6 +879,43 @@ func TestV5ToV6ServerListResource(t *testing.T) { } } +func TestV5ToV6ServerValidateActionConfig(t *testing.T) { + t.Parallel() + + ctx := context.Background() + v5server := &tf5testserver.TestServer{ + GetProviderSchemaResponse: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{ + "test_action": {}, + }, + }, + } + + v6server, err := tf5to6server.UpgradeServer(context.Background(), v5server.ProviderServer) + + if err != nil { + t.Fatalf("unexpected error downgrading server: %s", err) + } + + //nolint:staticcheck // Intentionally verifying interface implementation + actionServer, ok := v6server.(tfprotov6.ProviderServerWithActions) + if !ok { + t.Fatal("v6server should implement tfprotov6.ProviderServerWithActions") + } + + _, err = actionServer.ValidateActionConfig(ctx, &tfprotov6.ValidateActionConfigRequest{ + ActionType: "test_action", + }) + + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + if !v5server.ValidateActionConfigCalled["test_action"] { + t.Errorf("expected test_action ValidateActionConfig to be called") + } +} + func TestV5ToV6ServerPlanAction(t *testing.T) { t.Parallel() diff --git a/tf6muxserver/mux_server_ValidateActionConfig.go b/tf6muxserver/mux_server_ValidateActionConfig.go new file mode 100644 index 0000000..b9492b9 --- /dev/null +++ b/tf6muxserver/mux_server_ValidateActionConfig.go @@ -0,0 +1,52 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tf6muxserver + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + + "github.com/hashicorp/terraform-plugin-mux/internal/logging" +) + +func (s *muxServer) ValidateActionConfig(ctx context.Context, req *tfprotov6.ValidateActionConfigRequest) (*tfprotov6.ValidateActionConfigResponse, error) { + rpc := "ValidateActionTypeConfig" + ctx = logging.InitContext(ctx) + ctx = logging.RpcContext(ctx, rpc) + + server, diags, err := s.getActionServer(ctx, req.ActionType) + + if err != nil { + return nil, err + } + + if diagnosticsHasError(diags) { + return &tfprotov6.ValidateActionConfigResponse{ + Diagnostics: diags, + }, nil + } + + // TODO: Remove and call server.ValidateActionConfig below directly once interface becomes required. + actionServer, ok := server.(tfprotov6.ActionServer) + if !ok { + resp := &tfprotov6.ValidateActionConfigResponse{ + Diagnostics: []*tfprotov6.Diagnostic{ + { + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "ValidateActionConfig Not Implemented", + Detail: "A ValidateActionConfig call was received by the provider, however the provider does not implement ValidateActionConfig. " + + "Either upgrade the provider to a version that implements ValidateActionConfig or this is a bug in Terraform that should be reported to the Terraform maintainers.", + }, + }, + } + + return resp, nil + } + + ctx = logging.Tfprotov6ProviderServerContext(ctx, server) + logging.MuxTrace(ctx, "calling downstream server") + + return actionServer.ValidateActionConfig(ctx, req) +} diff --git a/tf6muxserver/mux_server_ValidateActionConfig_test.go b/tf6muxserver/mux_server_ValidateActionConfig_test.go new file mode 100644 index 0000000..99506d9 --- /dev/null +++ b/tf6muxserver/mux_server_ValidateActionConfig_test.go @@ -0,0 +1,78 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tf6muxserver_test + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + + "github.com/hashicorp/terraform-plugin-mux/internal/tf6testserver" + "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" +) + +func TestMuxServerValidateActionConfig(t *testing.T) { + t.Parallel() + + ctx := context.Background() + testServer1 := &tf6testserver.TestServer{ + GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{ + "test_action_server1": {}, + }, + }, + } + testServer2 := &tf6testserver.TestServer{ + GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{ + "test_action_server2": {}, + }, + }, + } + servers := []func() tfprotov6.ProviderServer{testServer1.ProviderServer, testServer2.ProviderServer} + muxServer, err := tf6muxserver.NewMuxServer(ctx, servers...) + + if err != nil { + t.Fatalf("unexpected error setting up factory: %s", err) + } + + //nolint:staticcheck // Intentionally verifying interface implementation + actionServer, ok := muxServer.ProviderServer().(tfprotov6.ProviderServerWithActions) + if !ok { + t.Fatal("muxServer should implement tfprotov6.ProviderServerWithActions") + } + + _, err = actionServer.ValidateActionConfig(ctx, &tfprotov6.ValidateActionConfigRequest{ + ActionType: "test_action_server1", + }) + + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + if !testServer1.ValidateActionConfigCalled["test_action_server1"] { + t.Errorf("expected test_action_server1 ValidateActionConfig to be called on server1") + } + + if testServer2.ValidateActionConfigCalled["test_action_server1"] { + t.Errorf("unexpected test_action_server1 ValidateActionConfig called on server2") + } + + _, err = actionServer.ValidateActionConfig(ctx, &tfprotov6.ValidateActionConfigRequest{ + ActionType: "test_action_server2", + }) + + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + if testServer1.ValidateActionConfigCalled["test_action_server2"] { + t.Errorf("unexpected test_action_server2 ValidateActionConfig called on server1") + } + + if !testServer2.ValidateActionConfigCalled["test_action_server2"] { + t.Errorf("expected test_action_server2 ValidateActionConfig to be called on server2") + } +} diff --git a/tf6to5server/tf6to5server.go b/tf6to5server/tf6to5server.go index 293daaf..7aea78d 100644 --- a/tf6to5server/tf6to5server.go +++ b/tf6to5server/tf6to5server.go @@ -353,6 +353,35 @@ func (s v6tov5Server) ListResource(ctx context.Context, req *tfprotov5.ListResou return tfprotov6tov5.ListResourceServerStream(v6Resp), nil } +func (s v6tov5Server) ValidateActionConfig(ctx context.Context, req *tfprotov5.ValidateActionConfigRequest) (*tfprotov5.ValidateActionConfigResponse, error) { + // TODO: Remove and call s.v6Server.ValidateActionConfig below directly once interface becomes required + actionServer, ok := s.v6Server.(tfprotov6.ActionServer) + if !ok { + v5Resp := &tfprotov5.ValidateActionConfigResponse{ + Diagnostics: []*tfprotov5.Diagnostic{ + { + Severity: tfprotov5.DiagnosticSeverityError, + Summary: "ValidateActionConfig Not Implemented", + Detail: "A ValidateActionConfig call was received by the provider, however the provider does not implement the RPC. " + + "Either upgrade the provider to a version that implements ValidateActionConfig or this is a bug in Terraform that should be reported to the Terraform maintainers.", + }, + }, + } + + return v5Resp, nil + } + + v6Req := tfprotov5tov6.ValidateActionConfigRequest(req) + + // v6Resp, err := s.v6Server.ValidateActionConfig(ctx, v6Req) + v6Resp, err := actionServer.ValidateActionConfig(ctx, v6Req) + if err != nil { + return nil, err + } + + return tfprotov6tov5.ValidateActionConfigResponse(v6Resp), nil +} + func (s v6tov5Server) PlanAction(ctx context.Context, req *tfprotov5.PlanActionRequest) (*tfprotov5.PlanActionResponse, error) { // TODO: Remove and call s.v6Server.PlanAction below directly once interface becomes required actionServer, ok := s.v6Server.(tfprotov6.ActionServer) diff --git a/tf6to5server/tf6to5server_test.go b/tf6to5server/tf6to5server_test.go index e709656..3946fbc 100644 --- a/tf6to5server/tf6to5server_test.go +++ b/tf6to5server/tf6to5server_test.go @@ -977,6 +977,43 @@ func TestV6ToV5ServerListResource(t *testing.T) { } } +func TestV6ToV5ServerValidateActionConfig(t *testing.T) { + t.Parallel() + + ctx := context.Background() + v6server := &tf6testserver.TestServer{ + GetProviderSchemaResponse: &tfprotov6.GetProviderSchemaResponse{ + ResourceSchemas: map[string]*tfprotov6.Schema{ + "test_action": {}, + }, + }, + } + + v5server, err := tf6to5server.DowngradeServer(context.Background(), v6server.ProviderServer) + + if err != nil { + t.Fatalf("unexpected error downgrading server: %s", err) + } + + //nolint:staticcheck // Intentionally verifying interface implementation + actionServer, ok := v5server.(tfprotov5.ProviderServerWithActions) + if !ok { + t.Fatal("v5server should implement tfprotov5.ProviderServerWithActions") + } + + _, err = actionServer.ValidateActionConfig(ctx, &tfprotov5.ValidateActionConfigRequest{ + ActionType: "test_action", + }) + + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + if !v6server.ValidateActionConfigCalled["test_action"] { + t.Errorf("expected test_action ValidateActionConfig to be called") + } +} + func TestV6ToV5ServerPlanAction(t *testing.T) { t.Parallel()