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:
Martin Atkins 2020-03-24 14:51:55 -07:00
parent 3ac0410fe2
commit 94e1ac2d07
1 changed files with 132 additions and 174 deletions

View File

@ -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