Merge pull request #15371 from hashicorp/jbardin/reinit-error
better UI output for requesting plugin related init
This commit is contained in:
commit
77cbd3bfc8
|
@ -8,6 +8,7 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
|
@ -407,3 +408,25 @@ func (b *Local) stateWorkspaceDir() string {
|
|||
|
||||
return DefaultWorkspaceDir
|
||||
}
|
||||
|
||||
func (b *Local) pluginInitRequired(providerErr *terraform.ResourceProviderError) {
|
||||
b.CLI.Output(b.Colorize().Color(fmt.Sprintf(
|
||||
strings.TrimSpace(errPluginInit)+"\n",
|
||||
providerErr)))
|
||||
}
|
||||
|
||||
// this relies on multierror to format the plugin errors below the copy
|
||||
const errPluginInit = `
|
||||
[reset][bold][yellow]Plugin reinitialization required. Please run "terraform init".[reset]
|
||||
[yellow]Reason: Could not satisfy plugin requirements.
|
||||
|
||||
Plugins are external binaries that Terraform uses to access and manipulate
|
||||
resources. The configuration provided requires plugins which can't be located,
|
||||
don't satisfy the version constraints, or are otherwise incompatible.
|
||||
|
||||
[reset][red]%s
|
||||
|
||||
[reset][yellow]Terraform automatically discovers provider requirements from your
|
||||
configuration, including providers used in child modules. To see the
|
||||
requirements and constraints from each module, run "terraform providers".
|
||||
`
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package local
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
@ -57,6 +58,15 @@ func (b *Local) context(op *backend.Operation) (*terraform.Context, state.State,
|
|||
} else {
|
||||
tfCtx, err = terraform.NewContext(&opts)
|
||||
}
|
||||
|
||||
// any errors resolving plugins returns this
|
||||
if rpe, ok := err.(*terraform.ResourceProviderError); ok {
|
||||
b.pluginInitRequired(rpe)
|
||||
// we wrote the full UI error here, so return a generic error for flow
|
||||
// control in the command.
|
||||
return nil, nil, errors.New("error satisfying plugin requirements")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package command
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
@ -45,7 +46,7 @@ func (r *multiVersionProviderResolver) ResolveProviders(
|
|||
var errs []error
|
||||
|
||||
chosen := choosePlugins(r.Available, reqd)
|
||||
for name := range reqd {
|
||||
for name, req := range reqd {
|
||||
if newest, available := chosen[name]; available {
|
||||
digest, err := newest.SHA256()
|
||||
if err != nil {
|
||||
|
@ -53,19 +54,34 @@ func (r *multiVersionProviderResolver) ResolveProviders(
|
|||
continue
|
||||
}
|
||||
if !reqd[name].AcceptsSHA256(digest) {
|
||||
// This generic error message is intended to avoid troubling
|
||||
// users with implementation details. The main useful point
|
||||
// here is that they need to run "terraform init" to
|
||||
// fix this, which is covered by the UI code reporting these
|
||||
// error messages.
|
||||
errs = append(errs, fmt.Errorf("provider.%s: installed but not yet initialized", name))
|
||||
errs = append(errs, fmt.Errorf("provider.%s: new or changed plugin executable", name))
|
||||
continue
|
||||
}
|
||||
|
||||
client := tfplugin.Client(newest)
|
||||
factories[name] = providerFactory(client)
|
||||
} else {
|
||||
errs = append(errs, fmt.Errorf("provider.%s: no suitable version installed", name))
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package terraform
|
|||
|
||||
import (
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -861,7 +862,7 @@ func TestContext2Refresh_unknownProvider(t *testing.T) {
|
|||
t.Fatal("successfully created context; want error")
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), "Can't satisfy provider requirements") {
|
||||
if !regexp.MustCompile(`provider ".+" is not available`).MatchString(err.Error()) {
|
||||
t.Fatalf("wrong error: %s", err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
multierror "github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/terraform/plugin/discovery"
|
||||
)
|
||||
|
||||
|
@ -162,6 +161,18 @@ type ResourceProvider interface {
|
|||
ReadDataApply(*InstanceInfo, *InstanceDiff) (*InstanceState, error)
|
||||
}
|
||||
|
||||
// ResourceProviderError may be returned when creating a Context if the
|
||||
// required providers cannot be satisfied. This error can then be used to
|
||||
// format a more useful message for the user.
|
||||
type ResourceProviderError struct {
|
||||
Errors []error
|
||||
}
|
||||
|
||||
func (e *ResourceProviderError) Error() string {
|
||||
// use multierror to format the default output
|
||||
return multierror.Append(nil, e.Errors...).Error()
|
||||
}
|
||||
|
||||
// ResourceProviderCloser is an interface that providers that can close
|
||||
// connections that aren't needed anymore must implement.
|
||||
type ResourceProviderCloser interface {
|
||||
|
@ -265,13 +276,9 @@ func ProviderHasDataSource(p ResourceProvider, n string) bool {
|
|||
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)
|
||||
return nil, &ResourceProviderError{
|
||||
Errors: errs,
|
||||
}
|
||||
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