From 549aede7928a5ecbceeddc829d6560d064c28099 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Mon, 30 Mar 2020 15:30:56 -0700 Subject: [PATCH] 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. --- backend/local/testing.go | 8 +- command/apply_test.go | 2 +- command/command_test.go | 16 +- command/init.go | 31 +--- command/meta.go | 20 ++- command/meta_providers.go | 142 +++++++++++++++++ command/plugins.go | 217 +------------------------- command/state_show_test.go | 8 +- command/version.go | 36 ++--- helper/resource/testing.go | 59 ++++--- providers/{resolver.go => factory.go} | 51 ------ terraform/context.go | 30 +--- terraform/resource_provider.go | 97 +----------- 13 files changed, 235 insertions(+), 482 deletions(-) rename providers/{resolver.go => factory.go} (55%) diff --git a/backend/local/testing.go b/backend/local/testing.go index fb73cce87..60af8e9d9 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 743f60815..d6daf971e 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 81a5b41d7..1dfd4bf09 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 134391a71..c567aa232 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 7ef6fbbf5..f2de72c53 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 8c2a554cf..b93accb72 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 85c18d49a..ce408f975 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 b91cca0b8..d3dff9b2d 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 9c061a84d..2a3c1056a 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 495e26d15..f7192b6bc 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 2ef387e46..1586ca341 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 146483365..563a6528e 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 a085e4fdc..8afe37741 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".