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  }