github.com/iaas-resource-provision/iaas-rpc@v1.0.7-0.20211021023331-ed21f798c408/internal/command/plugins.go (about)

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