Skip to content

Commit 77cbd3b

Browse files
authored
Merge pull request #15371 from hashicorp/jbardin/reinit-error
better UI output for requesting plugin related init
2 parents 2f4e9a6 + b14677b commit 77cbd3b

File tree

5 files changed

+74
-17
lines changed

5 files changed

+74
-17
lines changed

backend/local/backend.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"os"
99
"path/filepath"
1010
"sort"
11+
"strings"
1112
"sync"
1213

1314
"github.com/hashicorp/terraform/backend"
@@ -407,3 +408,25 @@ func (b *Local) stateWorkspaceDir() string {
407408

408409
return DefaultWorkspaceDir
409410
}
411+
412+
func (b *Local) pluginInitRequired(providerErr *terraform.ResourceProviderError) {
413+
b.CLI.Output(b.Colorize().Color(fmt.Sprintf(
414+
strings.TrimSpace(errPluginInit)+"\n",
415+
providerErr)))
416+
}
417+
418+
// this relies on multierror to format the plugin errors below the copy
419+
const errPluginInit = `
420+
[reset][bold][yellow]Plugin reinitialization required. Please run "terraform init".[reset]
421+
[yellow]Reason: Could not satisfy plugin requirements.
422+
423+
Plugins are external binaries that Terraform uses to access and manipulate
424+
resources. The configuration provided requires plugins which can't be located,
425+
don't satisfy the version constraints, or are otherwise incompatible.
426+
427+
[reset][red]%s
428+
429+
[reset][yellow]Terraform automatically discovers provider requirements from your
430+
configuration, including providers used in child modules. To see the
431+
requirements and constraints from each module, run "terraform providers".
432+
`

backend/local/backend_local.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package local
22

33
import (
4+
"errors"
45
"fmt"
56
"log"
67
"strings"
@@ -57,6 +58,15 @@ func (b *Local) context(op *backend.Operation) (*terraform.Context, state.State,
5758
} else {
5859
tfCtx, err = terraform.NewContext(&opts)
5960
}
61+
62+
// any errors resolving plugins returns this
63+
if rpe, ok := err.(*terraform.ResourceProviderError); ok {
64+
b.pluginInitRequired(rpe)
65+
// we wrote the full UI error here, so return a generic error for flow
66+
// control in the command.
67+
return nil, nil, errors.New("error satisfying plugin requirements")
68+
}
69+
6070
if err != nil {
6171
return nil, nil, err
6272
}

command/plugins.go

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package command
22

33
import (
44
"encoding/json"
5+
"errors"
56
"fmt"
67
"io/ioutil"
78
"log"
@@ -45,27 +46,42 @@ func (r *multiVersionProviderResolver) ResolveProviders(
4546
var errs []error
4647

4748
chosen := choosePlugins(r.Available, reqd)
48-
for name := range reqd {
49+
for name, req := range reqd {
4950
if newest, available := chosen[name]; available {
5051
digest, err := newest.SHA256()
5152
if err != nil {
5253
errs = append(errs, fmt.Errorf("provider.%s: failed to load plugin to verify its signature: %s", name, err))
5354
continue
5455
}
5556
if !reqd[name].AcceptsSHA256(digest) {
56-
// This generic error message is intended to avoid troubling
57-
// users with implementation details. The main useful point
58-
// here is that they need to run "terraform init" to
59-
// fix this, which is covered by the UI code reporting these
60-
// error messages.
61-
errs = append(errs, fmt.Errorf("provider.%s: installed but not yet initialized", name))
57+
errs = append(errs, fmt.Errorf("provider.%s: new or changed plugin executable", name))
6258
continue
6359
}
6460

6561
client := tfplugin.Client(newest)
6662
factories[name] = providerFactory(client)
6763
} else {
68-
errs = append(errs, fmt.Errorf("provider.%s: no suitable version installed", name))
64+
msg := fmt.Sprintf("provider.%s: no suitable version installed", name)
65+
66+
required := req.Versions.String()
67+
// no version is unconstrained
68+
if required == "" {
69+
required = "(any version)"
70+
}
71+
72+
foundVersions := []string{}
73+
for meta := range r.Available.WithName(name) {
74+
foundVersions = append(foundVersions, fmt.Sprintf("%q", meta.Version))
75+
}
76+
77+
found := "none"
78+
if len(foundVersions) > 0 {
79+
found = strings.Join(foundVersions, ", ")
80+
}
81+
82+
msg += fmt.Sprintf("\n version requirements: %q\n versions installed: %s", required, found)
83+
84+
errs = append(errs, errors.New(msg))
6985
}
7086
}
7187

terraform/context_refresh_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package terraform
22

33
import (
44
"reflect"
5+
"regexp"
56
"sort"
67
"strings"
78
"sync"
@@ -861,7 +862,7 @@ func TestContext2Refresh_unknownProvider(t *testing.T) {
861862
t.Fatal("successfully created context; want error")
862863
}
863864

864-
if !strings.Contains(err.Error(), "Can't satisfy provider requirements") {
865+
if !regexp.MustCompile(`provider ".+" is not available`).MatchString(err.Error()) {
865866
t.Fatalf("wrong error: %s", err)
866867
}
867868
}

terraform/resource_provider.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
package terraform
22

33
import (
4-
"bytes"
5-
"errors"
64
"fmt"
75

6+
multierror "github.com/hashicorp/go-multierror"
87
"github.com/hashicorp/terraform/plugin/discovery"
98
)
109

@@ -162,6 +161,18 @@ type ResourceProvider interface {
162161
ReadDataApply(*InstanceInfo, *InstanceDiff) (*InstanceState, error)
163162
}
164163

164+
// ResourceProviderError may be returned when creating a Context if the
165+
// required providers cannot be satisfied. This error can then be used to
166+
// format a more useful message for the user.
167+
type ResourceProviderError struct {
168+
Errors []error
169+
}
170+
171+
func (e *ResourceProviderError) Error() string {
172+
// use multierror to format the default output
173+
return multierror.Append(nil, e.Errors...).Error()
174+
}
175+
165176
// ResourceProviderCloser is an interface that providers that can close
166177
// connections that aren't needed anymore must implement.
167178
type ResourceProviderCloser interface {
@@ -265,13 +276,9 @@ func ProviderHasDataSource(p ResourceProvider, n string) bool {
265276
func resourceProviderFactories(resolver ResourceProviderResolver, reqd discovery.PluginRequirements) (map[string]ResourceProviderFactory, error) {
266277
ret, errs := resolver.ResolveProviders(reqd)
267278
if errs != nil {
268-
errBuf := &bytes.Buffer{}
269-
errBuf.WriteString("Can't satisfy provider requirements with currently-installed plugins:\n\n")
270-
for _, err := range errs {
271-
fmt.Fprintf(errBuf, "* %s\n", err)
279+
return nil, &ResourceProviderError{
280+
Errors: errs,
272281
}
273-
errBuf.WriteString("\nRun 'terraform init' to install the necessary provider plugins.\n")
274-
return nil, errors.New(errBuf.String())
275282
}
276283

277284
return ret, nil

0 commit comments

Comments
 (0)