github.com/kevholditch/terraform@v0.9.7-0.20170613192930-9706042ddd51/command/plugins.go (about)

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