github.com/opentofu/opentofu@v1.7.1/internal/command/plugins.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  	"fmt"
    10  	"log"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"runtime"
    14  
    15  	plugin "github.com/hashicorp/go-plugin"
    16  	"github.com/kardianos/osext"
    17  
    18  	fileprovisioner "github.com/opentofu/opentofu/internal/builtin/provisioners/file"
    19  	localexec "github.com/opentofu/opentofu/internal/builtin/provisioners/local-exec"
    20  	remoteexec "github.com/opentofu/opentofu/internal/builtin/provisioners/remote-exec"
    21  	"github.com/opentofu/opentofu/internal/logging"
    22  	tfplugin "github.com/opentofu/opentofu/internal/plugin"
    23  	"github.com/opentofu/opentofu/internal/plugin/discovery"
    24  	"github.com/opentofu/opentofu/internal/provisioners"
    25  )
    26  
    27  // NOTE WELL: The logic in this file is primarily about plugin types OTHER THAN
    28  // providers, which use an older set of approaches implemented here.
    29  //
    30  // The provider-related functions live primarily in meta_providers.go, and
    31  // lean on some different underlying mechanisms in order to support automatic
    32  // installation and a hierarchical addressing namespace, neither of which
    33  // are supported for other plugin types.
    34  
    35  // store the user-supplied path for plugin discovery
    36  func (m *Meta) storePluginPath(pluginPath []string) error {
    37  	if len(pluginPath) == 0 {
    38  		return nil
    39  	}
    40  
    41  	m.fixupMissingWorkingDir()
    42  
    43  	// remove the plugin dir record if the path was set to an empty string
    44  	if len(pluginPath) == 1 && (pluginPath[0] == "") {
    45  		return m.WorkingDir.SetForcedPluginDirs(nil)
    46  	}
    47  
    48  	return m.WorkingDir.SetForcedPluginDirs(pluginPath)
    49  }
    50  
    51  // Load the user-defined plugin search path into Meta.pluginPath if the file
    52  // exists.
    53  func (m *Meta) loadPluginPath() ([]string, error) {
    54  	m.fixupMissingWorkingDir()
    55  	return m.WorkingDir.ForcedPluginDirs()
    56  }
    57  
    58  // the default location for automatically installed plugins
    59  func (m *Meta) pluginDir() string {
    60  	return filepath.Join(m.DataDir(), "plugins", fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH))
    61  }
    62  
    63  // pluginDirs return a list of directories to search for plugins.
    64  //
    65  // Earlier entries in this slice get priority over later when multiple copies
    66  // of the same plugin version are found, but newer versions always override
    67  // older versions where both satisfy the provider version constraints.
    68  func (m *Meta) pluginDirs(includeAutoInstalled bool) []string {
    69  	// user defined paths take precedence
    70  	if len(m.pluginPath) > 0 {
    71  		return m.pluginPath
    72  	}
    73  
    74  	// When searching the following directories, earlier entries get precedence
    75  	// if the same plugin version is found twice, but newer versions will
    76  	// always get preference below regardless of where they are coming from.
    77  	// TODO: Add auto-install dir, default vendor dir and optional override
    78  	// vendor dir(s).
    79  	dirs := []string{"."}
    80  
    81  	// Look in the same directory as the OpenTofu executable.
    82  	// If found, this replaces what we found in the config path.
    83  	exePath, err := osext.Executable()
    84  	if err != nil {
    85  		log.Printf("[ERROR] Error discovering exe directory: %s", err)
    86  	} else {
    87  		dirs = append(dirs, filepath.Dir(exePath))
    88  	}
    89  
    90  	// add the user vendor directory
    91  	dirs = append(dirs, DefaultPluginVendorDir)
    92  
    93  	if includeAutoInstalled {
    94  		dirs = append(dirs, m.pluginDir())
    95  	}
    96  	dirs = append(dirs, m.GlobalPluginDirs...)
    97  
    98  	return dirs
    99  }
   100  
   101  func (m *Meta) provisionerFactories() map[string]provisioners.Factory {
   102  	dirs := m.pluginDirs(true)
   103  	plugins := discovery.FindPlugins("provisioner", dirs)
   104  	plugins, _ = plugins.ValidateVersions()
   105  
   106  	// For now our goal is to just find the latest version of each plugin
   107  	// we have on the system. All provisioners should be at version 0.0.0
   108  	// currently, so there should actually only be one instance of each plugin
   109  	// name here, even though the discovery interface forces us to pretend
   110  	// that might not be true.
   111  
   112  	factories := make(map[string]provisioners.Factory)
   113  
   114  	// Wire up the internal provisioners first. These might be overridden
   115  	// by discovered provisioners below.
   116  	for name, factory := range internalProvisionerFactories() {
   117  		factories[name] = factory
   118  	}
   119  
   120  	byName := plugins.ByName()
   121  	for name, metas := range byName {
   122  		// Since we validated versions above and we partitioned the sets
   123  		// by name, we're guaranteed that the metas in our set all have
   124  		// valid versions and that there's at least one meta.
   125  		newest := metas.Newest()
   126  
   127  		factories[name] = provisionerFactory(newest)
   128  	}
   129  
   130  	return factories
   131  }
   132  
   133  func provisionerFactory(meta discovery.PluginMeta) provisioners.Factory {
   134  	return func() (provisioners.Interface, error) {
   135  		cfg := &plugin.ClientConfig{
   136  			Cmd:              exec.Command(meta.Path),
   137  			HandshakeConfig:  tfplugin.Handshake,
   138  			VersionedPlugins: tfplugin.VersionedPlugins,
   139  			Managed:          true,
   140  			Logger:           logging.NewLogger("provisioner"),
   141  			AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
   142  			AutoMTLS:         enableProviderAutoMTLS,
   143  			SyncStdout:       logging.PluginOutputMonitor(fmt.Sprintf("%s:stdout", meta.Name)),
   144  			SyncStderr:       logging.PluginOutputMonitor(fmt.Sprintf("%s:stderr", meta.Name)),
   145  		}
   146  		client := plugin.NewClient(cfg)
   147  		return newProvisionerClient(client)
   148  	}
   149  }
   150  
   151  func internalProvisionerFactories() map[string]provisioners.Factory {
   152  	return map[string]provisioners.Factory{
   153  		"file":        provisioners.FactoryFixed(fileprovisioner.New()),
   154  		"local-exec":  provisioners.FactoryFixed(localexec.New()),
   155  		"remote-exec": provisioners.FactoryFixed(remoteexec.New()),
   156  	}
   157  }
   158  
   159  func newProvisionerClient(client *plugin.Client) (provisioners.Interface, error) {
   160  	// Request the RPC client so we can get the provisioner
   161  	// so we can build the actual RPC-implemented provisioner.
   162  	rpcClient, err := client.Client()
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  
   167  	raw, err := rpcClient.Dispense(tfplugin.ProvisionerPluginName)
   168  	if err != nil {
   169  		return nil, err
   170  	}
   171  
   172  	// store the client so that the plugin can kill the child process
   173  	p := raw.(*tfplugin.GRPCProvisioner)
   174  	p.PluginClient = client
   175  	return p, nil
   176  }