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.
This commit is contained in:
parent
83622cf19b
commit
5be15ed77c
|
@ -8,6 +8,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
|
@ -407,3 +408,24 @@ func (b *Local) stateWorkspaceDir() string {
|
||||||
|
|
||||||
return DefaultWorkspaceDir
|
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
|
||||||
|
`
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package local
|
package local
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -57,6 +58,15 @@ func (b *Local) context(op *backend.Operation) (*terraform.Context, state.State,
|
||||||
} else {
|
} else {
|
||||||
tfCtx, err = terraform.NewContext(&opts)
|
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 {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
package terraform
|
package terraform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
multierror "github.com/hashicorp/go-multierror"
|
||||||
"github.com/hashicorp/terraform/plugin/discovery"
|
"github.com/hashicorp/terraform/plugin/discovery"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -162,6 +161,18 @@ type ResourceProvider interface {
|
||||||
ReadDataApply(*InstanceInfo, *InstanceDiff) (*InstanceState, error)
|
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
|
// ResourceProviderCloser is an interface that providers that can close
|
||||||
// connections that aren't needed anymore must implement.
|
// connections that aren't needed anymore must implement.
|
||||||
type ResourceProviderCloser interface {
|
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) {
|
func resourceProviderFactories(resolver ResourceProviderResolver, reqd discovery.PluginRequirements) (map[string]ResourceProviderFactory, error) {
|
||||||
ret, errs := resolver.ResolveProviders(reqd)
|
ret, errs := resolver.ResolveProviders(reqd)
|
||||||
if errs != nil {
|
if errs != nil {
|
||||||
errBuf := &bytes.Buffer{}
|
return nil, &ResourceProviderError{
|
||||||
errBuf.WriteString("Can't satisfy provider requirements with currently-installed plugins:\n\n")
|
Errors: errs,
|
||||||
for _, err := range errs {
|
|
||||||
fmt.Fprintf(errBuf, "* %s\n", err)
|
|
||||||
}
|
}
|
||||||
errBuf.WriteString("\nRun 'terraform init' to install the necessary provider plugins.\n")
|
|
||||||
return nil, errors.New(errBuf.String())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret, nil
|
return ret, nil
|
||||||
|
|
Loading…
Reference in New Issue