kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/command/meta_providers.go (about)

     1  package command
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"log"
     7  	"os"
     8  	"os/exec"
     9  	"strings"
    10  
    11  	"github.com/hashicorp/go-multierror"
    12  	plugin "github.com/hashicorp/go-plugin"
    13  
    14  	"kubeform.dev/terraform-backend-sdk/addrs"
    15  	terraformProvider "kubeform.dev/terraform-backend-sdk/builtin/providers/terraform"
    16  	"kubeform.dev/terraform-backend-sdk/getproviders"
    17  	"kubeform.dev/terraform-backend-sdk/logging"
    18  	"kubeform.dev/terraform-backend-sdk/moduletest"
    19  	tfplugin "kubeform.dev/terraform-backend-sdk/plugin"
    20  	tfplugin6 "kubeform.dev/terraform-backend-sdk/plugin6"
    21  	"kubeform.dev/terraform-backend-sdk/providercache"
    22  	"kubeform.dev/terraform-backend-sdk/providers"
    23  	"kubeform.dev/terraform-backend-sdk/tfdiags"
    24  )
    25  
    26  // The TF_DISABLE_PLUGIN_TLS environment variable is intended only for use by
    27  // the plugin SDK test framework, to reduce startup overhead when rapidly
    28  // launching and killing lots of instances of the same provider.
    29  //
    30  // This is not intended to be set by end-users.
    31  var enableProviderAutoMTLS = os.Getenv("TF_DISABLE_PLUGIN_TLS") == ""
    32  
    33  // providerInstaller returns an object that knows how to install providers and
    34  // how to recover the selections from a prior installation process.
    35  //
    36  // The resulting provider installer is constructed from the results of
    37  // the other methods providerLocalCacheDir, providerGlobalCacheDir, and
    38  // providerInstallSource.
    39  //
    40  // Only one object returned from this method should be live at any time,
    41  // because objects inside contain caches that must be maintained properly.
    42  // Because this method wraps a result from providerLocalCacheDir, that
    43  // limitation applies also to results from that method.
    44  func (m *Meta) providerInstaller() *providercache.Installer {
    45  	return m.providerInstallerCustomSource(m.providerInstallSource())
    46  }
    47  
    48  // providerInstallerCustomSource is a variant of providerInstaller that
    49  // allows the caller to specify a different installation source than the one
    50  // that would naturally be selected.
    51  //
    52  // The result of this method has the same dependencies and constraints as
    53  // providerInstaller.
    54  //
    55  // The result of providerInstallerCustomSource differs from
    56  // providerInstaller only in how it determines package installation locations
    57  // during EnsureProviderVersions. A caller that doesn't call
    58  // EnsureProviderVersions (anything other than "terraform init") can safely
    59  // just use the providerInstaller method unconditionally.
    60  func (m *Meta) providerInstallerCustomSource(source getproviders.Source) *providercache.Installer {
    61  	targetDir := m.providerLocalCacheDir()
    62  	globalCacheDir := m.providerGlobalCacheDir()
    63  	inst := providercache.NewInstaller(targetDir, source)
    64  	if globalCacheDir != nil {
    65  		inst.SetGlobalCacheDir(globalCacheDir)
    66  	}
    67  	var builtinProviderTypes []string
    68  	for ty := range m.internalProviders() {
    69  		builtinProviderTypes = append(builtinProviderTypes, ty)
    70  	}
    71  	inst.SetBuiltInProviderTypes(builtinProviderTypes)
    72  	unmanagedProviderTypes := make(map[addrs.Provider]struct{}, len(m.UnmanagedProviders))
    73  	for ty := range m.UnmanagedProviders {
    74  		unmanagedProviderTypes[ty] = struct{}{}
    75  	}
    76  	inst.SetUnmanagedProviderTypes(unmanagedProviderTypes)
    77  	return inst
    78  }
    79  
    80  // providerCustomLocalDirectorySource produces a provider source that consults
    81  // only the given local filesystem directories for plugins to install.
    82  //
    83  // This is used to implement the -plugin-dir option for "terraform init", where
    84  // the result of this method is used instead of what would've been returned
    85  // from m.providerInstallSource.
    86  //
    87  // If the given list of directories is empty then the resulting source will
    88  // have no providers available for installation at all.
    89  func (m *Meta) providerCustomLocalDirectorySource(dirs []string) getproviders.Source {
    90  	var ret getproviders.MultiSource
    91  	for _, dir := range dirs {
    92  		ret = append(ret, getproviders.MultiSourceSelector{
    93  			Source: getproviders.NewFilesystemMirrorSource(dir),
    94  		})
    95  	}
    96  	return ret
    97  }
    98  
    99  // providerLocalCacheDir returns an object representing the
   100  // configuration-specific local cache directory. This is the
   101  // only location consulted for provider plugin packages for Terraform
   102  // operations other than provider installation.
   103  //
   104  // Only the provider installer (in "terraform init") is permitted to make
   105  // modifications to this cache directory. All other commands must treat it
   106  // as read-only.
   107  //
   108  // Only one object returned from this method should be live at any time,
   109  // because objects inside contain caches that must be maintained properly.
   110  func (m *Meta) providerLocalCacheDir() *providercache.Dir {
   111  	m.fixupMissingWorkingDir()
   112  	dir := m.WorkingDir.ProviderLocalCacheDir()
   113  	return providercache.NewDir(dir)
   114  }
   115  
   116  // providerGlobalCacheDir returns an object representing the shared global
   117  // provider cache directory, used as a read-through cache when installing
   118  // new provider plugin packages.
   119  //
   120  // This function may return nil, in which case there is no global cache
   121  // configured and new packages should be downloaded directly into individual
   122  // configuration-specific cache directories.
   123  //
   124  // Only one object returned from this method should be live at any time,
   125  // because objects inside contain caches that must be maintained properly.
   126  func (m *Meta) providerGlobalCacheDir() *providercache.Dir {
   127  	dir := m.PluginCacheDir
   128  	if dir == "" {
   129  		return nil // cache disabled
   130  	}
   131  	return providercache.NewDir(dir)
   132  }
   133  
   134  // providerInstallSource returns an object that knows how to consult one or
   135  // more external sources to determine the availability of and package
   136  // locations for versions of Terraform providers that are available for
   137  // automatic installation.
   138  //
   139  // This returns the standard provider install source that consults a number
   140  // of directories selected either automatically or via the CLI configuration.
   141  // Users may choose to override this during a "terraform init" command by
   142  // specifying one or more -plugin-dir options, in which case the installation
   143  // process will construct its own source consulting only those directories
   144  // and use that instead.
   145  func (m *Meta) providerInstallSource() getproviders.Source {
   146  	// A provider source should always be provided in normal use, but our
   147  	// unit tests might not always populate Meta fully and so we'll be robust
   148  	// by returning a non-nil source that just always answers that no plugins
   149  	// are available.
   150  	if m.ProviderSource == nil {
   151  		// A multi-source with no underlying sources is effectively an
   152  		// always-empty source.
   153  		return getproviders.MultiSource(nil)
   154  	}
   155  	return m.ProviderSource
   156  }
   157  
   158  // providerDevOverrideInitWarnings returns a diagnostics that contains at
   159  // least one warning if and only if there is at least one provider development
   160  // override in effect. If not, the result is always empty. The result never
   161  // contains error diagnostics.
   162  //
   163  // The init command can use this to include a warning that the results
   164  // may differ from what's expected due to the development overrides. For
   165  // other commands, providerDevOverrideRuntimeWarnings should be used.
   166  func (m *Meta) providerDevOverrideInitWarnings() tfdiags.Diagnostics {
   167  	if len(m.ProviderDevOverrides) == 0 {
   168  		return nil
   169  	}
   170  	var detailMsg strings.Builder
   171  	detailMsg.WriteString("The following provider development overrides are set in the CLI configuration:\n")
   172  	for addr, path := range m.ProviderDevOverrides {
   173  		detailMsg.WriteString(fmt.Sprintf(" - %s in %s\n", addr.ForDisplay(), path))
   174  	}
   175  	detailMsg.WriteString("\nSkip terraform init when using provider development overrides. It is not necessary and may error unexpectedly.")
   176  	return tfdiags.Diagnostics{
   177  		tfdiags.Sourceless(
   178  			tfdiags.Warning,
   179  			"Provider development overrides are in effect",
   180  			detailMsg.String(),
   181  		),
   182  	}
   183  }
   184  
   185  // providerDevOverrideRuntimeWarnings returns a diagnostics that contains at
   186  // least one warning if and only if there is at least one provider development
   187  // override in effect. If not, the result is always empty. The result never
   188  // contains error diagnostics.
   189  //
   190  // Certain commands can use this to include a warning that their results
   191  // may differ from what's expected due to the development overrides. It's
   192  // not necessary to bother the user with this warning on every command, but
   193  // it's helpful to return it on commands that have externally-visible side
   194  // effects and on commands that are used to verify conformance to schemas.
   195  //
   196  // See providerDevOverrideInitWarnings for warnings specific to the init
   197  // command.
   198  func (m *Meta) providerDevOverrideRuntimeWarnings() tfdiags.Diagnostics {
   199  	if len(m.ProviderDevOverrides) == 0 {
   200  		return nil
   201  	}
   202  	var detailMsg strings.Builder
   203  	detailMsg.WriteString("The following provider development overrides are set in the CLI configuration:\n")
   204  	for addr, path := range m.ProviderDevOverrides {
   205  		detailMsg.WriteString(fmt.Sprintf(" - %s in %s\n", addr.ForDisplay(), path))
   206  	}
   207  	detailMsg.WriteString("\nThe behavior may therefore not match any released version of the provider and applying changes may cause the state to become incompatible with published releases.")
   208  	return tfdiags.Diagnostics{
   209  		tfdiags.Sourceless(
   210  			tfdiags.Warning,
   211  			"Provider development overrides are in effect",
   212  			detailMsg.String(),
   213  		),
   214  	}
   215  }
   216  
   217  // providerFactories uses the selections made previously by an installer in
   218  // the local cache directory (m.providerLocalCacheDir) to produce a map
   219  // from provider addresses to factory functions to create instances of
   220  // those providers.
   221  //
   222  // providerFactories will return an error if the installer's selections cannot
   223  // be honored with what is currently in the cache, such as if a selected
   224  // package has been removed from the cache or if the contents of a selected
   225  // package have been modified outside of the installer. If it returns an error,
   226  // the returned map may be incomplete or invalid, but will be as complete
   227  // as possible given the cause of the error.
   228  func (m *Meta) providerFactories() (map[addrs.Provider]providers.Factory, error) {
   229  	locks, diags := m.lockedDependencies()
   230  	if diags.HasErrors() {
   231  		return nil, fmt.Errorf("failed to read dependency lock file: %s", diags.Err())
   232  	}
   233  
   234  	// We'll always run through all of our providers, even if one of them
   235  	// encounters an error, so that we can potentially report multiple errors
   236  	// where appropriate and so that callers can potentially make use of the
   237  	// partial result we return if e.g. they want to enumerate which providers
   238  	// are available, or call into one of the providers that didn't fail.
   239  	var err error
   240  
   241  	// For the providers from the lock file, we expect them to be already
   242  	// available in the provider cache because "terraform init" should already
   243  	// have put them there.
   244  	providerLocks := locks.AllProviders()
   245  	cacheDir := m.providerLocalCacheDir()
   246  
   247  	// The internal providers are _always_ available, even if the configuration
   248  	// doesn't request them, because they don't need any special installation
   249  	// and they'll just be ignored if not used.
   250  	internalFactories := m.internalProviders()
   251  
   252  	// We have two different special cases aimed at provider development
   253  	// use-cases, which are not for "production" use:
   254  	// - The CLI config can specify that a particular provider should always
   255  	// use a plugin from a particular local directory, ignoring anything the
   256  	// lock file or cache directory might have to say about it. This is useful
   257  	// for manual testing of local development builds.
   258  	// - The Terraform SDK test harness (and possibly other callers in future)
   259  	// can ask that we use its own already-started provider servers, which we
   260  	// call "unmanaged" because Terraform isn't responsible for starting
   261  	// and stopping them. This is intended for automated testing where a
   262  	// calling harness is responsible both for starting the provider server
   263  	// and orchestrating one or more non-interactive Terraform runs that then
   264  	// exercise it.
   265  	// Unmanaged providers take precedence over overridden providers because
   266  	// overrides are typically a "session-level" setting while unmanaged
   267  	// providers are typically scoped to a single unattended command.
   268  	devOverrideProviders := m.ProviderDevOverrides
   269  	unmanagedProviders := m.UnmanagedProviders
   270  
   271  	factories := make(map[addrs.Provider]providers.Factory, len(providerLocks)+len(internalFactories)+len(unmanagedProviders))
   272  	for name, factory := range internalFactories {
   273  		factories[addrs.NewBuiltInProvider(name)] = factory
   274  	}
   275  	for provider, lock := range providerLocks {
   276  		reportError := func(thisErr error) {
   277  			err = multierror.Append(err, thisErr)
   278  			// We'll populate a provider factory that just echoes our error
   279  			// again if called, which allows us to still report a helpful
   280  			// error even if it gets detected downstream somewhere from the
   281  			// caller using our partial result.
   282  			factories[provider] = providerFactoryError(thisErr)
   283  		}
   284  
   285  		version := lock.Version()
   286  		cached := cacheDir.ProviderVersion(provider, version)
   287  		if cached == nil {
   288  			reportError(fmt.Errorf(
   289  				"there is no package for %s %s cached in %s",
   290  				provider, version, cacheDir.BasePath(),
   291  			))
   292  			continue
   293  		}
   294  		// The cached package must match one of the checksums recorded in
   295  		// the lock file, if any.
   296  		if allowedHashes := lock.PreferredHashes(); len(allowedHashes) != 0 {
   297  			matched, err := cached.MatchesAnyHash(allowedHashes)
   298  			if err != nil {
   299  				reportError(fmt.Errorf(
   300  					"failed to verify checksum of %s %s package cached in in %s: %s",
   301  					provider, version, cacheDir.BasePath(), err,
   302  				))
   303  				continue
   304  			}
   305  			if !matched {
   306  				reportError(fmt.Errorf(
   307  					"the cached package for %s %s (in %s) does not match any of the checksums recorded in the dependency lock file",
   308  					provider, version, cacheDir.BasePath(),
   309  				))
   310  				continue
   311  			}
   312  		}
   313  		factories[provider] = providerFactory(cached)
   314  	}
   315  	for provider, localDir := range devOverrideProviders {
   316  		// It's likely that providers in this map will conflict with providers
   317  		// in providerLocks
   318  		factories[provider] = devOverrideProviderFactory(provider, localDir)
   319  	}
   320  	for provider, reattach := range unmanagedProviders {
   321  		factories[provider] = unmanagedProviderFactory(provider, reattach)
   322  	}
   323  	return factories, err
   324  }
   325  
   326  func (m *Meta) internalProviders() map[string]providers.Factory {
   327  	return map[string]providers.Factory{
   328  		"terraform": func() (providers.Interface, error) {
   329  			return terraformProvider.NewProvider(), nil
   330  		},
   331  		"test": func() (providers.Interface, error) {
   332  			return moduletest.NewProvider(), nil
   333  		},
   334  	}
   335  }
   336  
   337  // providerFactory produces a provider factory that runs up the executable
   338  // file in the given cache package and uses go-plugin to implement
   339  // providers.Interface against it.
   340  func providerFactory(meta *providercache.CachedProvider) providers.Factory {
   341  	return func() (providers.Interface, error) {
   342  		execFile, err := meta.ExecutableFile()
   343  		if err != nil {
   344  			return nil, err
   345  		}
   346  
   347  		config := &plugin.ClientConfig{
   348  			HandshakeConfig:  tfplugin.Handshake,
   349  			Logger:           logging.NewProviderLogger(""),
   350  			AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
   351  			Managed:          true,
   352  			Cmd:              exec.Command(execFile),
   353  			AutoMTLS:         enableProviderAutoMTLS,
   354  			VersionedPlugins: tfplugin.VersionedPlugins,
   355  			SyncStdout:       logging.PluginOutputMonitor(fmt.Sprintf("%s:stdout", meta.Provider)),
   356  			SyncStderr:       logging.PluginOutputMonitor(fmt.Sprintf("%s:stderr", meta.Provider)),
   357  		}
   358  
   359  		client := plugin.NewClient(config)
   360  		rpcClient, err := client.Client()
   361  		if err != nil {
   362  			return nil, err
   363  		}
   364  
   365  		raw, err := rpcClient.Dispense(tfplugin.ProviderPluginName)
   366  		if err != nil {
   367  			return nil, err
   368  		}
   369  
   370  		// store the client so that the plugin can kill the child process
   371  		protoVer := client.NegotiatedVersion()
   372  		switch protoVer {
   373  		case 5:
   374  			p := raw.(*tfplugin.GRPCProvider)
   375  			p.PluginClient = client
   376  			return p, nil
   377  		case 6:
   378  			p := raw.(*tfplugin6.GRPCProvider)
   379  			p.PluginClient = client
   380  			return p, nil
   381  		default:
   382  			panic("unsupported protocol version")
   383  		}
   384  	}
   385  }
   386  
   387  func devOverrideProviderFactory(provider addrs.Provider, localDir getproviders.PackageLocalDir) providers.Factory {
   388  	// A dev override is essentially a synthetic cache entry for our purposes
   389  	// here, so that's how we'll construct it. The providerFactory function
   390  	// doesn't actually care about the version, so we can leave it
   391  	// unspecified: overridden providers are not explicitly versioned.
   392  	log.Printf("[DEBUG] Provider %s is overridden to load from %s", provider, localDir)
   393  	return providerFactory(&providercache.CachedProvider{
   394  		Provider:   provider,
   395  		Version:    getproviders.UnspecifiedVersion,
   396  		PackageDir: string(localDir),
   397  	})
   398  }
   399  
   400  // unmanagedProviderFactory produces a provider factory that uses the passed
   401  // reattach information to connect to go-plugin processes that are already
   402  // running, and implements providers.Interface against it.
   403  func unmanagedProviderFactory(provider addrs.Provider, reattach *plugin.ReattachConfig) providers.Factory {
   404  	return func() (providers.Interface, error) {
   405  		config := &plugin.ClientConfig{
   406  			HandshakeConfig:  tfplugin.Handshake,
   407  			Logger:           logging.NewProviderLogger("unmanaged."),
   408  			AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
   409  			Managed:          false,
   410  			Reattach:         reattach,
   411  			SyncStdout:       logging.PluginOutputMonitor(fmt.Sprintf("%s:stdout", provider)),
   412  			SyncStderr:       logging.PluginOutputMonitor(fmt.Sprintf("%s:stderr", provider)),
   413  		}
   414  
   415  		if reattach.ProtocolVersion == 0 {
   416  			// As of the 0.15 release, sdk.v2 doesn't include the protocol
   417  			// version in the ReattachConfig (only recently added to
   418  			// go-plugin), so client.NegotiatedVersion() always returns 0. We
   419  			// assume that an unmanaged provider reporting protocol version 0 is
   420  			// actually using proto v5 for backwards compatibility.
   421  			if defaultPlugins, ok := tfplugin.VersionedPlugins[5]; ok {
   422  				config.Plugins = defaultPlugins
   423  			} else {
   424  				return nil, errors.New("no supported plugins for protocol 0")
   425  			}
   426  		} else if plugins, ok := tfplugin.VersionedPlugins[reattach.ProtocolVersion]; !ok {
   427  			return nil, fmt.Errorf("no supported plugins for protocol %d", reattach.ProtocolVersion)
   428  		} else {
   429  			config.Plugins = plugins
   430  		}
   431  
   432  		client := plugin.NewClient(config)
   433  		rpcClient, err := client.Client()
   434  		if err != nil {
   435  			return nil, err
   436  		}
   437  
   438  		raw, err := rpcClient.Dispense(tfplugin.ProviderPluginName)
   439  		if err != nil {
   440  			return nil, err
   441  		}
   442  
   443  		// store the client so that the plugin can kill the child process
   444  		protoVer := client.NegotiatedVersion()
   445  		switch protoVer {
   446  		case 0, 5:
   447  			// As of the 0.15 release, sdk.v2 doesn't include the protocol
   448  			// version in the ReattachConfig (only recently added to
   449  			// go-plugin), so client.NegotiatedVersion() always returns 0. We
   450  			// assume that an unmanaged provider reporting protocol version 0 is
   451  			// actually using proto v5 for backwards compatibility.
   452  			p := raw.(*tfplugin.GRPCProvider)
   453  			p.PluginClient = client
   454  			return p, nil
   455  		case 6:
   456  			p := raw.(*tfplugin6.GRPCProvider)
   457  			p.PluginClient = client
   458  			return p, nil
   459  		default:
   460  			return nil, fmt.Errorf("unsupported protocol version %d", protoVer)
   461  		}
   462  	}
   463  }
   464  
   465  // providerFactoryError is a stub providers.Factory that returns an error
   466  // when called. It's used to allow providerFactories to still produce a
   467  // factory for each available provider in an error case, for situations
   468  // where the caller can do something useful with that partial result.
   469  func providerFactoryError(err error) providers.Factory {
   470  	return func() (providers.Interface, error) {
   471  		return nil, err
   472  	}
   473  }