opentofu/internal/cloudplugin/cloudplugin1/grpc_client.go

73 lines
2.0 KiB
Go

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 {
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
}