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 }