mirror of
https://github.com/opentffoundation/opentf.git
synced 2026-01-02 22:00:19 -05:00
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.
166 lines
5.2 KiB
Go
166 lines
5.2 KiB
Go
package command
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os/exec"
|
|
"strings"
|
|
|
|
plugin "github.com/hashicorp/go-plugin"
|
|
tfplugin "github.com/hashicorp/terraform/plugin"
|
|
"github.com/hashicorp/terraform/plugin/discovery"
|
|
"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
|
|
}
|
|
|
|
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
|
|
|
|
// When searching the following directories, earlier entries get precedence
|
|
// if the same plugin version is found twice, but newer versions will
|
|
// always get preference below regardless of where they are coming from.
|
|
// TODO: Add auto-install dir, default vendor dir and optional override
|
|
// vendor dir(s).
|
|
dirs = append(dirs, ".")
|
|
dirs = append(dirs, m.GlobalPluginDirs...)
|
|
|
|
plugins := discovery.FindPlugins("provider", dirs)
|
|
plugins, _ = plugins.ValidateVersions()
|
|
|
|
return &multiVersionProviderResolver{
|
|
Available: plugins,
|
|
}
|
|
}
|
|
|
|
func (m *Meta) provisionerFactories() map[string]terraform.ResourceProvisionerFactory {
|
|
var dirs []string
|
|
|
|
// When searching the following directories, earlier entries get precedence
|
|
// if the same plugin version is found twice, but newer versions will
|
|
// always get preference below regardless of where they are coming from.
|
|
//
|
|
// NOTE: Currently we don't use versioning for provisioners, so the
|
|
// version handling here is just the minimum required to be able to use
|
|
// the plugin discovery package. All provisioner plugins should always
|
|
// be versionless, which we treat as version 0.0.0 here.
|
|
dirs = append(dirs, ".")
|
|
dirs = append(dirs, m.GlobalPluginDirs...)
|
|
|
|
plugins := discovery.FindPlugins("provisioner", dirs)
|
|
plugins, _ = plugins.ValidateVersions()
|
|
|
|
// For now our goal is to just find the latest version of each plugin
|
|
// we have on the system. All provisioners should be at version 0.0.0
|
|
// currently, so there should actually only be one instance of each plugin
|
|
// name here, even though the discovery interface forces us to pretend
|
|
// that might not be true.
|
|
|
|
factories := make(map[string]terraform.ResourceProvisionerFactory)
|
|
|
|
// Wire up the internal provisioners first. These might be overridden
|
|
// by discovered provisioners below.
|
|
for name := range InternalProvisioners {
|
|
client, err := internalPluginClient("provisioner", name)
|
|
if err != nil {
|
|
log.Printf("[WARN] failed to build command line for internal plugin %q: %s", name, err)
|
|
continue
|
|
}
|
|
factories[name] = provisionerFactory(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] = provisionerFactory(client)
|
|
}
|
|
|
|
return factories
|
|
}
|
|
|
|
func internalPluginClient(kind, name string) (*plugin.Client, error) {
|
|
cmdLine, err := BuildPluginCommandString(kind, name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// See the docstring for BuildPluginCommandString for why we need to do
|
|
// this split here.
|
|
cmdArgv := strings.Split(cmdLine, TFSPACE)
|
|
|
|
cfg := &plugin.ClientConfig{
|
|
Cmd: exec.Command(cmdArgv[0], cmdArgv[1:]...),
|
|
HandshakeConfig: tfplugin.Handshake,
|
|
Managed: true,
|
|
Plugins: tfplugin.PluginMap,
|
|
}
|
|
|
|
return plugin.NewClient(cfg), nil
|
|
}
|
|
|
|
func providerFactory(client *plugin.Client) terraform.ResourceProviderFactory {
|
|
return func() (terraform.ResourceProvider, error) {
|
|
// 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
|
|
}
|
|
|
|
return raw.(terraform.ResourceProvider), nil
|
|
}
|
|
}
|
|
|
|
func provisionerFactory(client *plugin.Client) terraform.ResourceProvisionerFactory {
|
|
return func() (terraform.ResourceProvisioner, error) {
|
|
// Request the RPC client so we can get the provisioner
|
|
// so we can build the actual RPC-implemented provisioner.
|
|
rpcClient, err := client.Client()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
raw, err := rpcClient.Dispense(tfplugin.ProvisionerPluginName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return raw.(terraform.ResourceProvisioner), nil
|
|
}
|
|
}
|