Remove terraform.ResourceProvider, use providercache.Installer instead

Back when we first introduced provider versioning in Terraform 0.10, we
did the provider version resolution in terraform.NewContext because we
weren't sure yet how exactly our versioning model was going to play out
(whether different versions could be selected per provider configuration,
for example) and because we were building around the limitations of our
existing filesystem-based plugin discovery model.

However, the new installer codepath is new able to do all of the
selections up front during installation, so we don't need such a heavy
inversion of control abstraction to get this done: the command package can
select the exact provider versions and pass their factories directly
to terraform.NewContext as a simple static map.

The result of this commit is that CLI commands other than "init" are now
able to consume the local cache directory and selections produced by the
installation process in "terraform init", passing all of the selected
providers down to the terraform.NewContext function for use in
implementing the main operations.

This commit is just enough to get the providers passing into the
terraform.Context. There's still plenty more to do here, including to
repair all of the tests this change has additionally broken.
This commit is contained in:
Martin Atkins 2020-03-30 15:30:56 -07:00
parent 5aa2e5ec8c
commit 549aede792
13 changed files with 235 additions and 482 deletions

View File

@ -112,11 +112,9 @@ func TestLocalProvider(t *testing.T, b *Local, name string, schema *terraform.Pr
}
// Setup our provider
b.ContextOpts.ProviderResolver = providers.ResolverFixed(
map[addrs.Provider]providers.Factory{
addrs.NewLegacyProvider(name): providers.FactoryFixed(p),
},
)
b.ContextOpts.Providers = map[addrs.Provider]providers.Factory{
addrs.NewLegacyProvider(name): providers.FactoryFixed(p),
}
return p

View File

@ -206,7 +206,7 @@ func TestApply_parallelism(t *testing.T) {
providerFactories[addrs.NewLegacyProvider(name)] = providers.FactoryFixed(provider)
}
testingOverrides := &testingOverrides{
ProviderResolver: providers.ResolverFixed(providerFactories),
Providers: providerFactories,
}
ui := new(cli.MockUi)

View File

@ -119,21 +119,17 @@ func testFixturePath(name string) string {
func metaOverridesForProvider(p providers.Interface) *testingOverrides {
return &testingOverrides{
ProviderResolver: providers.ResolverFixed(
map[addrs.Provider]providers.Factory{
addrs.NewLegacyProvider("test"): providers.FactoryFixed(p),
},
),
Providers: map[addrs.Provider]providers.Factory{
addrs.NewLegacyProvider("test"): providers.FactoryFixed(p),
},
}
}
func metaOverridesForProviderAndProvisioner(p providers.Interface, pr provisioners.Interface) *testingOverrides {
return &testingOverrides{
ProviderResolver: providers.ResolverFixed(
map[addrs.Provider]providers.Factory{
addrs.NewLegacyProvider("test"): providers.FactoryFixed(p),
},
),
Providers: map[addrs.Provider]providers.Factory{
addrs.NewLegacyProvider("test"): providers.FactoryFixed(p),
},
Provisioners: map[string]provisioners.Factory{
"shell": provisioners.FactoryFixed(pr),
},

View File

@ -20,7 +20,6 @@ import (
"github.com/hashicorp/terraform/internal/getproviders"
"github.com/hashicorp/terraform/internal/initwd"
"github.com/hashicorp/terraform/internal/providercache"
"github.com/hashicorp/terraform/plugin/discovery"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/tfdiags"
)
@ -32,12 +31,6 @@ type InitCommand struct {
// getPlugins is for the -get-plugins flag
getPlugins bool
// providerInstaller is used to download and install providers that
// aren't found locally. This uses a discovery.ProviderInstaller instance
// by default, but it can be overridden here as a way to mock fetching
// providers for tests.
providerInstaller discovery.Installer
}
func (c *InitCommand) Run(args []string) int {
@ -73,18 +66,6 @@ func (c *InitCommand) Run(args []string) int {
c.getPlugins = false
}
// set providerInstaller if we don't have a test version already
if c.providerInstaller == nil {
c.providerInstaller = &discovery.ProviderInstaller{
Dir: c.pluginDir(),
Cache: c.pluginCache(),
PluginProtocolVersion: discovery.PluginInstallProtocolVersion,
SkipVerify: !flagVerifyPlugins,
Ui: c.Ui,
Services: c.Services,
}
}
// Validate the arg count
args = cmdFlags.Args()
if len(args) > 1 {
@ -456,15 +437,9 @@ func (c *InitCommand) getProviders(earlyConfig *earlyconfig.Config, state *state
// TODO: If the user gave at least one -plugin-dir option on the command
// line, we should construct a one-off getproviders.Source that consults
// only those directories and use that instead of c.providerInstallSource()
// here.
targetDir := c.providerLocalCacheDir()
globalCacheDir := c.providerGlobalCacheDir()
source := c.providerInstallSource()
inst := providercache.NewInstaller(targetDir, source)
if globalCacheDir != nil {
inst.SetGlobalCacheDir(globalCacheDir)
}
// only those directories and pass that to c.providerInstallerCustomSource
// instead.
inst := c.providerInstaller()
// Because we're currently just streaming a series of events sequentially
// into the terminal, we're showing only a subset of the events to keep

View File

@ -187,8 +187,8 @@ type PluginOverrides struct {
}
type testingOverrides struct {
ProviderResolver providers.Resolver
Provisioners map[string]provisioners.Factory
Providers map[addrs.Provider]providers.Factory
Provisioners map[string]provisioners.Factory
}
// initStatePaths is used to initialize the default values for
@ -350,10 +350,22 @@ func (m *Meta) contextOpts() *terraform.ContextOpts {
// and just work with what we've been given, thus allowing the tests
// to provide mock providers and provisioners.
if m.testingOverrides != nil {
opts.ProviderResolver = m.testingOverrides.ProviderResolver
opts.Providers = m.testingOverrides.Providers
opts.Provisioners = m.testingOverrides.Provisioners
} else {
opts.ProviderResolver = m.providerResolver()
providerFactories, err := m.providerFactories()
if err != nil {
// providerFactories can fail if the plugin selections file is
// invalid in some way, but we don't have any way to report that
// from here so we'll just behave as if no providers are available
// in that case. However, we will produce a warning in case this
// shows up unexpectedly and prompts a bug report.
// This situation shouldn't arise commonly in practice because
// the selections file is generated programmatically.
log.Printf("[WARN] Failed to determine selected providers: %s", err)
providerFactories = nil
}
opts.Providers = providerFactories
opts.Provisioners = m.provisionerFactories()
}

View File

@ -1,12 +1,66 @@
package command
import (
"fmt"
"os"
"os/exec"
"path/filepath"
hclog "github.com/hashicorp/go-hclog"
plugin "github.com/hashicorp/go-plugin"
"github.com/hashicorp/terraform/addrs"
terraformProvider "github.com/hashicorp/terraform/builtin/providers/terraform"
"github.com/hashicorp/terraform/internal/getproviders"
"github.com/hashicorp/terraform/internal/providercache"
tfplugin "github.com/hashicorp/terraform/plugin"
"github.com/hashicorp/terraform/providers"
)
// The TF_DISABLE_PLUGIN_TLS environment variable is intended only for use by
// the plugin SDK test framework, to reduce startup overhead when rapidly
// launching and killing lots of instances of the same provider.
//
// This is not intended to be set by end-users.
var enableProviderAutoMTLS = os.Getenv("TF_DISABLE_PLUGIN_TLS") == ""
// providerInstaller returns an object that knows how to install providers and
// how to recover the selections from a prior installation process.
//
// The resulting provider installer is constructed from the results of
// the other methods providerLocalCacheDir, providerGlobalCacheDir, and
// providerInstallSource.
//
// Only one object returned from this method should be live at any time,
// because objects inside contain caches that must be maintained properly.
// Because this method wraps a result from providerLocalCacheDir, that
// limitation applies also to results from that method.
func (m *Meta) providerInstaller() *providercache.Installer {
return m.providerInstallerCustomSource(m.providerInstallSource())
}
// providerInstallerCustomSource is a variant of providerInstaller that
// allows the caller to specify a different installation source than the one
// that would naturally be selected.
//
// The result of this method has the same dependencies and constraints as
// providerInstaller.
//
// The result of providerInstallerCustomSource differs from
// providerInstaller only in how it determines package installation locations
// during EnsureProviderVersions. A caller that doesn't call
// EnsureProviderVersions (anything other than "terraform init") can safely
// just use the providerInstaller method unconditionally.
func (m *Meta) providerInstallerCustomSource(source getproviders.Source) *providercache.Installer {
targetDir := m.providerLocalCacheDir()
globalCacheDir := m.providerGlobalCacheDir()
inst := providercache.NewInstaller(targetDir, source)
if globalCacheDir != nil {
inst.SetGlobalCacheDir(globalCacheDir)
}
return inst
}
// providerLocalCacheDir returns an object representing the
// configuration-specific local cache directory. This is the
// only location consulted for provider plugin packages for Terraform
@ -15,6 +69,9 @@ import (
// Only the provider installer (in "terraform init") is permitted to make
// modifications to this cache directory. All other commands must treat it
// as read-only.
//
// Only one object returned from this method should be live at any time,
// because objects inside contain caches that must be maintained properly.
func (m *Meta) providerLocalCacheDir() *providercache.Dir {
dir := filepath.Join(m.DataDir(), "plugins")
if dir == "" {
@ -30,6 +87,9 @@ func (m *Meta) providerLocalCacheDir() *providercache.Dir {
// This function may return nil, in which case there is no global cache
// configured and new packages should be downloaded directly into individual
// configuration-specific cache directories.
//
// Only one object returned from this method should be live at any time,
// because objects inside contain caches that must be maintained properly.
func (m *Meta) providerGlobalCacheDir() *providercache.Dir {
dir := m.PluginCacheDir
if dir == "" {
@ -61,3 +121,85 @@ func (m *Meta) providerInstallSource() getproviders.Source {
}
return m.ProviderSource
}
// providerFactories uses the selections made previously by an installer in
// the local cache directory (m.providerLocalCacheDir) to produce a map
// from provider addresses to factory functions to create instances of
// those providers.
//
// providerFactories will return an error if the installer's selections cannot
// be honored with what is currently in the cache, such as if a selected
// package has been removed from the cache or if the contents of a selected
// package have been modified outside of the installer. If it returns an error,
// the returned map may be incomplete or invalid.
func (m *Meta) providerFactories() (map[addrs.Provider]providers.Factory, error) {
// We don't have to worry about potentially calling
// providerInstallerCustomSource here because we're only using this
// installer for its SelectedPackages method, which does not consult
// any provider sources.
inst := m.providerInstaller()
selected, err := inst.SelectedPackages()
if err != nil {
return nil, fmt.Errorf("failed to recall provider packages selected by earlier 'terraform init': %s", err)
}
// The internal providers are _always_ available, even if the configuration
// doesn't request them, because they don't need any special installation
// and they'll just be ignored if not used.
internalFactories := m.internalProviders()
factories := make(map[addrs.Provider]providers.Factory, len(selected)+len(internalFactories))
for name, factory := range internalFactories {
factories[addrs.NewBuiltInProvider(name)] = factory
}
for provider, cached := range selected {
factories[provider] = providerFactory(cached)
}
return factories, nil
}
func (m *Meta) internalProviders() map[string]providers.Factory {
return map[string]providers.Factory{
"terraform": func() (providers.Interface, error) {
return terraformProvider.NewProvider(), nil
},
}
}
// providerFactory produces a provider factory that runs up the executable
// file in the given cache package and uses go-plugin to implement
// providers.Interface against it.
func providerFactory(meta *providercache.CachedProvider) providers.Factory {
return func() (providers.Interface, error) {
logger := hclog.New(&hclog.LoggerOptions{
Name: "plugin",
Level: hclog.Trace,
Output: os.Stderr,
})
config := &plugin.ClientConfig{
Cmd: exec.Command(meta.ExecutableFile),
HandshakeConfig: tfplugin.Handshake,
VersionedPlugins: tfplugin.VersionedPlugins,
Managed: true,
Logger: logger,
AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
AutoMTLS: enableProviderAutoMTLS,
}
client := plugin.NewClient(config)
rpcClient, err := client.Client()
if err != nil {
return nil, err
}
raw, err := rpcClient.Dispense(tfplugin.ProviderPluginName)
if err != nil {
return nil, err
}
// store the client so that the plugin can kill the child process
p := raw.(*tfplugin.GRPCProvider)
p.PluginClient = client
return p, nil
}
}

View File

@ -2,7 +2,6 @@ package command
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
@ -15,104 +14,19 @@ import (
plugin "github.com/hashicorp/go-plugin"
"github.com/kardianos/osext"
"github.com/hashicorp/terraform/addrs"
terraformProvider "github.com/hashicorp/terraform/builtin/providers/terraform"
tfplugin "github.com/hashicorp/terraform/plugin"
"github.com/hashicorp/terraform/plugin/discovery"
"github.com/hashicorp/terraform/providers"
"github.com/hashicorp/terraform/provisioners"
"github.com/hashicorp/terraform/terraform"
)
// multiVersionProviderResolver is an implementation of
// terraform.ResourceProviderResolver that matches the given version constraints
// against a set of versioned provider plugins to find the newest version of
// each that satisfies the given constraints.
type multiVersionProviderResolver struct {
Available discovery.PluginMetaSet
// Internal is a map that overrides the usual plugin selection process
// for internal plugins. These plugins do not support version constraints
// (will produce an error if one is set). This should be used only in
// exceptional circumstances since it forces the provider's release
// schedule to be tied to that of Terraform Core.
Internal map[addrs.Provider]providers.Factory
}
func chooseProviders(avail discovery.PluginMetaSet, internal map[addrs.Provider]providers.Factory, reqd discovery.PluginRequirements) map[string]discovery.PluginMeta {
candidates := avail.ConstrainVersions(reqd)
ret := map[string]discovery.PluginMeta{}
for name, metas := range candidates {
// If the provider is in our internal map then we ignore any
// discovered plugins for it since these are dealt with separately.
if _, isInternal := internal[addrs.NewLegacyProvider(name)]; isInternal {
continue
}
if len(metas) == 0 {
continue
}
ret[name] = metas.Newest()
}
return ret
}
func (r *multiVersionProviderResolver) ResolveProviders(
reqd discovery.PluginRequirements,
) (map[addrs.Provider]providers.Factory, []error) {
factories := make(map[addrs.Provider]providers.Factory, len(reqd))
var errs []error
chosen := chooseProviders(r.Available, r.Internal, reqd)
for name, req := range reqd {
if factory, isInternal := r.Internal[addrs.NewLegacyProvider(name)]; isInternal {
if !req.Versions.Unconstrained() {
errs = append(errs, fmt.Errorf("provider.%s: this provider is built in to Terraform and so it does not support version constraints", name))
continue
}
factories[addrs.NewLegacyProvider(name)] = factory
continue
}
if newest, available := chosen[name]; available {
digest, err := newest.SHA256()
if err != nil {
errs = append(errs, fmt.Errorf("provider.%s: failed to load plugin to verify its signature: %s", name, err))
continue
}
if !reqd[name].AcceptsSHA256(digest) {
errs = append(errs, fmt.Errorf("provider.%s: new or changed plugin executable", name))
continue
}
factories[addrs.NewLegacyProvider(name)] = providerFactory(newest)
} else {
msg := fmt.Sprintf("provider.%s: no suitable version installed", name)
required := req.Versions.String()
// no version is unconstrained
if required == "" {
required = "(any version)"
}
foundVersions := []string{}
for meta := range r.Available.WithName(name) {
foundVersions = append(foundVersions, fmt.Sprintf("%q", meta.Version))
}
found := "none"
if len(foundVersions) > 0 {
found = strings.Join(foundVersions, ", ")
}
msg += fmt.Sprintf("\n version requirements: %q\n versions installed: %s", required, found)
errs = append(errs, errors.New(msg))
}
}
return factories, errs
}
// NOTE WELL: The logic in this file is primarily about plugin types OTHER THAN
// providers, which use an older set of approaches implemented here.
//
// The provider-related functions live primarily in meta_providers.go, and
// lean on some different underlying mechanisms in order to support automatic
// installation and a heirarchical addressing namespace, neither of which
// are supported for other plugin types.
// store the user-supplied path for plugin discovery
func (m *Meta) storePluginPath(pluginPath []string) error {
@ -216,101 +130,6 @@ func (m *Meta) pluginCache() discovery.PluginCache {
return discovery.NewLocalPluginCache(dir)
}
// providerPluginSet returns the set of valid providers that were discovered in
// the defined search paths.
func (m *Meta) providerPluginSet() discovery.PluginMetaSet {
plugins := discovery.FindPlugins("provider", m.pluginDirs(true))
// Add providers defined in the legacy .terraformrc,
if m.PluginOverrides != nil {
for k, v := range m.PluginOverrides.Providers {
log.Printf("[DEBUG] found plugin override in .terraformrc: %q, %q", k, v)
}
plugins = plugins.OverridePaths(m.PluginOverrides.Providers)
}
plugins, _ = plugins.ValidateVersions()
for p := range plugins {
log.Printf("[DEBUG] found valid plugin: %q, %q, %q", p.Name, p.Version, p.Path)
}
return plugins
}
// providerPluginAutoInstalledSet returns the set of providers that exist
// within the auto-install directory.
func (m *Meta) providerPluginAutoInstalledSet() discovery.PluginMetaSet {
plugins := discovery.FindPlugins("provider", []string{m.pluginDir()})
plugins, _ = plugins.ValidateVersions()
for p := range plugins {
log.Printf("[DEBUG] found valid plugin: %q", p.Name)
}
return plugins
}
// providerPluginManuallyInstalledSet returns the set of providers that exist
// in all locations *except* the auto-install directory.
func (m *Meta) providerPluginManuallyInstalledSet() discovery.PluginMetaSet {
plugins := discovery.FindPlugins("provider", m.pluginDirs(false))
// Add providers defined in the legacy .terraformrc,
if m.PluginOverrides != nil {
for k, v := range m.PluginOverrides.Providers {
log.Printf("[DEBUG] found plugin override in .terraformrc: %q, %q", k, v)
}
plugins = plugins.OverridePaths(m.PluginOverrides.Providers)
}
plugins, _ = plugins.ValidateVersions()
for p := range plugins {
log.Printf("[DEBUG] found valid plugin: %q, %q, %q", p.Name, p.Version, p.Path)
}
return plugins
}
func (m *Meta) providerResolver() providers.Resolver {
return &multiVersionProviderResolver{
Available: m.providerPluginSet(),
Internal: m.internalProviders(),
}
}
func (m *Meta) internalProviders() map[addrs.Provider]providers.Factory {
return map[addrs.Provider]providers.Factory{
addrs.NewLegacyProvider("terraform"): func() (providers.Interface, error) {
return terraformProvider.NewProvider(), nil
},
}
}
// filter the requirements returning only the providers that we can't resolve
func (m *Meta) missingProviders(avail discovery.PluginMetaSet, reqd discovery.PluginRequirements) discovery.PluginRequirements {
missing := make(discovery.PluginRequirements)
candidates := avail.ConstrainVersions(reqd)
internal := m.internalProviders()
for name, versionSet := range reqd {
// internal providers can't be missing
if _, ok := internal[addrs.NewLegacyProvider(name)]; ok {
continue
}
log.Printf("[DEBUG] plugin requirements: %q=%q", name, versionSet.Versions)
if metas := candidates[name]; metas.Count() == 0 {
missing[name] = versionSet
}
}
return missing
}
func (m *Meta) provisionerFactories() map[string]terraform.ProvisionerFactory {
dirs := m.pluginDirs(true)
plugins := discovery.FindPlugins("provisioner", dirs)
@ -364,28 +183,6 @@ func internalPluginClient(kind, name string) (*plugin.Client, error) {
return plugin.NewClient(cfg), nil
}
func providerFactory(meta discovery.PluginMeta) providers.Factory {
return func() (providers.Interface, error) {
client := tfplugin.Client(meta)
// Request the RPC client so we can get the provider
// so we can build the actual RPC-implemented provider.
rpcClient, err := client.Client()
if err != nil {
return nil, err
}
raw, err := rpcClient.Dispense(tfplugin.ProviderPluginName)
if err != nil {
return nil, err
}
// store the client so that the plugin can kill the child process
p := raw.(*tfplugin.GRPCProvider)
p.PluginClient = client
return p, nil
}
}
func provisionerFactory(meta discovery.PluginMeta) terraform.ProvisionerFactory {
return func() (provisioners.Interface, error) {
client := tfplugin.Client(meta)

View File

@ -229,11 +229,9 @@ func TestStateShow_configured_provider(t *testing.T) {
c := &StateShowCommand{
Meta: Meta{
testingOverrides: &testingOverrides{
ProviderResolver: providers.ResolverFixed(
map[addrs.Provider]providers.Factory{
addrs.NewLegacyProvider("test-beta"): providers.FactoryFixed(p),
},
),
Providers: map[addrs.Provider]providers.Factory{
addrs.NewLegacyProvider("test-beta"): providers.FactoryFixed(p),
},
},
Ui: ui,
},

View File

@ -55,33 +55,19 @@ func (c *VersionCommand) Run(args []string) int {
// Generally-speaking this is a best-effort thing that will give us a good
// result in the usual case where the user successfully ran "terraform init"
// and then hit a problem running _another_ command.
providerPlugins := c.providerPluginSet()
pluginsLockFile := c.providerPluginsLock()
pluginsLock := pluginsLockFile.Read()
providerInstaller := c.providerInstaller()
providerSelections, err := providerInstaller.SelectedPackages()
var pluginVersions []string
for meta := range providerPlugins {
name := meta.Name
wantHash, wanted := pluginsLock[name]
if !wanted {
// Ignore providers that aren't used by the current config at all
continue
}
gotHash, err := meta.SHA256()
if err != nil {
// if we can't read the file to hash it, ignore it.
continue
}
if !bytes.Equal(gotHash, wantHash) {
// Not the plugin we've locked, so ignore it.
continue
}
// If we get here then we've found a selected plugin, so we'll print
// out its details.
if meta.Version == "0.0.0" {
pluginVersions = append(pluginVersions, fmt.Sprintf("+ provider.%s (unversioned)", name))
if err != nil {
// we'll just ignore it and show no plugins at all, then.
providerSelections = nil
}
for providerAddr, cached := range providerSelections {
version := cached.Version.String()
if version == "0.0.0" {
pluginVersions = append(pluginVersions, fmt.Sprintf("+ provider %s (unversioned)", providerAddr))
} else {
pluginVersions = append(pluginVersions, fmt.Sprintf("+ provider.%s v%s", name, meta.Version))
pluginVersions = append(pluginVersions, fmt.Sprintf("+ provider %s v%s", providerAddr, version))
}
}
if len(pluginVersions) != 0 {

View File

@ -481,10 +481,19 @@ func Test(t TestT, c TestCase) {
c.PreCheck()
}
providerFactories, err := testProviderFactories(c)
if err != nil {
t.Fatal(err)
}
// get instances of all providers, so we can use the individual
// resources to shim the state during the tests.
providers := make(map[string]terraform.ResourceProvider)
for name, pf := range testProviderFactories(c) {
legacyProviderFactories, err := testProviderFactoriesLegacy(c)
if err != nil {
t.Fatal(err)
}
for name, pf := range legacyProviderFactories {
p, err := pf()
if err != nil {
t.Fatal(err)
@ -492,12 +501,7 @@ func Test(t TestT, c TestCase) {
providers[name] = p
}
providerResolver, err := testProviderResolver(c)
if err != nil {
t.Fatal(err)
}
opts := terraform.ContextOpts{ProviderResolver: providerResolver}
opts := terraform.ContextOpts{Providers: providerFactories}
// A single state variable to track the lifecycle, starting with no state
var state *terraform.State
@ -650,10 +654,14 @@ func testProviderConfig(c TestCase) string {
return strings.Join(lines, "")
}
// testProviderFactories combines the fixed Providers and
// ResourceProviderFactory functions into a single map of
// ResourceProviderFactory functions.
func testProviderFactories(c TestCase) map[string]terraform.ResourceProviderFactory {
// testProviderFactoriesLegacy is like testProviderFactories but it returns
// providers implementing the legacy interface terraform.ResourceProvider,
// rather than the current providers.Interface.
//
// It also identifies all providers as legacy-style single names rather than
// full addresses, for compatibility with legacy code that doesn't understand
// FQNs.
func testProviderFactoriesLegacy(c TestCase) (map[string]terraform.ResourceProviderFactory, error) {
ctxProviders := make(map[string]terraform.ResourceProviderFactory)
for k, pf := range c.ProviderFactories {
ctxProviders[k] = pf
@ -663,24 +671,25 @@ func testProviderFactories(c TestCase) map[string]terraform.ResourceProviderFact
for k, p := range c.Providers {
ctxProviders[k] = terraform.ResourceProviderFactoryFixed(p)
}
return ctxProviders
return ctxProviders, nil
}
// testProviderResolver is a helper to build a ResourceProviderResolver
// with pre instantiated ResourceProviders, so that we can reset them for the
// test, while only calling the factory function once.
// Any errors are stored so that they can be returned by the factory in
// terraform to match non-test behavior.
func testProviderResolver(c TestCase) (providers.Resolver, error) {
ctxProviders := testProviderFactories(c)
// testProviderFactories combines the fixed Providers and
// ResourceProviderFactory functions into a single map of
// ResourceProviderFactory functions.
func testProviderFactories(c TestCase) (map[addrs.Provider]providers.Factory, error) {
ctxProviders, err := testProviderFactoriesLegacy(c)
if err != nil {
return nil, err
}
// wrap the old provider factories in the test grpc server so they can be
// called from terraform.
// We additionally wrap all of the factories as a GRPCTestProvider, which
// allows them to appear as a new-style providers.Interface, rather than
// the legacy terraform.ResourceProvider.
newProviders := make(map[addrs.Provider]providers.Factory)
for k, pf := range ctxProviders {
for legacyName, pf := range ctxProviders {
factory := pf // must copy to ensure each closure sees its own value
newProviders[addrs.NewLegacyProvider(k)] = func() (providers.Interface, error) {
newProviders[addrs.NewLegacyProvider(legacyName)] = func() (providers.Interface, error) {
p, err := factory()
if err != nil {
return nil, err
@ -693,7 +702,7 @@ func testProviderResolver(c TestCase) (providers.Resolver, error) {
}
}
return providers.ResolverFixed(newProviders), nil
return newProviders, nil
}
// UnitTest is a helper to force the acceptance testing harness to run in the

View File

@ -1,56 +1,5 @@
package providers
import (
"fmt"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/plugin/discovery"
)
// Resolver is an interface implemented by objects that are able to resolve
// a given set of resource provider version constraints into Factory
// callbacks.
type Resolver interface {
// Given a constraint map, return a Factory for each requested provider.
// If some or all of the constraints cannot be satisfied, return a non-nil
// slice of errors describing the problems.
ResolveProviders(reqd discovery.PluginRequirements) (map[addrs.Provider]Factory, []error)
}
// ResolverFunc wraps a callback function and turns it into a Resolver
// implementation, for convenience in situations where a function and its
// associated closure are sufficient as a resolver implementation.
type ResolverFunc func(reqd discovery.PluginRequirements) (map[addrs.Provider]Factory, []error)
// ResolveProviders implements Resolver by calling the
// wrapped function.
func (f ResolverFunc) ResolveProviders(reqd discovery.PluginRequirements) (map[addrs.Provider]Factory, []error) {
return f(reqd)
}
// ResolverFixed returns a Resolver that has a fixed set of provider factories
// provided by the caller. The returned resolver ignores version constraints
// entirely and just returns the given factory for each requested provider
// name.
//
// This function is primarily used in tests, to provide mock providers or
// in-process providers under test.
func ResolverFixed(factories map[addrs.Provider]Factory) Resolver {
return ResolverFunc(func(reqd discovery.PluginRequirements) (map[addrs.Provider]Factory, []error) {
ret := make(map[addrs.Provider]Factory, len(reqd))
var errs []error
for name := range reqd {
fqn := addrs.NewLegacyProvider(name)
if factory, exists := factories[fqn]; exists {
ret[fqn] = factory
} else {
errs = append(errs, fmt.Errorf("provider %q is not available", name))
}
}
return ret, errs
})
}
// Factory is a function type that creates a new instance of a resource
// provider, or returns an error if that is impossible.
type Factory func() (Interface, error)

View File

@ -55,10 +55,10 @@ type ContextOpts struct {
Meta *ContextMeta
Destroy bool
Hooks []Hook
Parallelism int
ProviderResolver providers.Resolver
Provisioners map[string]ProvisionerFactory
Hooks []Hook
Parallelism int
Providers map[addrs.Provider]providers.Factory
Provisioners map[string]provisioners.Factory
// If non-nil, will apply as additional constraints on the provider
// plugins that will be requested from the provider resolver.
@ -169,28 +169,8 @@ func NewContext(opts *ContextOpts) (*Context, tfdiags.Diagnostics) {
// override the defaults.
variables = variables.Override(opts.Variables)
// Bind available provider plugins to the constraints in config
var providerFactories map[addrs.Provider]providers.Factory
if opts.ProviderResolver != nil {
deps := ConfigTreeDependencies(opts.Config, state)
reqd := deps.AllProviderRequirements()
if opts.ProviderSHA256s != nil && !opts.SkipProviderVerify {
reqd.LockExecutables(opts.ProviderSHA256s)
}
log.Printf("[TRACE] terraform.NewContext: resolving provider version selections")
var providerDiags tfdiags.Diagnostics
providerFactories, providerDiags = resourceProviderFactories(opts.ProviderResolver, reqd)
diags = diags.Append(providerDiags)
if diags.HasErrors() {
return nil, diags
}
} else {
providerFactories = make(map[addrs.Provider]providers.Factory)
}
components := &basicComponentFactory{
providers: providerFactories,
providers: opts.Providers,
provisioners: opts.Provisioners,
}

View File

@ -1,24 +1,10 @@
package terraform
import (
"fmt"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/tfdiags"
"github.com/hashicorp/terraform/plugin/discovery"
"github.com/hashicorp/terraform/providers"
)
// ResourceProvider is an interface that must be implemented by any
// resource provider: the thing that creates and manages the resources in
// a Terraform configuration.
// ResourceProvider is a legacy interface for providers.
//
// Important implementation note: All returned pointers, such as
// *ResourceConfig, *InstanceState, *InstanceDiff, etc. must not point to
// shared data. Terraform is highly parallel and assumes that this data is safe
// to read/write in parallel so it must be unique references. Note that it is
// safe to return arguments as results, however.
// This is retained only for compatibility with legacy code. The current
// interface for providers is providers.Interface, in the sibling directory
// named "providers".
type ResourceProvider interface {
/*********************************************************************
* Functions related to the provider
@ -203,53 +189,6 @@ type DataSource struct {
SchemaAvailable bool
}
// ResourceProviderResolver is an interface implemented by objects that are
// able to resolve a given set of resource provider version constraints
// into ResourceProviderFactory callbacks.
type ResourceProviderResolver interface {
// Given a constraint map, return a ResourceProviderFactory for each
// requested provider. If some or all of the constraints cannot be
// satisfied, return a non-nil slice of errors describing the problems.
ResolveProviders(reqd discovery.PluginRequirements) (map[addrs.Provider]ResourceProviderFactory, []error)
}
// ResourceProviderResolverFunc wraps a callback function and turns it into
// a ResourceProviderResolver implementation, for convenience in situations
// where a function and its associated closure are sufficient as a resolver
// implementation.
type ResourceProviderResolverFunc func(reqd discovery.PluginRequirements) (map[addrs.Provider]ResourceProviderFactory, []error)
// ResolveProviders implements ResourceProviderResolver by calling the
// wrapped function.
func (f ResourceProviderResolverFunc) ResolveProviders(reqd discovery.PluginRequirements) (map[addrs.Provider]ResourceProviderFactory, []error) {
return f(reqd)
}
// ResourceProviderResolverFixed returns a ResourceProviderResolver that
// has a fixed set of provider factories provided by the caller. The returned
// resolver ignores version constraints entirely and just returns the given
// factory for each requested provider name.
//
// This function is primarily used in tests, to provide mock providers or
// in-process providers under test.
func ResourceProviderResolverFixed(factories map[addrs.Provider]ResourceProviderFactory) ResourceProviderResolver {
return ResourceProviderResolverFunc(func(reqd discovery.PluginRequirements) (map[addrs.Provider]ResourceProviderFactory, []error) {
ret := make(map[addrs.Provider]ResourceProviderFactory, len(reqd))
var errs []error
for name := range reqd {
// FIXME: discovery.PluginRequirements should use addrs.Provider as
// the map keys instead of a string
fqn := addrs.NewLegacyProvider(name)
if factory, exists := factories[fqn]; exists {
ret[fqn] = factory
} else {
errs = append(errs, fmt.Errorf("provider %q is not available", name))
}
}
return ret, errs
})
}
// ResourceProviderFactory is a function type that creates a new instance
// of a resource provider.
type ResourceProviderFactory func() (ResourceProvider, error)
@ -282,34 +221,6 @@ func ProviderHasDataSource(p ResourceProvider, n string) bool {
return false
}
// resourceProviderFactories matches available plugins to the given version
// requirements to produce a map of compatible provider plugins if possible,
// or an error if the currently-available plugins are insufficient.
//
// This should be called only with configurations that have passed calls
// to config.Validate(), which ensures that all of the given version
// constraints are valid. It will panic if any invalid constraints are present.
func resourceProviderFactories(resolver providers.Resolver, reqd discovery.PluginRequirements) (map[addrs.Provider]providers.Factory, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
ret, errs := resolver.ResolveProviders(reqd)
if errs != nil {
diags = diags.Append(
tfdiags.Sourceless(tfdiags.Error,
"Could not satisfy plugin requirements",
errPluginInit,
),
)
for _, err := range errs {
diags = diags.Append(err)
}
return nil, diags
}
return ret, nil
}
const errPluginInit = `
Plugin reinitialization required. Please run "terraform init".