diff --git a/backend/local/backend.go b/backend/local/backend.go index 5956832580..02152c17c6 100644 --- a/backend/local/backend.go +++ b/backend/local/backend.go @@ -8,6 +8,7 @@ import ( "os" "path/filepath" "sort" + "strings" "sync" "github.com/hashicorp/terraform/backend" @@ -407,3 +408,24 @@ 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", + "Could not satisfy plugin requirements", + providerErr))) +} + +const errPluginInit = ` +[reset][bold][yellow]Plugin reinitialization required. Please run "terraform init".[reset] +[yellow]Reason: %s + +Plugins are external binaries that Terraform uses to access and manipulate +resources. If this message is showing up, it means that the configuration you +have references plugins which can't be located, don't satisfy the version +constraints, or are otherwise incompatible. + +The errors encountered discovering plugins are: + +%s +` diff --git a/backend/local/backend_local.go b/backend/local/backend_local.go index 7e5c872a14..0b829b6117 100644 --- a/backend/local/backend_local.go +++ b/backend/local/backend_local.go @@ -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 } diff --git a/terraform/resource_provider.go b/terraform/resource_provider.go index 37d59e4806..7d78f67ef9 100644 --- a/terraform/resource_provider.go +++ b/terraform/resource_provider.go @@ -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