From 5be15ed77cfa1d9a8417c1f69904753a733d28e1 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Thu, 22 Jun 2017 13:15:30 -0400 Subject: [PATCH] have the local backend provide a plugin init msg During plan and apply, because the provider constraints need to be built from a plan, they are not checked until the terraform.Context is created. Since the context is always requested by the backend during the Operation, the backend needs to be responsible for generating contextual error messages for the user. Instead of formatting the ResolveProviders errors during NewContext, return a special error type, ResourceProviderError to signal that init will be required. The backend can then extract and format the errors. --- backend/local/backend.go | 22 ++++++++++++++++++++++ backend/local/backend_local.go | 10 ++++++++++ terraform/resource_provider.go | 23 +++++++++++++++-------- 3 files changed, 47 insertions(+), 8 deletions(-) diff --git a/backend/local/backend.go b/backend/local/backend.go index 595683258..02152c17c 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 7e5c872a1..0b829b611 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 37d59e480..7d78f67ef 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