github.com/sl1pm4t/terraform@v0.6.4-0.20170725213156-870617d22df3/command/plugins.go (about)

     1  package command
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"log"
     9  	"os"
    10  	"os/exec"
    11  	"path/filepath"
    12  	"runtime"
    13  	"strings"
    14  
    15  	plugin "github.com/hashicorp/go-plugin"
    16  	tfplugin "github.com/hashicorp/terraform/plugin"
    17  	"github.com/hashicorp/terraform/plugin/discovery"
    18  	"github.com/hashicorp/terraform/terraform"
    19  	"github.com/kardianos/osext"
    20  )
    21  
    22  // multiVersionProviderResolver is an implementation of
    23  // terraform.ResourceProviderResolver that matches the given version constraints
    24  // against a set of versioned provider plugins to find the newest version of
    25  // each that satisfies the given constraints.
    26  type multiVersionProviderResolver struct {
    27  	Available discovery.PluginMetaSet
    28  }
    29  
    30  func choosePlugins(avail discovery.PluginMetaSet, reqd discovery.PluginRequirements) map[string]discovery.PluginMeta {
    31  	candidates := avail.ConstrainVersions(reqd)
    32  	ret := map[string]discovery.PluginMeta{}
    33  	for name, metas := range candidates {
    34  		if len(metas) == 0 {
    35  			continue
    36  		}
    37  		ret[name] = metas.Newest()
    38  	}
    39  	return ret
    40  }
    41  
    42  func (r *multiVersionProviderResolver) ResolveProviders(
    43  	reqd discovery.PluginRequirements,
    44  ) (map[string]terraform.ResourceProviderFactory, []error) {
    45  	factories := make(map[string]terraform.ResourceProviderFactory, len(reqd))
    46  	var errs []error
    47  
    48  	chosen := choosePlugins(r.Available, reqd)
    49  	for name, req := range reqd {
    50  		if newest, available := chosen[name]; available {
    51  			digest, err := newest.SHA256()
    52  			if err != nil {
    53  				errs = append(errs, fmt.Errorf("provider.%s: failed to load plugin to verify its signature: %s", name, err))
    54  				continue
    55  			}
    56  			if !reqd[name].AcceptsSHA256(digest) {
    57  				errs = append(errs, fmt.Errorf("provider.%s: new or changed plugin executable", name))
    58  				continue
    59  			}
    60  
    61  			client := tfplugin.Client(newest)
    62  			factories[name] = providerFactory(client)
    63  		} else {
    64  			msg := fmt.Sprintf("provider.%s: no suitable version installed", name)
    65  
    66  			required := req.Versions.String()
    67  			// no version is unconstrained
    68  			if required == "" {
    69  				required = "(any version)"
    70  			}
    71  
    72  			foundVersions := []string{}
    73  			for meta := range r.Available.WithName(name) {
    74  				foundVersions = append(foundVersions, fmt.Sprintf("%q", meta.Version))
    75  			}
    76  
    77  			found := "none"
    78  			if len(foundVersions) > 0 {
    79  				found = strings.Join(foundVersions, ", ")
    80  			}
    81  
    82  			msg += fmt.Sprintf("\n  version requirements: %q\n  versions installed: %s", required, found)
    83  
    84  			errs = append(errs, errors.New(msg))
    85  		}
    86  	}
    87  
    88  	return factories, errs
    89  }
    90  
    91  // store the user-supplied path for plugin discovery
    92  func (m *Meta) storePluginPath(pluginPath []string) error {
    93  	if len(pluginPath) == 0 {
    94  		return nil
    95  	}
    96  
    97  	js, err := json.MarshalIndent(pluginPath, "", "  ")
    98  	if err != nil {
    99  		return err
   100  	}
   101  
   102  	// if this fails, so will WriteFile
   103  	os.MkdirAll(m.DataDir(), 0755)
   104  
   105  	return ioutil.WriteFile(filepath.Join(m.DataDir(), PluginPathFile), js, 0644)
   106  }
   107  
   108  // Load the user-defined plugin search path into Meta.pluginPath if the file
   109  // exists.
   110  func (m *Meta) loadPluginPath() ([]string, error) {
   111  	js, err := ioutil.ReadFile(filepath.Join(m.DataDir(), PluginPathFile))
   112  	if os.IsNotExist(err) {
   113  		return nil, nil
   114  	}
   115  
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  
   120  	var pluginPath []string
   121  	if err := json.Unmarshal(js, &pluginPath); err != nil {
   122  		return nil, err
   123  	}
   124  
   125  	return pluginPath, nil
   126  }
   127  
   128  // the default location for automatically installed plugins
   129  func (m *Meta) pluginDir() string {
   130  	return filepath.Join(m.DataDir(), "plugins", fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH))
   131  }
   132  
   133  // pluginDirs return a list of directories to search for plugins.
   134  //
   135  // Earlier entries in this slice get priority over later when multiple copies
   136  // of the same plugin version are found, but newer versions always override
   137  // older versions where both satisfy the provider version constraints.
   138  func (m *Meta) pluginDirs(includeAutoInstalled bool) []string {
   139  	// user defined paths take precedence
   140  	if len(m.pluginPath) > 0 {
   141  		return m.pluginPath
   142  	}
   143  
   144  	// When searching the following directories, earlier entries get precedence
   145  	// if the same plugin version is found twice, but newer versions will
   146  	// always get preference below regardless of where they are coming from.
   147  	// TODO: Add auto-install dir, default vendor dir and optional override
   148  	// vendor dir(s).
   149  	dirs := []string{"."}
   150  
   151  	// Look in the same directory as the Terraform executable.
   152  	// If found, this replaces what we found in the config path.
   153  	exePath, err := osext.Executable()
   154  	if err != nil {
   155  		log.Printf("[ERROR] Error discovering exe directory: %s", err)
   156  	} else {
   157  		dirs = append(dirs, filepath.Dir(exePath))
   158  	}
   159  
   160  	// add the user vendor directory
   161  	dirs = append(dirs, DefaultPluginVendorDir)
   162  
   163  	if includeAutoInstalled {
   164  		dirs = append(dirs, m.pluginDir())
   165  	}
   166  	dirs = append(dirs, m.GlobalPluginDirs...)
   167  
   168  	return dirs
   169  }
   170  
   171  // providerPluginSet returns the set of valid providers that were discovered in
   172  // the defined search paths.
   173  func (m *Meta) providerPluginSet() discovery.PluginMetaSet {
   174  	plugins := discovery.FindPlugins("provider", m.pluginDirs(true))
   175  	plugins, _ = plugins.ValidateVersions()
   176  
   177  	for p := range plugins {
   178  		log.Printf("[DEBUG] found valid plugin: %q", p.Name)
   179  	}
   180  
   181  	return plugins
   182  }
   183  
   184  // providerPluginAutoInstalledSet returns the set of providers that exist
   185  // within the auto-install directory.
   186  func (m *Meta) providerPluginAutoInstalledSet() discovery.PluginMetaSet {
   187  	plugins := discovery.FindPlugins("provider", []string{m.pluginDir()})
   188  	plugins, _ = plugins.ValidateVersions()
   189  
   190  	for p := range plugins {
   191  		log.Printf("[DEBUG] found valid plugin: %q", p.Name)
   192  	}
   193  
   194  	return plugins
   195  }
   196  
   197  // providerPluginManuallyInstalledSet returns the set of providers that exist
   198  // in all locations *except* the auto-install directory.
   199  func (m *Meta) providerPluginManuallyInstalledSet() discovery.PluginMetaSet {
   200  	plugins := discovery.FindPlugins("provider", m.pluginDirs(false))
   201  	plugins, _ = plugins.ValidateVersions()
   202  
   203  	for p := range plugins {
   204  		log.Printf("[DEBUG] found valid plugin: %q", p.Name)
   205  	}
   206  
   207  	return plugins
   208  }
   209  
   210  func (m *Meta) providerResolver() terraform.ResourceProviderResolver {
   211  	return &multiVersionProviderResolver{
   212  		Available: m.providerPluginSet(),
   213  	}
   214  }
   215  
   216  // filter the requirements returning only the providers that we can't resolve
   217  func (m *Meta) missingPlugins(avail discovery.PluginMetaSet, reqd discovery.PluginRequirements) discovery.PluginRequirements {
   218  	missing := make(discovery.PluginRequirements)
   219  
   220  	for n, r := range reqd {
   221  		log.Printf("[DEBUG] plugin requirements: %q=%q", n, r.Versions)
   222  	}
   223  
   224  	candidates := avail.ConstrainVersions(reqd)
   225  
   226  	for name, versionSet := range reqd {
   227  		if metas := candidates[name]; metas.Count() == 0 {
   228  			missing[name] = versionSet
   229  		}
   230  	}
   231  
   232  	return missing
   233  }
   234  
   235  func (m *Meta) provisionerFactories() map[string]terraform.ResourceProvisionerFactory {
   236  	dirs := m.pluginDirs(true)
   237  	plugins := discovery.FindPlugins("provisioner", dirs)
   238  	plugins, _ = plugins.ValidateVersions()
   239  
   240  	// For now our goal is to just find the latest version of each plugin
   241  	// we have on the system. All provisioners should be at version 0.0.0
   242  	// currently, so there should actually only be one instance of each plugin
   243  	// name here, even though the discovery interface forces us to pretend
   244  	// that might not be true.
   245  
   246  	factories := make(map[string]terraform.ResourceProvisionerFactory)
   247  
   248  	// Wire up the internal provisioners first. These might be overridden
   249  	// by discovered provisioners below.
   250  	for name := range InternalProvisioners {
   251  		client, err := internalPluginClient("provisioner", name)
   252  		if err != nil {
   253  			log.Printf("[WARN] failed to build command line for internal plugin %q: %s", name, err)
   254  			continue
   255  		}
   256  		factories[name] = provisionerFactory(client)
   257  	}
   258  
   259  	byName := plugins.ByName()
   260  	for name, metas := range byName {
   261  		// Since we validated versions above and we partitioned the sets
   262  		// by name, we're guaranteed that the metas in our set all have
   263  		// valid versions and that there's at least one meta.
   264  		newest := metas.Newest()
   265  		client := tfplugin.Client(newest)
   266  		factories[name] = provisionerFactory(client)
   267  	}
   268  
   269  	return factories
   270  }
   271  
   272  func internalPluginClient(kind, name string) (*plugin.Client, error) {
   273  	cmdLine, err := BuildPluginCommandString(kind, name)
   274  	if err != nil {
   275  		return nil, err
   276  	}
   277  
   278  	// See the docstring for BuildPluginCommandString for why we need to do
   279  	// this split here.
   280  	cmdArgv := strings.Split(cmdLine, TFSPACE)
   281  
   282  	cfg := &plugin.ClientConfig{
   283  		Cmd:             exec.Command(cmdArgv[0], cmdArgv[1:]...),
   284  		HandshakeConfig: tfplugin.Handshake,
   285  		Managed:         true,
   286  		Plugins:         tfplugin.PluginMap,
   287  	}
   288  
   289  	return plugin.NewClient(cfg), nil
   290  }
   291  
   292  func providerFactory(client *plugin.Client) terraform.ResourceProviderFactory {
   293  	return func() (terraform.ResourceProvider, error) {
   294  		// Request the RPC client so we can get the provider
   295  		// so we can build the actual RPC-implemented provider.
   296  		rpcClient, err := client.Client()
   297  		if err != nil {
   298  			return nil, err
   299  		}
   300  
   301  		raw, err := rpcClient.Dispense(tfplugin.ProviderPluginName)
   302  		if err != nil {
   303  			return nil, err
   304  		}
   305  
   306  		return raw.(terraform.ResourceProvider), nil
   307  	}
   308  }
   309  
   310  func provisionerFactory(client *plugin.Client) terraform.ResourceProvisionerFactory {
   311  	return func() (terraform.ResourceProvisioner, error) {
   312  		// Request the RPC client so we can get the provisioner
   313  		// so we can build the actual RPC-implemented provisioner.
   314  		rpcClient, err := client.Client()
   315  		if err != nil {
   316  			return nil, err
   317  		}
   318  
   319  		raw, err := rpcClient.Dispense(tfplugin.ProvisionerPluginName)
   320  		if err != nil {
   321  			return nil, err
   322  		}
   323  
   324  		return raw.(terraform.ResourceProvisioner), nil
   325  	}
   326  }