github.com/openshift/hashicorp-terraform@v0.11.12-beta1/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  	path := filepath.Join(m.DataDir(), PluginPathFile)
   121  
   122  	// remove the plugin dir record if the path was set to an empty string
   123  	if len(pluginPath) == 1 && (pluginPath[0] == "") {
   124  		err := os.Remove(path)
   125  		if !os.IsNotExist(err) {
   126  			return err
   127  		}
   128  		return nil
   129  	}
   130  
   131  	js, err := json.MarshalIndent(pluginPath, "", "  ")
   132  	if err != nil {
   133  		return err
   134  	}
   135  
   136  	// if this fails, so will WriteFile
   137  	os.MkdirAll(m.DataDir(), 0755)
   138  
   139  	return ioutil.WriteFile(path, js, 0644)
   140  }
   141  
   142  // Load the user-defined plugin search path into Meta.pluginPath if the file
   143  // exists.
   144  func (m *Meta) loadPluginPath() ([]string, error) {
   145  	js, err := ioutil.ReadFile(filepath.Join(m.DataDir(), PluginPathFile))
   146  	if os.IsNotExist(err) {
   147  		return nil, nil
   148  	}
   149  
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  
   154  	var pluginPath []string
   155  	if err := json.Unmarshal(js, &pluginPath); err != nil {
   156  		return nil, err
   157  	}
   158  
   159  	return pluginPath, nil
   160  }
   161  
   162  // the default location for automatically installed plugins
   163  func (m *Meta) pluginDir() string {
   164  	return filepath.Join(m.DataDir(), "plugins", fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH))
   165  }
   166  
   167  // pluginDirs return a list of directories to search for plugins.
   168  //
   169  // Earlier entries in this slice get priority over later when multiple copies
   170  // of the same plugin version are found, but newer versions always override
   171  // older versions where both satisfy the provider version constraints.
   172  func (m *Meta) pluginDirs(includeAutoInstalled bool) []string {
   173  	// user defined paths take precedence
   174  	if len(m.pluginPath) > 0 {
   175  		return m.pluginPath
   176  	}
   177  
   178  	// When searching the following directories, earlier entries get precedence
   179  	// if the same plugin version is found twice, but newer versions will
   180  	// always get preference below regardless of where they are coming from.
   181  	// TODO: Add auto-install dir, default vendor dir and optional override
   182  	// vendor dir(s).
   183  	dirs := []string{"."}
   184  
   185  	// Look in the same directory as the Terraform executable.
   186  	// If found, this replaces what we found in the config path.
   187  	exePath, err := osext.Executable()
   188  	if err != nil {
   189  		log.Printf("[ERROR] Error discovering exe directory: %s", err)
   190  	} else {
   191  		dirs = append(dirs, filepath.Dir(exePath))
   192  	}
   193  
   194  	// add the user vendor directory
   195  	dirs = append(dirs, DefaultPluginVendorDir)
   196  
   197  	if includeAutoInstalled {
   198  		dirs = append(dirs, m.pluginDir())
   199  	}
   200  	dirs = append(dirs, m.GlobalPluginDirs...)
   201  
   202  	return dirs
   203  }
   204  
   205  func (m *Meta) pluginCache() discovery.PluginCache {
   206  	dir := m.PluginCacheDir
   207  	if dir == "" {
   208  		return nil // cache disabled
   209  	}
   210  
   211  	dir = filepath.Join(dir, pluginMachineName)
   212  
   213  	return discovery.NewLocalPluginCache(dir)
   214  }
   215  
   216  // providerPluginSet returns the set of valid providers that were discovered in
   217  // the defined search paths.
   218  func (m *Meta) providerPluginSet() discovery.PluginMetaSet {
   219  	plugins := discovery.FindPlugins("provider", m.pluginDirs(true))
   220  
   221  	// Add providers defined in the legacy .terraformrc,
   222  	if m.PluginOverrides != nil {
   223  		for k, v := range m.PluginOverrides.Providers {
   224  			log.Printf("[DEBUG] found plugin override in .terraformrc: %q, %q", k, v)
   225  		}
   226  		plugins = plugins.OverridePaths(m.PluginOverrides.Providers)
   227  	}
   228  
   229  	plugins, _ = plugins.ValidateVersions()
   230  
   231  	for p := range plugins {
   232  		log.Printf("[DEBUG] found valid plugin: %q, %q, %q", p.Name, p.Version, p.Path)
   233  	}
   234  
   235  	return plugins
   236  }
   237  
   238  // providerPluginAutoInstalledSet returns the set of providers that exist
   239  // within the auto-install directory.
   240  func (m *Meta) providerPluginAutoInstalledSet() discovery.PluginMetaSet {
   241  	plugins := discovery.FindPlugins("provider", []string{m.pluginDir()})
   242  	plugins, _ = plugins.ValidateVersions()
   243  
   244  	for p := range plugins {
   245  		log.Printf("[DEBUG] found valid plugin: %q", p.Name)
   246  	}
   247  
   248  	return plugins
   249  }
   250  
   251  // providerPluginManuallyInstalledSet returns the set of providers that exist
   252  // in all locations *except* the auto-install directory.
   253  func (m *Meta) providerPluginManuallyInstalledSet() discovery.PluginMetaSet {
   254  	plugins := discovery.FindPlugins("provider", m.pluginDirs(false))
   255  
   256  	// Add providers defined in the legacy .terraformrc,
   257  	if m.PluginOverrides != nil {
   258  		for k, v := range m.PluginOverrides.Providers {
   259  			log.Printf("[DEBUG] found plugin override in .terraformrc: %q, %q", k, v)
   260  		}
   261  
   262  		plugins = plugins.OverridePaths(m.PluginOverrides.Providers)
   263  	}
   264  
   265  	plugins, _ = plugins.ValidateVersions()
   266  
   267  	for p := range plugins {
   268  		log.Printf("[DEBUG] found valid plugin: %q, %q, %q", p.Name, p.Version, p.Path)
   269  	}
   270  
   271  	return plugins
   272  }
   273  
   274  func (m *Meta) providerResolver() terraform.ResourceProviderResolver {
   275  	return &multiVersionProviderResolver{
   276  		Available: m.providerPluginSet(),
   277  		Internal:  m.internalProviders(),
   278  	}
   279  }
   280  
   281  func (m *Meta) internalProviders() map[string]terraform.ResourceProviderFactory {
   282  	return map[string]terraform.ResourceProviderFactory{
   283  		"terraform": func() (terraform.ResourceProvider, error) {
   284  			return terraformProvider.Provider(), nil
   285  		},
   286  	}
   287  }
   288  
   289  // filter the requirements returning only the providers that we can't resolve
   290  func (m *Meta) missingPlugins(avail discovery.PluginMetaSet, reqd discovery.PluginRequirements) discovery.PluginRequirements {
   291  	missing := make(discovery.PluginRequirements)
   292  
   293  	candidates := avail.ConstrainVersions(reqd)
   294  	internal := m.internalProviders()
   295  
   296  	for name, versionSet := range reqd {
   297  		// internal providers can't be missing
   298  		if _, ok := internal[name]; ok {
   299  			continue
   300  		}
   301  
   302  		log.Printf("[DEBUG] plugin requirements: %q=%q", name, versionSet.Versions)
   303  		if metas := candidates[name]; metas.Count() == 0 {
   304  			missing[name] = versionSet
   305  		}
   306  	}
   307  
   308  	return missing
   309  }
   310  
   311  func (m *Meta) provisionerFactories() map[string]terraform.ResourceProvisionerFactory {
   312  	dirs := m.pluginDirs(true)
   313  	plugins := discovery.FindPlugins("provisioner", dirs)
   314  	plugins, _ = plugins.ValidateVersions()
   315  
   316  	// For now our goal is to just find the latest version of each plugin
   317  	// we have on the system. All provisioners should be at version 0.0.0
   318  	// currently, so there should actually only be one instance of each plugin
   319  	// name here, even though the discovery interface forces us to pretend
   320  	// that might not be true.
   321  
   322  	factories := make(map[string]terraform.ResourceProvisionerFactory)
   323  
   324  	// Wire up the internal provisioners first. These might be overridden
   325  	// by discovered provisioners below.
   326  	for name := range InternalProvisioners {
   327  		client, err := internalPluginClient("provisioner", name)
   328  		if err != nil {
   329  			log.Printf("[WARN] failed to build command line for internal plugin %q: %s", name, err)
   330  			continue
   331  		}
   332  		factories[name] = provisionerFactory(client)
   333  	}
   334  
   335  	byName := plugins.ByName()
   336  	for name, metas := range byName {
   337  		// Since we validated versions above and we partitioned the sets
   338  		// by name, we're guaranteed that the metas in our set all have
   339  		// valid versions and that there's at least one meta.
   340  		newest := metas.Newest()
   341  		client := tfplugin.Client(newest)
   342  		factories[name] = provisionerFactory(client)
   343  	}
   344  
   345  	return factories
   346  }
   347  
   348  func internalPluginClient(kind, name string) (*plugin.Client, error) {
   349  	cmdLine, err := BuildPluginCommandString(kind, name)
   350  	if err != nil {
   351  		return nil, err
   352  	}
   353  
   354  	// See the docstring for BuildPluginCommandString for why we need to do
   355  	// this split here.
   356  	cmdArgv := strings.Split(cmdLine, TFSPACE)
   357  
   358  	cfg := &plugin.ClientConfig{
   359  		Cmd:             exec.Command(cmdArgv[0], cmdArgv[1:]...),
   360  		HandshakeConfig: tfplugin.Handshake,
   361  		Managed:         true,
   362  		Plugins:         tfplugin.PluginMap,
   363  	}
   364  
   365  	return plugin.NewClient(cfg), nil
   366  }
   367  
   368  func providerFactory(client *plugin.Client) terraform.ResourceProviderFactory {
   369  	return func() (terraform.ResourceProvider, error) {
   370  		// Request the RPC client so we can get the provider
   371  		// so we can build the actual RPC-implemented provider.
   372  		rpcClient, err := client.Client()
   373  		if err != nil {
   374  			return nil, err
   375  		}
   376  
   377  		raw, err := rpcClient.Dispense(tfplugin.ProviderPluginName)
   378  		if err != nil {
   379  			return nil, err
   380  		}
   381  
   382  		return raw.(terraform.ResourceProvider), nil
   383  	}
   384  }
   385  
   386  func provisionerFactory(client *plugin.Client) terraform.ResourceProvisionerFactory {
   387  	return func() (terraform.ResourceProvisioner, error) {
   388  		// Request the RPC client so we can get the provisioner
   389  		// so we can build the actual RPC-implemented provisioner.
   390  		rpcClient, err := client.Client()
   391  		if err != nil {
   392  			return nil, err
   393  		}
   394  
   395  		raw, err := rpcClient.Dispense(tfplugin.ProvisionerPluginName)
   396  		if err != nil {
   397  			return nil, err
   398  		}
   399  
   400  		return raw.(terraform.ResourceProvisioner), nil
   401  	}
   402  }