github.com/opentofu/opentofu@v1.7.1/internal/cloudplugin/cloudplugin1/grpc_client.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package cloudplugin1 7 8 import ( 9 "context" 10 "fmt" 11 "io" 12 "log" 13 14 "github.com/opentofu/opentofu/internal/cloudplugin" 15 "github.com/opentofu/opentofu/internal/cloudplugin/cloudproto1" 16 ) 17 18 // GRPCCloudClient is the client interface for interacting with terraform-cloudplugin 19 type GRPCCloudClient struct { 20 client cloudproto1.CommandServiceClient 21 context context.Context 22 } 23 24 // Proof that GRPCCloudClient fulfills the go-plugin interface 25 var _ cloudplugin.Cloud1 = GRPCCloudClient{} 26 27 // Execute sends the client Execute request and waits for the plugin to return 28 // an exit code response before returning 29 func (c GRPCCloudClient) Execute(args []string, stdout, stderr io.Writer) int { 30 client, err := c.client.Execute(c.context, &cloudproto1.CommandRequest{ 31 Args: args, 32 }) 33 34 if err != nil { 35 fmt.Fprint(stderr, err.Error()) 36 return 1 37 } 38 39 for { 40 // cloudplugin streams output as multiple CommandResponse value. Each 41 // value will either contain stdout bytes, stderr bytes, or an exit code. 42 response, err := client.Recv() 43 if err == io.EOF { 44 log.Print("[DEBUG] received EOF from cloudplugin") 45 break 46 } else if err != nil { 47 fmt.Fprintf(stderr, "Failed to receive command response from cloudplugin: %s", err) 48 return 1 49 } 50 51 if bytes := response.GetStdout(); len(bytes) > 0 { 52 _, err := fmt.Fprint(stdout, string(bytes)) 53 if err != nil { 54 log.Printf("[ERROR] Failed to write cloudplugin output to stdout: %s", err) 55 return 1 56 } 57 } else if bytes := response.GetStderr(); len(bytes) > 0 { 58 fmt.Fprint(stderr, string(bytes)) 59 if err != nil { 60 log.Printf("[ERROR] Failed to write cloudplugin output to stderr: %s", err) 61 return 1 62 } 63 } else { 64 exitCode := response.GetExitCode() 65 log.Printf("[TRACE] received exit code: %d", exitCode) 66 if exitCode < 0 || exitCode > 255 { 67 log.Printf("[ERROR] cloudplugin returned an invalid error code %d", exitCode) 68 return 255 69 } 70 return int(exitCode) 71 } 72 } 73 74 // This should indicate a bug in the plugin 75 fmt.Fprint(stderr, "cloudplugin exited without responding with an error code") 76 return 1 77 }