getproviders: add a registry-specific error and modify output when a
provider is not found. Previously a user would see the following error even if terraform was only searching the local filesystem: "provider registry registry.terraform.io does not have a provider named ...." This PR adds a registry-specific error type and modifies the MultiSource installer to check for registry errors. It will return the registry-specific error message if there is one, but if not the error message will list all locations searched.
This commit is contained in:
parent
a33a613703
commit
8d28d73de3
|
@ -576,13 +576,12 @@ func (c *ZeroThirteenUpgradeCommand) detectProviderSources(requiredProviders map
|
|||
if err == nil {
|
||||
rp.Type = p
|
||||
} else {
|
||||
if _, ok := err.(getproviders.ErrProviderNotKnown); ok {
|
||||
// Setting the provider address to a zero value struct
|
||||
// indicates that there is no known FQN for this provider,
|
||||
// which will cause us to write an explanatory comment in the
|
||||
// HCL output advising the user what to do about this.
|
||||
rp.Type = addrs.Provider{}
|
||||
}
|
||||
// Setting the provider address to a zero value struct
|
||||
// indicates that there is no known FQN for this provider,
|
||||
// which will cause us to write an explanatory comment in the
|
||||
// HCL output advising the user what to do about this.
|
||||
rp.Type = addrs.Provider{}
|
||||
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Warning,
|
||||
"Could not detect provider source",
|
||||
|
|
|
@ -453,7 +453,7 @@ func (c *InitCommand) getProviders(earlyConfig *earlyconfig.Config, state *state
|
|||
|
||||
// The default (or configured) search paths are logged earlier, in provider_source.go
|
||||
// Log that those are being overridden by the `-plugin-dir` command line options
|
||||
log.Printf("[DEBUG] init: overriding provider plugin search paths")
|
||||
log.Println("[DEBUG] init: overriding provider plugin search paths")
|
||||
log.Printf("[DEBUG] will search for provider plugins in %s", pluginDirs)
|
||||
}
|
||||
|
||||
|
@ -495,11 +495,30 @@ func (c *InitCommand) getProviders(earlyConfig *earlyconfig.Config, state *state
|
|||
c.Ui.Info(fmt.Sprintf("- Installing %s v%s...", provider.ForDisplay(), version))
|
||||
},
|
||||
QueryPackagesFailure: func(provider addrs.Provider, err error) {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Failed to query available provider packages",
|
||||
fmt.Sprintf("Could not retrieve the list of available versions for provider %s: %s.", provider.ForDisplay(), err),
|
||||
))
|
||||
switch errorTy := err.(type) {
|
||||
case getproviders.ErrProviderNotFound:
|
||||
sources := errorTy.Sources
|
||||
displaySources := make([]string, len(sources))
|
||||
for i, source := range sources {
|
||||
displaySources[i] = fmt.Sprintf("- %s", source)
|
||||
}
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Failed to query available provider packages",
|
||||
fmt.Sprintf("Could not retrieve the list of available versions for provider %s: %s\n\n%s ",
|
||||
provider.ForDisplay(), err, strings.Join(displaySources, "\n"),
|
||||
),
|
||||
))
|
||||
default:
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Failed to query available provider packages",
|
||||
fmt.Sprintf("Could not retrieve the list of available versions for provider %s: %s ",
|
||||
provider.ForDisplay(), err,
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
},
|
||||
LinkFromCacheFailure: func(provider addrs.Provider, version getproviders.Version, err error) {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
|
|
|
@ -73,7 +73,22 @@ func (err ErrUnauthorized) Error() string {
|
|||
}
|
||||
}
|
||||
|
||||
// ErrProviderNotKnown is an error type used to indicate that the hostname
|
||||
// ErrProviderNotFound is an error type used to indicate that requested provider
|
||||
// was not found in the source(s) included in the Description field. This can be
|
||||
// used to produce user-friendly error messages.
|
||||
type ErrProviderNotFound struct {
|
||||
Provider addrs.Provider
|
||||
Sources []string
|
||||
}
|
||||
|
||||
func (err ErrProviderNotFound) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"provider %s was not found in any of the search locations",
|
||||
err.Provider,
|
||||
)
|
||||
}
|
||||
|
||||
// ErrRegistryProviderNotKnown is an error type used to indicate that the hostname
|
||||
// given in a provider address does appear to be a provider registry but that
|
||||
// registry does not know about the given provider namespace or type.
|
||||
//
|
||||
|
@ -85,11 +100,11 @@ func (err ErrUnauthorized) Error() string {
|
|||
// because we expect that the caller will have better context to decide what
|
||||
// hints are appropriate, e.g. by looking at the configuration given by the
|
||||
// user.
|
||||
type ErrProviderNotKnown struct {
|
||||
type ErrRegistryProviderNotKnown struct {
|
||||
Provider addrs.Provider
|
||||
}
|
||||
|
||||
func (err ErrProviderNotKnown) Error() string {
|
||||
func (err ErrRegistryProviderNotKnown) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"provider registry %s does not have a provider named %s",
|
||||
err.Provider.Hostname.ForDisplay(),
|
||||
|
@ -181,7 +196,7 @@ func (err ErrQueryFailed) Unwrap() error {
|
|||
// grow in future.
|
||||
func ErrIsNotExist(err error) bool {
|
||||
switch err.(type) {
|
||||
case ErrProviderNotKnown, ErrPlatformNotSupported:
|
||||
case ErrProviderNotFound, ErrPlatformNotSupported:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
|
|
@ -120,3 +120,8 @@ func (s *FilesystemMirrorSource) scanAllVersions() error {
|
|||
s.allPackages = ret
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *FilesystemMirrorSource) ForDisplay(provider addrs.Provider) string {
|
||||
// TODO: Since we have the provider, this could show the entire search path
|
||||
return s.baseDir
|
||||
}
|
||||
|
|
|
@ -35,3 +35,8 @@ func (s *HTTPMirrorSource) AvailableVersions(provider addrs.Provider) (VersionLi
|
|||
func (s *HTTPMirrorSource) PackageMeta(provider addrs.Provider, version Version, target Platform) (PackageMeta, error) {
|
||||
return PackageMeta{}, fmt.Errorf("Network-based provider mirrors are not supported in this version of Terraform")
|
||||
}
|
||||
|
||||
// ForDisplay returns a string description of the source for user-facing output.
|
||||
func (s *HTTPMirrorSource) ForDisplay(provider addrs.Provider) string {
|
||||
return "Network-based provider mirrors are not supported in this version of Terraform"
|
||||
}
|
||||
|
|
|
@ -94,3 +94,7 @@ func (s *MemoizeSource) PackageMeta(provider addrs.Provider, version Version, ta
|
|||
}
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (s *MemoizeSource) ForDisplay(provider addrs.Provider) string {
|
||||
return s.underlying.ForDisplay(provider)
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ func TestMemoizeSource(t *testing.T) {
|
|||
}
|
||||
|
||||
_, err = source.AvailableVersions(nonexistProvider)
|
||||
if want, ok := err.(ErrProviderNotKnown); !ok {
|
||||
if want, ok := err.(ErrProviderNotFound); !ok {
|
||||
t.Fatalf("wrong error type from nonexist call:\ngot: %T\nwant: %T", err, want)
|
||||
}
|
||||
|
||||
|
@ -122,11 +122,11 @@ func TestMemoizeSource(t *testing.T) {
|
|||
source := NewMemoizeSource(mock)
|
||||
|
||||
_, err := source.AvailableVersions(nonexistProvider)
|
||||
if want, ok := err.(ErrProviderNotKnown); !ok {
|
||||
if want, ok := err.(ErrProviderNotFound); !ok {
|
||||
t.Fatalf("wrong error type from first call:\ngot: %T\nwant: %T", err, want)
|
||||
}
|
||||
_, err = source.AvailableVersions(nonexistProvider)
|
||||
if want, ok := err.(ErrProviderNotKnown); !ok {
|
||||
if want, ok := err.(ErrProviderNotFound); !ok {
|
||||
t.Fatalf("wrong error type from second call:\ngot: %T\nwant: %T", err, want)
|
||||
}
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ func (s *MockSource) AvailableVersions(provider addrs.Provider) (VersionList, er
|
|||
if len(ret) == 0 {
|
||||
// In this case, we'll behave like a registry that doesn't know about
|
||||
// this provider at all, rather than just returning an empty result.
|
||||
return nil, ErrProviderNotKnown{provider}
|
||||
return nil, ErrProviderNotFound{provider, []string{"mock source"}}
|
||||
}
|
||||
ret.Sort()
|
||||
return ret, nil
|
||||
|
@ -198,3 +198,7 @@ func FakeInstallablePackageMeta(provider addrs.Provider, version Version, protoc
|
|||
}
|
||||
return meta, close, nil
|
||||
}
|
||||
|
||||
func (s *MockSource) ForDisplay(provider addrs.Provider) string {
|
||||
return "mock source"
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ func (s MultiSource) AvailableVersions(provider addrs.Provider) (VersionList, er
|
|||
// We will return the union of all versions reported by the nested
|
||||
// sources that have matching patterns that accept the given provider.
|
||||
vs := make(map[Version]struct{})
|
||||
var registryError bool
|
||||
for _, selector := range s {
|
||||
if !selector.CanHandleProvider(provider) {
|
||||
continue // doesn't match the given patterns
|
||||
|
@ -43,8 +44,11 @@ func (s MultiSource) AvailableVersions(provider addrs.Provider) (VersionList, er
|
|||
thisSourceVersions, err := selector.Source.AvailableVersions(provider)
|
||||
switch err.(type) {
|
||||
case nil:
|
||||
// okay
|
||||
case ErrProviderNotKnown:
|
||||
// okay
|
||||
case ErrRegistryProviderNotKnown:
|
||||
registryError = true
|
||||
continue // ignore, then
|
||||
case ErrProviderNotFound:
|
||||
continue // ignore, then
|
||||
default:
|
||||
return nil, err
|
||||
|
@ -55,7 +59,11 @@ func (s MultiSource) AvailableVersions(provider addrs.Provider) (VersionList, er
|
|||
}
|
||||
|
||||
if len(vs) == 0 {
|
||||
return nil, ErrProviderNotKnown{provider}
|
||||
if registryError {
|
||||
return nil, ErrRegistryProviderNotKnown{provider}
|
||||
} else {
|
||||
return nil, ErrProviderNotFound{provider, s.sourcesForProvider(provider)}
|
||||
}
|
||||
}
|
||||
ret := make(VersionList, 0, len(vs))
|
||||
for v := range vs {
|
||||
|
@ -70,7 +78,7 @@ func (s MultiSource) AvailableVersions(provider addrs.Provider) (VersionList, er
|
|||
// from the first selector that indicates availability of it.
|
||||
func (s MultiSource) PackageMeta(provider addrs.Provider, version Version, target Platform) (PackageMeta, error) {
|
||||
if len(s) == 0 { // Easy case: no providers exist at all
|
||||
return PackageMeta{}, ErrProviderNotKnown{provider}
|
||||
return PackageMeta{}, ErrProviderNotFound{provider, s.sourcesForProvider(provider)}
|
||||
}
|
||||
|
||||
for _, selector := range s {
|
||||
|
@ -81,7 +89,7 @@ func (s MultiSource) PackageMeta(provider addrs.Provider, version Version, targe
|
|||
switch err.(type) {
|
||||
case nil:
|
||||
return meta, nil
|
||||
case ErrProviderNotKnown, ErrPlatformNotSupported:
|
||||
case ErrProviderNotFound, ErrRegistryProviderNotKnown, ErrPlatformNotSupported:
|
||||
continue // ignore, then
|
||||
default:
|
||||
return PackageMeta{}, err
|
||||
|
@ -224,3 +232,20 @@ func normalizeProviderNameOrWildcard(s string) (string, error) {
|
|||
}
|
||||
return addrs.ParseProviderPart(s)
|
||||
}
|
||||
|
||||
func (s MultiSource) ForDisplay(provider addrs.Provider) string {
|
||||
return strings.Join(s.sourcesForProvider(provider), "\n")
|
||||
}
|
||||
|
||||
// sourcesForProvider returns a list of source display strings configured for a
|
||||
// given provider, taking into account any `Exclude` statements.
|
||||
func (s MultiSource) sourcesForProvider(provider addrs.Provider) []string {
|
||||
ret := make([]string, 0)
|
||||
for _, selector := range s {
|
||||
if !selector.CanHandleProvider(provider) {
|
||||
continue // doesn't match the given patterns
|
||||
}
|
||||
ret = append(ret, selector.Source.ForDisplay(provider))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ func TestMultiSourceAvailableVersions(t *testing.T) {
|
|||
}
|
||||
|
||||
_, err = multi.AvailableVersions(addrs.NewDefaultProvider("baz"))
|
||||
if want, ok := err.(ErrProviderNotKnown); !ok {
|
||||
if want, ok := err.(ErrProviderNotFound); !ok {
|
||||
t.Fatalf("wrong error type:\ngot: %T\nwant: %T", err, want)
|
||||
}
|
||||
})
|
||||
|
@ -147,10 +147,32 @@ func TestMultiSourceAvailableVersions(t *testing.T) {
|
|||
}
|
||||
|
||||
_, err = multi.AvailableVersions(addrs.NewDefaultProvider("baz"))
|
||||
if want, ok := err.(ErrProviderNotKnown); !ok {
|
||||
if want, ok := err.(ErrProviderNotFound); !ok {
|
||||
t.Fatalf("wrong error type:\ngot: %T\nwant: %T", err, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("provider not found", func(t *testing.T) {
|
||||
s1 := NewMockSource(nil)
|
||||
s2 := NewMockSource(nil)
|
||||
multi := MultiSource{
|
||||
{Source: s1},
|
||||
{Source: s2},
|
||||
}
|
||||
|
||||
_, err := multi.AvailableVersions(addrs.NewDefaultProvider("foo"))
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got success")
|
||||
}
|
||||
|
||||
wantErr := `provider registry.terraform.io/hashicorp/foo was not found in any of the search locations`
|
||||
_ = []string{"mock source", "mock source"}
|
||||
|
||||
if err.Error() != wantErr {
|
||||
t.Fatalf("wrong error.\ngot: %s\nwant: %s\n", err, wantErr)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestMultiSourcePackageMeta(t *testing.T) {
|
||||
|
|
|
@ -93,7 +93,7 @@ func newRegistryClient(baseURL *url.URL, creds svcauth.HostCredentials) *registr
|
|||
// ProviderVersions returns the raw version and protocol strings produced by the
|
||||
// registry for the given provider.
|
||||
//
|
||||
// The returned error will be ErrProviderNotKnown if the registry responds with
|
||||
// The returned error will be ErrRegistryProviderNotKnown if the registry responds with
|
||||
// 404 Not Found to indicate that the namespace or provider type are not known,
|
||||
// ErrUnauthorized if the registry responds with 401 or 403 status codes, or
|
||||
// ErrQueryFailed for any other protocol or operational problem.
|
||||
|
@ -122,7 +122,7 @@ func (c *registryClient) ProviderVersions(addr addrs.Provider) (map[string][]str
|
|||
case http.StatusOK:
|
||||
// Great!
|
||||
case http.StatusNotFound:
|
||||
return nil, ErrProviderNotKnown{
|
||||
return nil, ErrRegistryProviderNotKnown{
|
||||
Provider: addr,
|
||||
}
|
||||
case http.StatusUnauthorized, http.StatusForbidden:
|
||||
|
@ -357,7 +357,6 @@ func (c *registryClient) PackageMeta(provider addrs.Provider, version Version, t
|
|||
return ret, nil
|
||||
}
|
||||
|
||||
// LegacyProviderDefaultNamespace returns the raw address strings produced by
|
||||
// findClosestProtocolCompatibleVersion searches for the provider version with the closest protocol match.
|
||||
func (c *registryClient) findClosestProtocolCompatibleVersion(provider addrs.Provider, version Version) (Version, error) {
|
||||
var match Version
|
||||
|
@ -401,7 +400,7 @@ FindMatch:
|
|||
return match, nil
|
||||
}
|
||||
|
||||
// LegacyProviderCanonicalAddress returns the raw address strings produced by
|
||||
// LegacyProviderDefaultNamespace returns the raw address strings produced by
|
||||
// the registry when asked about the given unqualified provider type name.
|
||||
// The returned namespace string is taken verbatim from the registry's response.
|
||||
//
|
||||
|
@ -437,7 +436,7 @@ func (c *registryClient) LegacyProviderDefaultNamespace(typeName string) (string
|
|||
case http.StatusOK:
|
||||
// Great!
|
||||
case http.StatusNotFound:
|
||||
return "", ErrProviderNotKnown{
|
||||
return "", ErrProviderNotFound{
|
||||
Provider: placeholderProviderAddr,
|
||||
}
|
||||
case http.StatusUnauthorized, http.StatusForbidden:
|
||||
|
|
|
@ -167,3 +167,7 @@ func (s *RegistrySource) registryClient(hostname svchost.Hostname) (*registryCli
|
|||
|
||||
return newRegistryClient(url, creds), nil
|
||||
}
|
||||
|
||||
func (s *RegistrySource) ForDisplay(provider addrs.Provider) string {
|
||||
return fmt.Sprintf("registry %s", provider.Hostname.ForDisplay())
|
||||
}
|
||||
|
|
|
@ -9,4 +9,5 @@ import (
|
|||
type Source interface {
|
||||
AvailableVersions(provider addrs.Provider) (VersionList, error)
|
||||
PackageMeta(provider addrs.Provider, version Version, target Platform) (PackageMeta, error)
|
||||
ForDisplay(provider addrs.Provider) string
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue