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