Merge pull request #15371 from hashicorp/jbardin/reinit-error

better UI output for requesting plugin related init
This commit is contained in:
James Bardin 2017-06-22 15:53:13 -04:00 committed by GitHub
commit 77cbd3bfc8
5 changed files with 74 additions and 17 deletions

View File

@ -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".
`

View File

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

View File

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

View File

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

View File

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