github.com/opentofu/opentofu@v1.7.1/internal/command/cloud.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 command
     7  
     8  import (
     9  	"bytes"
    10  	"fmt"
    11  	"io"
    12  	"os"
    13  	"os/exec"
    14  
    15  	"github.com/hashicorp/go-plugin"
    16  
    17  	"github.com/opentofu/opentofu/internal/cloudplugin"
    18  	"github.com/opentofu/opentofu/internal/cloudplugin/cloudplugin1"
    19  	"github.com/opentofu/opentofu/internal/logging"
    20  )
    21  
    22  // CloudCommand is a Command implementation that interacts with Terraform
    23  // Cloud for operations that are inherently planless. It delegates
    24  // all execution to an internal plugin.
    25  type CloudCommand struct {
    26  	Meta
    27  }
    28  
    29  const (
    30  	// DefaultCloudPluginVersion is the implied protocol version, though all
    31  	// historical versions are defined explicitly.
    32  	DefaultCloudPluginVersion = 1
    33  
    34  	// ExitRPCError is the exit code that is returned if an plugin
    35  	// communication error occurred.
    36  	ExitRPCError = 99
    37  )
    38  
    39  var (
    40  	// Handshake is used to verify that the plugin is the appropriate plugin for
    41  	// the client. This is not a security verification.
    42  	Handshake = plugin.HandshakeConfig{
    43  		MagicCookieKey:   "TF_CLOUDPLUGIN_MAGIC_COOKIE",
    44  		MagicCookieValue: "721fca41431b780ff3ad2623838faaa178d74c65e1cfdfe19537c31656496bf9f82d6c6707f71d81c8eed0db9043f79e56ab4582d013bc08ead14f57961461dc",
    45  		ProtocolVersion:  DefaultCloudPluginVersion,
    46  	}
    47  )
    48  
    49  func (c *CloudCommand) proxy(args []string, stdout, stderr io.Writer) int {
    50  	client := plugin.NewClient(&plugin.ClientConfig{
    51  		HandshakeConfig:  Handshake,
    52  		AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
    53  		Cmd:              exec.Command("./terraform-cloudplugin"),
    54  		Logger:           logging.NewCloudLogger(),
    55  		VersionedPlugins: map[int]plugin.PluginSet{
    56  			1: {
    57  				"cloud": &cloudplugin1.GRPCCloudPlugin{},
    58  			},
    59  		},
    60  	})
    61  	defer client.Kill()
    62  
    63  	// Connect via RPC
    64  	rpcClient, err := client.Client()
    65  	if err != nil {
    66  		fmt.Fprintf(stderr, "Failed to create cloud plugin client: %s", err)
    67  		return ExitRPCError
    68  	}
    69  
    70  	// Request the plugin
    71  	raw, err := rpcClient.Dispense("cloud")
    72  	if err != nil {
    73  		fmt.Fprintf(stderr, "Failed to request cloud plugin interface: %s", err)
    74  		return ExitRPCError
    75  	}
    76  
    77  	// Proxy the request
    78  	// Note: future changes will need to determine the type of raw when
    79  	// multiple versions are possible.
    80  	cloud1, ok := raw.(cloudplugin.Cloud1)
    81  	if !ok {
    82  		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.")
    83  		return ExitRPCError
    84  	}
    85  	return cloud1.Execute(args, stdout, stderr)
    86  }
    87  
    88  // Run runs the cloud command with the given arguments.
    89  func (c *CloudCommand) Run(args []string) int {
    90  	args = c.Meta.process(args)
    91  
    92  	// TODO: Download and verify the signing of the terraform-cloudplugin
    93  	// release that is appropriate for this OS/Arch
    94  	if _, err := os.Stat("./terraform-cloudplugin"); err != nil {
    95  		c.Ui.Warn("terraform-cloudplugin not found. This plugin does not have an official release yet.")
    96  		return 1
    97  	}
    98  
    99  	// TODO: Need to use some type of c.Meta handle here
   100  	return c.proxy(args, os.Stdout, os.Stderr)
   101  }
   102  
   103  // Help returns help text for the cloud command.
   104  func (c *CloudCommand) Help() string {
   105  	helpText := new(bytes.Buffer)
   106  	c.proxy([]string{}, helpText, io.Discard)
   107  
   108  	return helpText.String()
   109  }
   110  
   111  // Synopsis returns a short summary of the cloud command.
   112  func (c *CloudCommand) Synopsis() string {
   113  	return "Manage cloud backend settings and metadata"
   114  }