cloudplugin: Remove incomplete stub of "cloud" subcommand

We seem to have inherited an incomplete implementation of something from
the predecessor project here: a "tofu cloud" command that just tries to
immediately delegate any invocation to another executable called
"terraform-cloudplugin" in the current working directory, used as a
go-plugin style plugin.

This has some TODO comments suggesting that it was intended to change to
download a plugin from some remote place before executing it, but our
stubby version doesn't do that. I was also hidden behind an experimental
feature guard and so has never been accessible in any released version of
OpenTofu; we don't currently produce any releases with experimental
features enabled.

Therefore this commit just deletes it so that we don't have this dead code
to potentially worry about. Perhaps one day we'll offer some extension
point for adding extra subcommands through plugins, but if we do that then
we'll presumably design our own mechanism for doing it rather than
extending this dead code that was added for reasons unknown to us.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
This commit is contained in:
Martin Atkins
2025-03-03 15:36:38 -08:00
parent e43390b120
commit ceaab30ba1
10 changed files with 0 additions and 1026 deletions

View File

@@ -443,14 +443,6 @@ func initCommands(
},
}
if meta.AllowExperimentalFeatures {
commands["cloud"] = func() (cli.Command, error) {
return &command.CloudCommand{
Meta: meta,
}, nil
}
}
primaryCommands = []string{
"init",
"validate",

View File

@@ -1,77 +0,0 @@
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package cloudplugin1
import (
"context"
"fmt"
"io"
"log"
"github.com/opentofu/opentofu/internal/cloudplugin"
"github.com/opentofu/opentofu/internal/cloudplugin/cloudproto1"
)
// GRPCCloudClient is the client interface for interacting with terraform-cloudplugin
type GRPCCloudClient struct {
client cloudproto1.CommandServiceClient
context context.Context
}
// Proof that GRPCCloudClient fulfills the go-plugin interface
var _ cloudplugin.Cloud1 = GRPCCloudClient{}
// Execute sends the client Execute request and waits for the plugin to return
// an exit code response before returning
func (c GRPCCloudClient) Execute(args []string, stdout, stderr io.Writer) int {
client, err := c.client.Execute(c.context, &cloudproto1.CommandRequest{
Args: args,
})
if err != nil {
fmt.Fprint(stderr, err.Error())
return 1
}
for {
// cloudplugin streams output as multiple CommandResponse value. Each
// value will either contain stdout bytes, stderr bytes, or an exit code.
response, err := client.Recv()
if err == io.EOF {
log.Print("[DEBUG] received EOF from cloudplugin")
break
} else if err != nil {
fmt.Fprintf(stderr, "Failed to receive command response from cloudplugin: %s", err)
return 1
}
if bytes := response.GetStdout(); len(bytes) > 0 {
_, err := fmt.Fprint(stdout, string(bytes))
if err != nil {
log.Printf("[ERROR] Failed to write cloudplugin output to stdout: %s", err)
return 1
}
} else if bytes := response.GetStderr(); len(bytes) > 0 {
_, err := fmt.Fprint(stderr, string(bytes))
if err != nil {
log.Printf("[ERROR] Failed to write cloudplugin output to stderr: %s", err)
return 1
}
} else {
exitCode := response.GetExitCode()
log.Printf("[TRACE] received exit code: %d", exitCode)
if exitCode < 0 || exitCode > 255 {
log.Printf("[ERROR] cloudplugin returned an invalid error code %d", exitCode)
return 255
}
return int(exitCode)
}
}
// This should indicate a bug in the plugin
fmt.Fprint(stderr, "cloudplugin exited without responding with an error code")
return 1
}

View File

@@ -1,142 +0,0 @@
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package cloudplugin1
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"testing"
"github.com/opentofu/opentofu/internal/cloudplugin/cloudproto1"
"github.com/opentofu/opentofu/internal/cloudplugin/mock_cloudproto1"
"go.uber.org/mock/gomock"
)
var mockError = "this is a mock error"
func testGRPCloudClient(t *testing.T, ctrl *gomock.Controller, client *mock_cloudproto1.MockCommandService_ExecuteClient, executeError error) *GRPCCloudClient {
t.Helper()
if client != nil && executeError != nil {
t.Fatal("one of client or executeError must be nil")
}
result := mock_cloudproto1.NewMockCommandServiceClient(ctrl)
result.EXPECT().Execute(
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Return(client, executeError)
return &GRPCCloudClient{
client: result,
context: context.Background(),
}
}
func Test_GRPCCloudClient_ExecuteError(t *testing.T) {
ctrl := gomock.NewController(t)
gRPCClient := testGRPCloudClient(t, ctrl, nil, errors.New(mockError))
buffer := bytes.Buffer{}
exitCode := gRPCClient.Execute([]string{"example"}, io.Discard, &buffer)
if exitCode != 1 {
t.Fatalf("expected exit %d, got %d", 1, exitCode)
}
if buffer.String() != mockError {
t.Errorf("expected error %q, got %q", mockError, buffer.String())
}
}
func Test_GRPCCloudClient_Execute_RecvError(t *testing.T) {
ctrl := gomock.NewController(t)
executeClient := mock_cloudproto1.NewMockCommandService_ExecuteClient(ctrl)
executeClient.EXPECT().Recv().Return(nil, errors.New(mockError))
gRPCClient := testGRPCloudClient(t, ctrl, executeClient, nil)
buffer := bytes.Buffer{}
exitCode := gRPCClient.Execute([]string{"example"}, io.Discard, &buffer)
if exitCode != 1 {
t.Fatalf("expected exit %d, got %d", 1, exitCode)
}
mockRecvError := fmt.Sprintf("Failed to receive command response from cloudplugin: %s", mockError)
if buffer.String() != mockRecvError {
t.Errorf("expected error %q, got %q", mockRecvError, buffer.String())
}
}
func Test_GRPCCloudClient_Execute_Invalid_Exit(t *testing.T) {
ctrl := gomock.NewController(t)
executeClient := mock_cloudproto1.NewMockCommandService_ExecuteClient(ctrl)
executeClient.EXPECT().Recv().Return(
&cloudproto1.CommandResponse{
Data: &cloudproto1.CommandResponse_ExitCode{
ExitCode: 3_000,
},
}, nil,
)
gRPCClient := testGRPCloudClient(t, ctrl, executeClient, nil)
exitCode := gRPCClient.Execute([]string{"example"}, io.Discard, io.Discard)
if exitCode != 255 {
t.Fatalf("expected exit %q, got %q", 255, exitCode)
}
}
func Test_GRPCCloudClient_Execute(t *testing.T) {
ctrl := gomock.NewController(t)
executeClient := mock_cloudproto1.NewMockCommandService_ExecuteClient(ctrl)
gomock.InOrder(
executeClient.EXPECT().Recv().Return(
&cloudproto1.CommandResponse{
Data: &cloudproto1.CommandResponse_Stdout{
Stdout: []byte("firstcall\n"),
},
}, nil,
),
executeClient.EXPECT().Recv().Return(
&cloudproto1.CommandResponse{
Data: &cloudproto1.CommandResponse_Stdout{
Stdout: []byte("secondcall\n"),
},
}, nil,
),
executeClient.EXPECT().Recv().Return(
&cloudproto1.CommandResponse{
Data: &cloudproto1.CommandResponse_ExitCode{
ExitCode: 99,
},
}, nil,
),
)
gRPCClient := testGRPCloudClient(t, ctrl, executeClient, nil)
stdoutBuffer := bytes.Buffer{}
exitCode := gRPCClient.Execute([]string{"example"}, &stdoutBuffer, io.Discard)
if exitCode != 99 {
t.Fatalf("expected exit %q, got %q", 99, exitCode)
}
if stdoutBuffer.String() != "firstcall\nsecondcall\n" {
t.Errorf("expected output %q, got %q", "firstcall\nsecondcall\n", stdoutBuffer.String())
}
}

View File

@@ -1,50 +0,0 @@
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package cloudplugin1
import (
"context"
"errors"
"net/rpc"
"github.com/hashicorp/go-plugin"
"github.com/opentofu/opentofu/internal/cloudplugin"
"github.com/opentofu/opentofu/internal/cloudplugin/cloudproto1"
"google.golang.org/grpc"
)
// GRPCCloudPlugin is the go-plugin implementation, but only the client
// implementation exists in this package.
type GRPCCloudPlugin struct {
plugin.GRPCPlugin
Impl cloudplugin.Cloud1
}
// Server always returns an error; we're only implementing the GRPCPlugin
// interface, not the Plugin interface.
func (p *GRPCCloudPlugin) Server(*plugin.MuxBroker) (interface{}, error) {
return nil, errors.New("cloudplugin only implements gRPC clients")
}
// Client always returns an error; we're only implementing the GRPCPlugin
// interface, not the Plugin interface.
func (p *GRPCCloudPlugin) Client(*plugin.MuxBroker, *rpc.Client) (interface{}, error) {
return nil, errors.New("cloudplugin only implements gRPC clients")
}
// GRPCServer always returns an error; we're only implementing the client
// interface, not the server.
func (p *GRPCCloudPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error {
return errors.New("cloudplugin only implements gRPC clients")
}
// GRPCClient returns a new GRPC client for interacting with the cloud plugin server.
func (p *GRPCCloudPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) {
return &GRPCCloudClient{
client: cloudproto1.NewCommandServiceClient(c),
context: ctx,
}, nil
}

View File

@@ -1,395 +0,0 @@
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc v3.15.6
// source: cloudproto1.proto
package cloudproto1
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// CommandRequest is used to request the execution of a specific command with
// provided flags. It is the raw args from the terraform cloud command.
type CommandRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Args []string `protobuf:"bytes,1,rep,name=args,proto3" json:"args,omitempty"`
}
func (x *CommandRequest) Reset() {
*x = CommandRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_cloudproto1_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CommandRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CommandRequest) ProtoMessage() {}
func (x *CommandRequest) ProtoReflect() protoreflect.Message {
mi := &file_cloudproto1_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CommandRequest.ProtoReflect.Descriptor instead.
func (*CommandRequest) Descriptor() ([]byte, []int) {
return file_cloudproto1_proto_rawDescGZIP(), []int{0}
}
func (x *CommandRequest) GetArgs() []string {
if x != nil {
return x.Args
}
return nil
}
// CommandResponse contains the result of the command execution, including any
// output or errors.
type CommandResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Data:
//
// *CommandResponse_ExitCode
// *CommandResponse_Stdout
// *CommandResponse_Stderr
Data isCommandResponse_Data `protobuf_oneof:"data"`
}
func (x *CommandResponse) Reset() {
*x = CommandResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_cloudproto1_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CommandResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CommandResponse) ProtoMessage() {}
func (x *CommandResponse) ProtoReflect() protoreflect.Message {
mi := &file_cloudproto1_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CommandResponse.ProtoReflect.Descriptor instead.
func (*CommandResponse) Descriptor() ([]byte, []int) {
return file_cloudproto1_proto_rawDescGZIP(), []int{1}
}
func (m *CommandResponse) GetData() isCommandResponse_Data {
if m != nil {
return m.Data
}
return nil
}
func (x *CommandResponse) GetExitCode() int32 {
if x, ok := x.GetData().(*CommandResponse_ExitCode); ok {
return x.ExitCode
}
return 0
}
func (x *CommandResponse) GetStdout() []byte {
if x, ok := x.GetData().(*CommandResponse_Stdout); ok {
return x.Stdout
}
return nil
}
func (x *CommandResponse) GetStderr() []byte {
if x, ok := x.GetData().(*CommandResponse_Stderr); ok {
return x.Stderr
}
return nil
}
type isCommandResponse_Data interface {
isCommandResponse_Data()
}
type CommandResponse_ExitCode struct {
ExitCode int32 `protobuf:"varint,1,opt,name=exitCode,proto3,oneof"`
}
type CommandResponse_Stdout struct {
Stdout []byte `protobuf:"bytes,2,opt,name=stdout,proto3,oneof"`
}
type CommandResponse_Stderr struct {
Stderr []byte `protobuf:"bytes,3,opt,name=stderr,proto3,oneof"`
}
func (*CommandResponse_ExitCode) isCommandResponse_Data() {}
func (*CommandResponse_Stdout) isCommandResponse_Data() {}
func (*CommandResponse_Stderr) isCommandResponse_Data() {}
var File_cloudproto1_proto protoreflect.FileDescriptor
var file_cloudproto1_proto_rawDesc = []byte{
0x0a, 0x11, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x31, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x31,
0x22, 0x24, 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x72, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09,
0x52, 0x04, 0x61, 0x72, 0x67, 0x73, 0x22, 0x6b, 0x0a, 0x0f, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e,
0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x08, 0x65, 0x78, 0x69,
0x74, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x08, 0x65,
0x78, 0x69, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x06, 0x73, 0x74, 0x64, 0x6f, 0x75,
0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x06, 0x73, 0x74, 0x64, 0x6f, 0x75,
0x74, 0x12, 0x18, 0x0a, 0x06, 0x73, 0x74, 0x64, 0x65, 0x72, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28,
0x0c, 0x48, 0x00, 0x52, 0x06, 0x73, 0x74, 0x64, 0x65, 0x72, 0x72, 0x42, 0x06, 0x0a, 0x04, 0x64,
0x61, 0x74, 0x61, 0x32, 0x5a, 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x53, 0x65,
0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x48, 0x0a, 0x07, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65,
0x12, 0x1b, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x31, 0x2e, 0x43,
0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e,
0x63, 0x6c, 0x6f, 0x75, 0x64, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d,
0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x42,
0x3f, 0x5a, 0x3d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70,
0x65, 0x6e, 0x74, 0x6f, 0x66, 0x75, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x74, 0x6f, 0x66, 0x75, 0x2f,
0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x70, 0x6c,
0x75, 0x67, 0x69, 0x6e, 0x2f, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x31,
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_cloudproto1_proto_rawDescOnce sync.Once
file_cloudproto1_proto_rawDescData = file_cloudproto1_proto_rawDesc
)
func file_cloudproto1_proto_rawDescGZIP() []byte {
file_cloudproto1_proto_rawDescOnce.Do(func() {
file_cloudproto1_proto_rawDescData = protoimpl.X.CompressGZIP(file_cloudproto1_proto_rawDescData)
})
return file_cloudproto1_proto_rawDescData
}
var file_cloudproto1_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_cloudproto1_proto_goTypes = []interface{}{
(*CommandRequest)(nil), // 0: cloudproto1.CommandRequest
(*CommandResponse)(nil), // 1: cloudproto1.CommandResponse
}
var file_cloudproto1_proto_depIdxs = []int32{
0, // 0: cloudproto1.CommandService.Execute:input_type -> cloudproto1.CommandRequest
1, // 1: cloudproto1.CommandService.Execute:output_type -> cloudproto1.CommandResponse
1, // [1:2] is the sub-list for method output_type
0, // [0:1] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_cloudproto1_proto_init() }
func file_cloudproto1_proto_init() {
if File_cloudproto1_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_cloudproto1_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*CommandRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_cloudproto1_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*CommandResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_cloudproto1_proto_msgTypes[1].OneofWrappers = []interface{}{
(*CommandResponse_ExitCode)(nil),
(*CommandResponse_Stdout)(nil),
(*CommandResponse_Stderr)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_cloudproto1_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_cloudproto1_proto_goTypes,
DependencyIndexes: file_cloudproto1_proto_depIdxs,
MessageInfos: file_cloudproto1_proto_msgTypes,
}.Build()
File_cloudproto1_proto = out.File
file_cloudproto1_proto_rawDesc = nil
file_cloudproto1_proto_goTypes = nil
file_cloudproto1_proto_depIdxs = nil
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConnInterface
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion6
// CommandServiceClient is the client API for CommandService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type CommandServiceClient interface {
// Execute runs a specific command with the provided flags and returns the result.
Execute(ctx context.Context, in *CommandRequest, opts ...grpc.CallOption) (CommandService_ExecuteClient, error)
}
type commandServiceClient struct {
cc grpc.ClientConnInterface
}
func NewCommandServiceClient(cc grpc.ClientConnInterface) CommandServiceClient {
return &commandServiceClient{cc}
}
func (c *commandServiceClient) Execute(ctx context.Context, in *CommandRequest, opts ...grpc.CallOption) (CommandService_ExecuteClient, error) {
stream, err := c.cc.NewStream(ctx, &_CommandService_serviceDesc.Streams[0], "/cloudproto1.CommandService/Execute", opts...)
if err != nil {
return nil, err
}
x := &commandServiceExecuteClient{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 CommandService_ExecuteClient interface {
Recv() (*CommandResponse, error)
grpc.ClientStream
}
type commandServiceExecuteClient struct {
grpc.ClientStream
}
func (x *commandServiceExecuteClient) Recv() (*CommandResponse, error) {
m := new(CommandResponse)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// CommandServiceServer is the server API for CommandService service.
type CommandServiceServer interface {
// Execute runs a specific command with the provided flags and returns the result.
Execute(*CommandRequest, CommandService_ExecuteServer) error
}
// UnimplementedCommandServiceServer can be embedded to have forward compatible implementations.
type UnimplementedCommandServiceServer struct {
}
func (*UnimplementedCommandServiceServer) Execute(*CommandRequest, CommandService_ExecuteServer) error {
return status.Errorf(codes.Unimplemented, "method Execute not implemented")
}
func RegisterCommandServiceServer(s *grpc.Server, srv CommandServiceServer) {
s.RegisterService(&_CommandService_serviceDesc, srv)
}
func _CommandService_Execute_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(CommandRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(CommandServiceServer).Execute(m, &commandServiceExecuteServer{stream})
}
type CommandService_ExecuteServer interface {
Send(*CommandResponse) error
grpc.ServerStream
}
type commandServiceExecuteServer struct {
grpc.ServerStream
}
func (x *commandServiceExecuteServer) Send(m *CommandResponse) error {
return x.ServerStream.SendMsg(m)
}
var _CommandService_serviceDesc = grpc.ServiceDesc{
ServiceName: "cloudproto1.CommandService",
HandlerType: (*CommandServiceServer)(nil),
Methods: []grpc.MethodDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "Execute",
Handler: _CommandService_Execute_Handler,
ServerStreams: true,
},
},
Metadata: "cloudproto1.proto",
}

View File

@@ -1,32 +0,0 @@
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
syntax = "proto3";
package cloudproto1;
option go_package = "github.com/opentofu/opentofu/internal/cloudplugin/cloudproto1";
// CommandRequest is used to request the execution of a specific command with
// provided flags. It is the raw args from the terraform cloud command.
message CommandRequest {
repeated string args = 1;
}
// CommandResponse contains the result of the command execution, including any
// output or errors.
message CommandResponse {
oneof data {
int32 exitCode = 1;
bytes stdout = 2;
bytes stderr = 3;
}
}
// PluginService defines the gRPC service to handle available commands and
// their execution.
service CommandService {
// Execute runs a specific command with the provided flags and returns the result.
rpc Execute(CommandRequest) returns (stream CommandResponse) {}
}

View File

@@ -1,14 +0,0 @@
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package cloudplugin
import (
"io"
)
type Cloud1 interface {
Execute(args []string, stdout, stderr io.Writer) int
}

View File

@@ -1,8 +0,0 @@
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
//go:generate go run go.uber.org/mock/mockgen -destination mock.go github.com/opentofu/opentofu/internal/cloudplugin/cloudproto1 CommandServiceClient,CommandService_ExecuteClient
package mock_cloudproto1

View File

@@ -1,186 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/opentofu/opentofu/internal/cloudplugin/cloudproto1 (interfaces: CommandServiceClient,CommandService_ExecuteClient)
//
// Generated by this command:
//
// mockgen -destination mock.go github.com/opentofu/opentofu/internal/cloudplugin/cloudproto1 CommandServiceClient,CommandService_ExecuteClient
//
// Package mock_cloudproto1 is a generated GoMock package.
package mock_cloudproto1
import (
context "context"
reflect "reflect"
cloudproto1 "github.com/opentofu/opentofu/internal/cloudplugin/cloudproto1"
gomock "go.uber.org/mock/gomock"
grpc "google.golang.org/grpc"
metadata "google.golang.org/grpc/metadata"
)
// MockCommandServiceClient is a mock of CommandServiceClient interface.
type MockCommandServiceClient struct {
ctrl *gomock.Controller
recorder *MockCommandServiceClientMockRecorder
}
// MockCommandServiceClientMockRecorder is the mock recorder for MockCommandServiceClient.
type MockCommandServiceClientMockRecorder struct {
mock *MockCommandServiceClient
}
// NewMockCommandServiceClient creates a new mock instance.
func NewMockCommandServiceClient(ctrl *gomock.Controller) *MockCommandServiceClient {
mock := &MockCommandServiceClient{ctrl: ctrl}
mock.recorder = &MockCommandServiceClientMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockCommandServiceClient) EXPECT() *MockCommandServiceClientMockRecorder {
return m.recorder
}
// Execute mocks base method.
func (m *MockCommandServiceClient) Execute(arg0 context.Context, arg1 *cloudproto1.CommandRequest, arg2 ...grpc.CallOption) (cloudproto1.CommandService_ExecuteClient, error) {
m.ctrl.T.Helper()
varargs := []any{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "Execute", varargs...)
ret0, _ := ret[0].(cloudproto1.CommandService_ExecuteClient)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Execute indicates an expected call of Execute.
func (mr *MockCommandServiceClientMockRecorder) Execute(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, "Execute", reflect.TypeOf((*MockCommandServiceClient)(nil).Execute), varargs...)
}
// MockCommandService_ExecuteClient is a mock of CommandService_ExecuteClient interface.
type MockCommandService_ExecuteClient struct {
ctrl *gomock.Controller
recorder *MockCommandService_ExecuteClientMockRecorder
}
// MockCommandService_ExecuteClientMockRecorder is the mock recorder for MockCommandService_ExecuteClient.
type MockCommandService_ExecuteClientMockRecorder struct {
mock *MockCommandService_ExecuteClient
}
// NewMockCommandService_ExecuteClient creates a new mock instance.
func NewMockCommandService_ExecuteClient(ctrl *gomock.Controller) *MockCommandService_ExecuteClient {
mock := &MockCommandService_ExecuteClient{ctrl: ctrl}
mock.recorder = &MockCommandService_ExecuteClientMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockCommandService_ExecuteClient) EXPECT() *MockCommandService_ExecuteClientMockRecorder {
return m.recorder
}
// CloseSend mocks base method.
func (m *MockCommandService_ExecuteClient) 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 *MockCommandService_ExecuteClientMockRecorder) CloseSend() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseSend", reflect.TypeOf((*MockCommandService_ExecuteClient)(nil).CloseSend))
}
// Context mocks base method.
func (m *MockCommandService_ExecuteClient) 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 *MockCommandService_ExecuteClientMockRecorder) Context() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Context", reflect.TypeOf((*MockCommandService_ExecuteClient)(nil).Context))
}
// Header mocks base method.
func (m *MockCommandService_ExecuteClient) 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 *MockCommandService_ExecuteClientMockRecorder) Header() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Header", reflect.TypeOf((*MockCommandService_ExecuteClient)(nil).Header))
}
// Recv mocks base method.
func (m *MockCommandService_ExecuteClient) Recv() (*cloudproto1.CommandResponse, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Recv")
ret0, _ := ret[0].(*cloudproto1.CommandResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Recv indicates an expected call of Recv.
func (mr *MockCommandService_ExecuteClientMockRecorder) Recv() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Recv", reflect.TypeOf((*MockCommandService_ExecuteClient)(nil).Recv))
}
// RecvMsg mocks base method.
func (m *MockCommandService_ExecuteClient) 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 *MockCommandService_ExecuteClientMockRecorder) RecvMsg(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecvMsg", reflect.TypeOf((*MockCommandService_ExecuteClient)(nil).RecvMsg), arg0)
}
// SendMsg mocks base method.
func (m *MockCommandService_ExecuteClient) 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 *MockCommandService_ExecuteClientMockRecorder) SendMsg(arg0 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMsg", reflect.TypeOf((*MockCommandService_ExecuteClient)(nil).SendMsg), arg0)
}
// Trailer mocks base method.
func (m *MockCommandService_ExecuteClient) 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 *MockCommandService_ExecuteClientMockRecorder) Trailer() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Trailer", reflect.TypeOf((*MockCommandService_ExecuteClient)(nil).Trailer))
}

View File

@@ -1,114 +0,0 @@
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package command
import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
"github.com/hashicorp/go-plugin"
"github.com/opentofu/opentofu/internal/cloudplugin"
"github.com/opentofu/opentofu/internal/cloudplugin/cloudplugin1"
"github.com/opentofu/opentofu/internal/logging"
)
// CloudCommand is a Command implementation that interacts with Terraform
// Cloud for operations that are inherently planless. It delegates
// all execution to an internal plugin.
type CloudCommand struct {
Meta
}
const (
// DefaultCloudPluginVersion is the implied protocol version, though all
// historical versions are defined explicitly.
DefaultCloudPluginVersion = 1
// ExitRPCError is the exit code that is returned if an plugin
// communication error occurred.
ExitRPCError = 99
)
var (
// Handshake is used to verify that the plugin is the appropriate plugin for
// the client. This is not a security verification.
Handshake = plugin.HandshakeConfig{
MagicCookieKey: "TF_CLOUDPLUGIN_MAGIC_COOKIE",
MagicCookieValue: "721fca41431b780ff3ad2623838faaa178d74c65e1cfdfe19537c31656496bf9f82d6c6707f71d81c8eed0db9043f79e56ab4582d013bc08ead14f57961461dc",
ProtocolVersion: DefaultCloudPluginVersion,
}
)
func (c *CloudCommand) proxy(args []string, stdout, stderr io.Writer) int {
client := plugin.NewClient(&plugin.ClientConfig{
HandshakeConfig: Handshake,
AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
Cmd: exec.Command("./terraform-cloudplugin"),
Logger: logging.NewCloudLogger(),
VersionedPlugins: map[int]plugin.PluginSet{
1: {
"cloud": &cloudplugin1.GRPCCloudPlugin{},
},
},
})
defer client.Kill()
// Connect via RPC
rpcClient, err := client.Client()
if err != nil {
fmt.Fprintf(stderr, "Failed to create cloud plugin client: %s", err)
return ExitRPCError
}
// Request the plugin
raw, err := rpcClient.Dispense("cloud")
if err != nil {
fmt.Fprintf(stderr, "Failed to request cloud plugin interface: %s", err)
return ExitRPCError
}
// Proxy the request
// Note: future changes will need to determine the type of raw when
// multiple versions are possible.
cloud1, ok := raw.(cloudplugin.Cloud1)
if !ok {
c.Ui.Error("If more than one cloudplugin versions are available, they need to be added to the cloud command. This is a bug in OpenTofu.")
return ExitRPCError
}
return cloud1.Execute(args, stdout, stderr)
}
// Run runs the cloud command with the given arguments.
func (c *CloudCommand) Run(args []string) int {
args = c.Meta.process(args)
// TODO: Download and verify the signing of the terraform-cloudplugin
// release that is appropriate for this OS/Arch
if _, err := os.Stat("./terraform-cloudplugin"); err != nil {
c.Ui.Warn("terraform-cloudplugin not found. This plugin does not have an official release yet.")
return 1
}
// TODO: Need to use some type of c.Meta handle here
return c.proxy(args, os.Stdout, os.Stderr)
}
// Help returns help text for the cloud command.
func (c *CloudCommand) Help() string {
helpText := new(bytes.Buffer)
c.proxy([]string{}, helpText, io.Discard)
return helpText.String()
}
// Synopsis returns a short summary of the cloud command.
func (c *CloudCommand) Synopsis() string {
return "Manage cloud backend settings and metadata"
}