command: Minimal integration of new provider installer in "init"
There's still a lot of work to do here around both the UX and the follow-up steps that need to happen after installation completes, but this is enough to faciliate some initial end-to-end testing of the new-style install process.
This commit is contained in:
parent
3ac0410fe2
commit
94e1ac2d07
164
command/init.go
164
command/init.go
|
@ -1,10 +1,9 @@
|
||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl/v2"
|
"github.com/hashicorp/hcl/v2"
|
||||||
|
@ -12,19 +11,19 @@ import (
|
||||||
"github.com/posener/complete"
|
"github.com/posener/complete"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
"github.com/hashicorp/errwrap"
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
backendInit "github.com/hashicorp/terraform/backend/init"
|
backendInit "github.com/hashicorp/terraform/backend/init"
|
||||||
"github.com/hashicorp/terraform/configs"
|
"github.com/hashicorp/terraform/configs"
|
||||||
"github.com/hashicorp/terraform/configs/configschema"
|
"github.com/hashicorp/terraform/configs/configschema"
|
||||||
"github.com/hashicorp/terraform/internal/earlyconfig"
|
"github.com/hashicorp/terraform/internal/earlyconfig"
|
||||||
|
"github.com/hashicorp/terraform/internal/getproviders"
|
||||||
"github.com/hashicorp/terraform/internal/initwd"
|
"github.com/hashicorp/terraform/internal/initwd"
|
||||||
|
"github.com/hashicorp/terraform/internal/providercache"
|
||||||
|
"github.com/hashicorp/terraform/moduledeps"
|
||||||
"github.com/hashicorp/terraform/plugin/discovery"
|
"github.com/hashicorp/terraform/plugin/discovery"
|
||||||
"github.com/hashicorp/terraform/states"
|
"github.com/hashicorp/terraform/states"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
"github.com/hashicorp/terraform/version"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// InitCommand is a Command implementation that takes a Terraform
|
// InitCommand is a Command implementation that takes a Terraform
|
||||||
|
@ -444,118 +443,76 @@ the backend configuration is present and valid.
|
||||||
// Load the complete module tree, and fetch any missing providers.
|
// Load the complete module tree, and fetch any missing providers.
|
||||||
// This method outputs its own Ui.
|
// This method outputs its own Ui.
|
||||||
func (c *InitCommand) getProviders(earlyConfig *earlyconfig.Config, state *states.State, upgrade bool) (output bool, diags tfdiags.Diagnostics) {
|
func (c *InitCommand) getProviders(earlyConfig *earlyconfig.Config, state *states.State, upgrade bool) (output bool, diags tfdiags.Diagnostics) {
|
||||||
var available discovery.PluginMetaSet
|
// First we'll collect all the provider dependencies we can see in the
|
||||||
if upgrade {
|
// configuration and the state.
|
||||||
// If we're in upgrade mode, we ignore any auto-installed plugins
|
reqs := make(map[addrs.Provider]getproviders.VersionConstraints)
|
||||||
// in "available", causing us to reinstall and possibly upgrade them.
|
|
||||||
available = c.providerPluginManuallyInstalledSet()
|
|
||||||
} else {
|
|
||||||
available = c.providerPluginSet()
|
|
||||||
}
|
|
||||||
|
|
||||||
configDeps, depsDiags := earlyConfig.ProviderDependencies()
|
configDeps, depsDiags := earlyConfig.ProviderDependencies()
|
||||||
diags = diags.Append(depsDiags)
|
diags = diags.Append(depsDiags)
|
||||||
if depsDiags.HasErrors() {
|
if depsDiags.HasErrors() {
|
||||||
return false, diags
|
return false, diags
|
||||||
}
|
}
|
||||||
|
err := configDeps.WalkTree(func(path []string, parent *moduledeps.Module, current *moduledeps.Module) error {
|
||||||
configReqs := configDeps.AllProviderRequirements()
|
for addr, dep := range current.Providers {
|
||||||
// FIXME: This is weird because ConfigTreeDependencies was written before
|
// Our moduledeps API is still using the older model for capturing
|
||||||
// we switched over to using earlyConfig as the main source of dependencies.
|
// version constraints, so we need some light conversion here until
|
||||||
// In future we should clean this up to be a more reasonable API.
|
// we get everything else updated to use getproviders.VersionConstraints.
|
||||||
stateReqs := terraform.ConfigTreeDependencies(nil, state).AllProviderRequirements()
|
// This is gross but avoids doing lots of cross-cutting rework
|
||||||
|
// all at once.
|
||||||
requirements := configReqs.Merge(stateReqs)
|
constraintsStr := dep.Constraints.String()
|
||||||
if len(requirements) == 0 {
|
constraints, err := getproviders.ParseVersionConstraints(constraintsStr)
|
||||||
// nothing to initialize
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Ui.Output(c.Colorize().Color(
|
|
||||||
"\n[reset][bold]Initializing provider plugins...",
|
|
||||||
))
|
|
||||||
|
|
||||||
missing := c.missingProviders(available, requirements)
|
|
||||||
|
|
||||||
if c.getPlugins {
|
|
||||||
if len(missing) > 0 {
|
|
||||||
c.Ui.Output("- Checking for available provider plugins...")
|
|
||||||
}
|
|
||||||
|
|
||||||
for provider, reqd := range missing {
|
|
||||||
pty := addrs.NewLegacyProvider(provider)
|
|
||||||
_, providerDiags, err := c.providerInstaller.Get(pty, reqd.Versions)
|
|
||||||
diags = diags.Append(providerDiags)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
constraint := reqd.Versions.String()
|
return err
|
||||||
if constraint == "" {
|
}
|
||||||
constraint = "(any version)"
|
reqs[addr] = append(reqs[addr], constraints...)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
// This should never happen: indicates that our old version model
|
||||||
|
// produced a string representation of constraints that our new
|
||||||
|
// one couldn't parse. That's a bug.
|
||||||
|
diags = diags.Append(fmt.Errorf("internal error handling provider version constraints (this is a bug): %s", err))
|
||||||
|
return false, diags
|
||||||
|
}
|
||||||
|
if state != nil {
|
||||||
|
for _, configAddr := range state.ProviderAddrs() {
|
||||||
|
if _, ok := reqs[configAddr.Provider]; !ok {
|
||||||
|
reqs[configAddr.Provider] = nil // just needs to be present, unconstrained
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
// TODO: If the user gave at least one -plugin-dir option on the command
|
||||||
case err == discovery.ErrorServiceUnreachable, err == discovery.ErrorPublicRegistryUnreachable:
|
// line, we should construct a one-off getproviders.Source that consults
|
||||||
c.Ui.Error(errDiscoveryServiceUnreachable)
|
// only those directories and use that instead of c.providerInstallSource()
|
||||||
case err == discovery.ErrorNoSuchProvider:
|
// here.
|
||||||
c.Ui.Error(fmt.Sprintf(errProviderNotFound, provider, DefaultPluginVendorDir))
|
targetDir := c.providerLocalCacheDir()
|
||||||
case err == discovery.ErrorNoSuitableVersion:
|
globalCacheDir := c.providerGlobalCacheDir()
|
||||||
if reqd.Versions.Unconstrained() {
|
source := c.providerInstallSource()
|
||||||
// This should never happen, but might crop up if we catch
|
inst := providercache.NewInstaller(targetDir, source)
|
||||||
// the releases server in a weird state where the provider's
|
if globalCacheDir != nil {
|
||||||
// directory is present but does not yet contain any
|
inst.SetGlobalCacheDir(globalCacheDir)
|
||||||
// versions. We'll treat it like ErrorNoSuchProvider, then.
|
|
||||||
c.Ui.Error(fmt.Sprintf(errProviderNotFound, provider, DefaultPluginVendorDir))
|
|
||||||
} else {
|
|
||||||
c.Ui.Error(fmt.Sprintf(errProviderVersionsUnsuitable, provider, reqd.Versions))
|
|
||||||
}
|
|
||||||
case errwrap.Contains(err, discovery.ErrorVersionIncompatible.Error()):
|
|
||||||
// Attempt to fetch nested error to display to the user which versions
|
|
||||||
// we considered and which versions might be compatible. Otherwise,
|
|
||||||
// we'll just display a generic version incompatible msg
|
|
||||||
incompatErr := errwrap.GetType(err, fmt.Errorf(""))
|
|
||||||
if incompatErr != nil {
|
|
||||||
c.Ui.Error(incompatErr.Error())
|
|
||||||
} else {
|
|
||||||
// Generic version incompatible msg
|
|
||||||
c.Ui.Error(fmt.Sprintf(errProviderIncompatible, provider, constraint))
|
|
||||||
}
|
|
||||||
// Reset nested errors
|
|
||||||
err = discovery.ErrorVersionIncompatible
|
|
||||||
case err == discovery.ErrorNoVersionCompatible:
|
|
||||||
// Generic version incompatible msg
|
|
||||||
c.Ui.Error(fmt.Sprintf(errProviderIncompatible, provider, constraint))
|
|
||||||
case err == discovery.ErrorSignatureVerification:
|
|
||||||
c.Ui.Error(fmt.Sprintf(errSignatureVerification, provider, version.SemVer))
|
|
||||||
case err == discovery.ErrorChecksumVerification,
|
|
||||||
err == discovery.ErrorMissingChecksumVerification:
|
|
||||||
c.Ui.Error(fmt.Sprintf(errChecksumVerification, provider))
|
|
||||||
default:
|
|
||||||
c.Ui.Error(fmt.Sprintf(errProviderInstallError, provider, err.Error(), DefaultPluginVendorDir))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mode := providercache.InstallNewProvidersOnly
|
||||||
|
if upgrade {
|
||||||
|
mode = providercache.InstallUpgrades
|
||||||
|
}
|
||||||
|
// TODO: Use the context-based InstallerEvents API to get notifications
|
||||||
|
// about ongoing progress here, so we can update the UI along the way.
|
||||||
|
_, err = inst.EnsureProviderVersions(context.TODO(), reqs, mode)
|
||||||
|
if err != nil {
|
||||||
|
// Temporary clumsy error handling, because we'll replace this with
|
||||||
|
// ongoing progress via InstallerEvents before we ship it.
|
||||||
diags = diags.Append(err)
|
diags = diags.Append(err)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if diags.HasErrors() {
|
return false, diags
|
||||||
return true, diags
|
|
||||||
}
|
|
||||||
} else if len(missing) > 0 {
|
|
||||||
// we have missing providers, but aren't going to try and download them
|
|
||||||
var lines []string
|
|
||||||
for provider, reqd := range missing {
|
|
||||||
if reqd.Versions.Unconstrained() {
|
|
||||||
lines = append(lines, fmt.Sprintf("* %s (any version)\n", provider))
|
|
||||||
} else {
|
|
||||||
lines = append(lines, fmt.Sprintf("* %s (%s)\n", provider, reqd.Versions))
|
|
||||||
}
|
|
||||||
diags = diags.Append(fmt.Errorf("missing provider %q", provider))
|
|
||||||
}
|
|
||||||
sort.Strings(lines)
|
|
||||||
c.Ui.Error(fmt.Sprintf(errMissingProvidersNoInstall, strings.Join(lines, ""), DefaultPluginVendorDir))
|
|
||||||
return true, diags
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// TODO: Write the selections into the plugins lock file so we can be
|
||||||
|
// sure that future commands will use exactly those provider packages.
|
||||||
|
// TODO: Emit constraint suggestions for unconstrained providers.
|
||||||
|
/*
|
||||||
// With all the providers downloaded, we'll generate our lock file
|
// With all the providers downloaded, we'll generate our lock file
|
||||||
// that ensures the provider binaries remain unchanged until we init
|
// that ensures the provider binaries remain unchanged until we init
|
||||||
// again. If anything changes, other commands that use providers will
|
// again. If anything changes, other commands that use providers will
|
||||||
|
@ -629,6 +586,7 @@ func (c *InitCommand) getProviders(earlyConfig *earlyconfig.Config, state *state
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, diags
|
return true, diags
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
// backendConfigOverrideBody interprets the raw values of -backend-config
|
// backendConfigOverrideBody interprets the raw values of -backend-config
|
||||||
|
|
Loading…
Reference in New Issue