mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
core: use ResourceProviderResolver to resolve providers
Previously the set of providers was fixed early on in the command package processing. In order to be version-aware we need to defer this work until later, so this interface exists so we can hold on to the possibly-many versions of plugins we have available and then later, once we've finished determining the provider dependencies, select the appropriate version of each provider to produce the final set of providers to use. This commit establishes the use of this new mechanism, and thus populates the provider factory map with only the providers that result from the dependency resolution process. This disables support for internal provider plugins, though the mechanisms for building and launching these are still here vestigially, to be cleaned up in a subsequent commit. This also adds a new awkward quirk to the "terraform import" workflow where one can't import a resource from a provider that isn't already mentioned (implicitly or explicitly) in config. We will do some UX work in subsequent commits to make this behavior better. This breaks many tests due to the change in interface, but to keep this particular diff reasonably easy to read the test fixes are split into a separate commit.
This commit is contained in:
parent
ba3ee00837
commit
7ca592ac06
@ -119,8 +119,8 @@ type PluginOverrides struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type testingOverrides struct {
|
type testingOverrides struct {
|
||||||
Providers map[string]terraform.ResourceProviderFactory
|
ProviderResolver terraform.ResourceProviderResolver
|
||||||
Provisioners map[string]terraform.ResourceProvisionerFactory
|
Provisioners map[string]terraform.ResourceProvisionerFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
// initStatePaths is used to initialize the default values for
|
// initStatePaths is used to initialize the default values for
|
||||||
@ -237,10 +237,10 @@ func (m *Meta) contextOpts() *terraform.ContextOpts {
|
|||||||
// and just work with what we've been given, thus allowing the tests
|
// and just work with what we've been given, thus allowing the tests
|
||||||
// to provide mock providers and provisioners.
|
// to provide mock providers and provisioners.
|
||||||
if m.testingOverrides != nil {
|
if m.testingOverrides != nil {
|
||||||
opts.Providers = m.testingOverrides.Providers
|
opts.ProviderResolver = m.testingOverrides.ProviderResolver
|
||||||
opts.Provisioners = m.testingOverrides.Provisioners
|
opts.Provisioners = m.testingOverrides.Provisioners
|
||||||
} else {
|
} else {
|
||||||
opts.Providers = m.providerFactories()
|
opts.ProviderResolver = m.providerResolver()
|
||||||
opts.Provisioners = m.provisionerFactories()
|
opts.Provisioners = m.provisionerFactories()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
@ -11,7 +12,35 @@ import (
|
|||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (m *Meta) providerFactories() map[string]terraform.ResourceProviderFactory {
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *multiVersionProviderResolver) ResolveProviders(
|
||||||
|
reqd discovery.PluginRequirements,
|
||||||
|
) (map[string]terraform.ResourceProviderFactory, []error) {
|
||||||
|
factories := make(map[string]terraform.ResourceProviderFactory, len(reqd))
|
||||||
|
var errs []error
|
||||||
|
|
||||||
|
candidates := r.Available.ConstrainVersions(reqd)
|
||||||
|
for name := range reqd {
|
||||||
|
if metas := candidates[name]; metas != nil {
|
||||||
|
newest := metas.Newest()
|
||||||
|
client := tfplugin.Client(newest)
|
||||||
|
factories[name] = providerFactory(client)
|
||||||
|
} else {
|
||||||
|
errs = append(errs, fmt.Errorf("provider.%s: no suitable version installed", name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return factories, errs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Meta) providerResolver() terraform.ResourceProviderResolver {
|
||||||
var dirs []string
|
var dirs []string
|
||||||
|
|
||||||
// When searching the following directories, earlier entries get precedence
|
// When searching the following directories, earlier entries get precedence
|
||||||
@ -25,36 +54,9 @@ func (m *Meta) providerFactories() map[string]terraform.ResourceProviderFactory
|
|||||||
plugins := discovery.FindPlugins("provider", dirs)
|
plugins := discovery.FindPlugins("provider", dirs)
|
||||||
plugins, _ = plugins.ValidateVersions()
|
plugins, _ = plugins.ValidateVersions()
|
||||||
|
|
||||||
// For now our goal is to just find the latest version of each plugin
|
return &multiVersionProviderResolver{
|
||||||
// we have on the system, emulating our pre-versioning behavior.
|
Available: plugins,
|
||||||
// TODO: Reorganize how providers are handled so that we can use
|
|
||||||
// version constraints from configuration to select which plugins
|
|
||||||
// we will use when multiple are available.
|
|
||||||
|
|
||||||
factories := make(map[string]terraform.ResourceProviderFactory)
|
|
||||||
|
|
||||||
// Wire up the internal provisioners first. These might be overridden
|
|
||||||
// by discovered providers below.
|
|
||||||
for name := range InternalProviders {
|
|
||||||
client, err := internalPluginClient("provider", name)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("[WARN] failed to build command line for internal plugin %q: %s", name, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
factories[name] = providerFactory(client)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
byName := plugins.ByName()
|
|
||||||
for name, metas := range byName {
|
|
||||||
// Since we validated versions above and we partitioned the sets
|
|
||||||
// by name, we're guaranteed that the metas in our set all have
|
|
||||||
// valid versions and that there's at least one meta.
|
|
||||||
newest := metas.Newest()
|
|
||||||
client := tfplugin.Client(newest)
|
|
||||||
factories[name] = providerFactory(client)
|
|
||||||
}
|
|
||||||
|
|
||||||
return factories
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Meta) provisionerFactories() map[string]terraform.ResourceProvisionerFactory {
|
func (m *Meta) provisionerFactories() map[string]terraform.ResourceProvisionerFactory {
|
||||||
|
@ -57,7 +57,7 @@ type ContextOpts struct {
|
|||||||
Parallelism int
|
Parallelism int
|
||||||
State *State
|
State *State
|
||||||
StateFutureAllowed bool
|
StateFutureAllowed bool
|
||||||
Providers map[string]ResourceProviderFactory
|
ProviderResolver ResourceProviderResolver
|
||||||
Provisioners map[string]ResourceProvisionerFactory
|
Provisioners map[string]ResourceProvisionerFactory
|
||||||
Shadow bool
|
Shadow bool
|
||||||
Targets []string
|
Targets []string
|
||||||
@ -166,7 +166,6 @@ func NewContext(opts *ContextOpts) (*Context, error) {
|
|||||||
// set by environment variables if necessary. This includes
|
// set by environment variables if necessary. This includes
|
||||||
// values taken from -var-file in addition.
|
// values taken from -var-file in addition.
|
||||||
variables := make(map[string]interface{})
|
variables := make(map[string]interface{})
|
||||||
|
|
||||||
if opts.Module != nil {
|
if opts.Module != nil {
|
||||||
var err error
|
var err error
|
||||||
variables, err = Variables(opts.Module, opts.Variables)
|
variables, err = Variables(opts.Module, opts.Variables)
|
||||||
@ -175,6 +174,20 @@ func NewContext(opts *ContextOpts) (*Context, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bind available provider plugins to the constraints in config
|
||||||
|
var providers map[string]ResourceProviderFactory
|
||||||
|
if opts.ProviderResolver != nil {
|
||||||
|
var err error
|
||||||
|
deps := moduleTreeDependencies(opts.Module, state)
|
||||||
|
reqd := deps.AllPluginRequirements()
|
||||||
|
providers, err = resourceProviderFactories(opts.ProviderResolver, reqd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
providers = make(map[string]ResourceProviderFactory)
|
||||||
|
}
|
||||||
|
|
||||||
diff := opts.Diff
|
diff := opts.Diff
|
||||||
if diff == nil {
|
if diff == nil {
|
||||||
diff = &Diff{}
|
diff = &Diff{}
|
||||||
@ -182,7 +195,7 @@ func NewContext(opts *ContextOpts) (*Context, error) {
|
|||||||
|
|
||||||
return &Context{
|
return &Context{
|
||||||
components: &basicComponentFactory{
|
components: &basicComponentFactory{
|
||||||
providers: opts.Providers,
|
providers: providers,
|
||||||
provisioners: opts.Provisioners,
|
provisioners: opts.Provisioners,
|
||||||
},
|
},
|
||||||
destroy: opts.Destroy,
|
destroy: opts.Destroy,
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package terraform
|
package terraform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/plugin/discovery"
|
"github.com/hashicorp/terraform/plugin/discovery"
|
||||||
@ -252,3 +254,25 @@ func ProviderHasDataSource(p ResourceProvider, n string) bool {
|
|||||||
|
|
||||||
return false
|
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 ResourceProviderResolver, reqd discovery.PluginRequirements) (map[string]ResourceProviderFactory, error) {
|
||||||
|
ret, errs := resolver.ResolveProviders(reqd)
|
||||||
|
if errs != nil {
|
||||||
|
errBuf := &bytes.Buffer{}
|
||||||
|
errBuf.WriteString("Can't satisfy provider requirements with currently-installed plugins:\n\n")
|
||||||
|
for _, err := range errs {
|
||||||
|
fmt.Fprintf(errBuf, "* %s\n", err)
|
||||||
|
}
|
||||||
|
errBuf.WriteString("\nRun 'terraform init' to install the necessary provider plugins.\n")
|
||||||
|
return nil, errors.New(errBuf.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user