diff --git a/backend/local/testing.go b/backend/local/testing.go index fb73cce877..60af8e9d94 100644 --- a/backend/local/testing.go +++ b/backend/local/testing.go @@ -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 diff --git a/command/apply_test.go b/command/apply_test.go index 743f608158..d6daf971e1 100644 --- a/command/apply_test.go +++ b/command/apply_test.go @@ -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) diff --git a/command/command_test.go b/command/command_test.go index 81a5b41d7c..1dfd4bf09e 100644 --- a/command/command_test.go +++ b/command/command_test.go @@ -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), }, diff --git a/command/init.go b/command/init.go index 134391a715..c567aa232a 100644 --- a/command/init.go +++ b/command/init.go @@ -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 diff --git a/command/meta.go b/command/meta.go index 7ef6fbbf5e..f2de72c53e 100644 --- a/command/meta.go +++ b/command/meta.go @@ -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() } diff --git a/command/meta_providers.go b/command/meta_providers.go index 8c2a554cf5..b93accb727 100644 --- a/command/meta_providers.go +++ b/command/meta_providers.go @@ -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 + } +} diff --git a/command/plugins.go b/command/plugins.go index 85c18d49ac..ce408f9756 100644 --- a/command/plugins.go +++ b/command/plugins.go @@ -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) diff --git a/command/state_show_test.go b/command/state_show_test.go index b91cca0b8c..d3dff9b2d0 100644 --- a/command/state_show_test.go +++ b/command/state_show_test.go @@ -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, }, diff --git a/command/version.go b/command/version.go index 9c061a84da..2a3c1056ae 100644 --- a/command/version.go +++ b/command/version.go @@ -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 { diff --git a/helper/resource/testing.go b/helper/resource/testing.go index 495e26d158..f7192b6bc7 100644 --- a/helper/resource/testing.go +++ b/helper/resource/testing.go @@ -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 diff --git a/providers/resolver.go b/providers/factory.go similarity index 55% rename from providers/resolver.go rename to providers/factory.go index 2ef387e467..1586ca3417 100644 --- a/providers/resolver.go +++ b/providers/factory.go @@ -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) diff --git a/terraform/context.go b/terraform/context.go index 1464833658..563a6528e6 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -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, } diff --git a/terraform/resource_provider.go b/terraform/resource_provider.go index a085e4fdca..8afe377414 100644 --- a/terraform/resource_provider.go +++ b/terraform/resource_provider.go @@ -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".