internal/getproviders: Implement MultiSource

We previously had only a stub implementation for a totally-empty
MultiSource. Here we have an initial implementation of the full
functionality, which we'll need to support "terraform init -plugin-dir=..."
in a subsequent commit.
This commit is contained in:
Martin Atkins 2020-03-31 15:59:56 -07:00
parent 2ff4582be2
commit f7072a8f29
2 changed files with 86 additions and 11 deletions

View File

@ -145,3 +145,24 @@ func (err ErrQueryFailed) Error() string {
func (err ErrQueryFailed) Unwrap() error { func (err ErrQueryFailed) Unwrap() error {
return err.Wrapped return err.Wrapped
} }
// ErrIsNotExist returns true if and only if the given error is one of the
// errors from this package that represents an affirmative response that a
// requested object does not exist.
//
// This is as opposed to errors indicating that the source is unavailable
// or misconfigured in some way, where we therefore cannot say for certain
// whether the requested object exists.
//
// If a caller needs to take a special action based on something not existing,
// such as falling back on some other source, use this function rather than
// direct type assertions so that the set of possible "not exist" errors can
// grow in future.
func ErrIsNotExist(err error) bool {
switch err.(type) {
case ErrProviderNotKnown, ErrPlatformNotSupported:
return true
default:
return false
}
}

View File

@ -2,7 +2,6 @@ package getproviders
import ( import (
"fmt" "fmt"
"regexp"
"strings" "strings"
svchost "github.com/hashicorp/terraform-svchost" svchost "github.com/hashicorp/terraform-svchost"
@ -34,19 +33,68 @@ func (s MultiSource) AvailableVersions(provider addrs.Provider) (VersionList, er
return nil, nil return nil, nil
} }
// TODO: Implement // We will return the union of all versions reported by the nested
panic("MultiSource.AvailableVersions not yet implemented") // sources that have matching patterns that accept the given provider.
vs := make(map[Version]struct{})
for _, selector := range s {
if !selector.CanHandleProvider(provider) {
continue // doesn't match the given patterns
}
thisSourceVersions, err := selector.Source.AvailableVersions(provider)
switch err.(type) {
case nil:
// okay
case ErrProviderNotKnown:
continue // ignore, then
default:
return nil, err
}
for _, v := range thisSourceVersions {
vs[v] = struct{}{}
}
}
if len(vs) == 0 {
return nil, ErrProviderNotKnown{provider}
}
ret := make(VersionList, 0, len(vs))
for v := range vs {
ret = append(ret, v)
}
ret.Sort()
return ret, nil
} }
// PackageMeta retrieves the package metadata for the given provider from the // PackageMeta retrieves the package metadata for the requested provider package
// first selector that indicates support for it. // from the first selector that indicates availability of it.
func (s MultiSource) PackageMeta(provider addrs.Provider, version Version, target Platform) (PackageMeta, error) { func (s MultiSource) PackageMeta(provider addrs.Provider, version Version, target Platform) (PackageMeta, error) {
if len(s) == 0 { // Easy case: no providers exist at all if len(s) == 0 { // Easy case: no providers exist at all
return PackageMeta{}, ErrProviderNotKnown{provider} return PackageMeta{}, ErrProviderNotKnown{provider}
} }
// TODO: Implement for _, selector := range s {
panic("MultiSource.PackageMeta not yet implemented") if !selector.CanHandleProvider(provider) {
continue // doesn't match the given patterns
}
meta, err := selector.Source.PackageMeta(provider, version, target)
switch err.(type) {
case nil:
return meta, nil
case ErrProviderNotKnown, ErrPlatformNotSupported:
continue // ignore, then
default:
return PackageMeta{}, err
}
}
// If we fall out here then none of the sources have the requested
// package.
return PackageMeta{}, ErrPlatformNotSupported{
Provider: provider,
Version: version,
Platform: target,
}
} }
// MultiSourceSelector is an element of the source selection configuration on // MultiSourceSelector is an element of the source selection configuration on
@ -103,10 +151,10 @@ func ParseMultiSourceMatchingPatterns(strs []string) (MultiSourceMatchingPattern
parts = parts[1:] parts = parts[1:]
} }
if !validProviderNamePattern.MatchString(parts[1]) { if !validProviderNameOrWildcard(parts[1]) {
return nil, fmt.Errorf("invalid provider type %q in provider matching pattern %q: must either be the wildcard * or a provider type name", parts[1], str) return nil, fmt.Errorf("invalid provider type %q in provider matching pattern %q: must either be the wildcard * or a provider type name", parts[1], str)
} }
if !validProviderNamePattern.MatchString(parts[0]) { if !validProviderNameOrWildcard(parts[0]) {
return nil, fmt.Errorf("invalid registry namespace %q in provider matching pattern %q: must either be the wildcard * or a literal namespace", parts[1], str) return nil, fmt.Errorf("invalid registry namespace %q in provider matching pattern %q: must either be the wildcard * or a literal namespace", parts[1], str)
} }
@ -165,6 +213,12 @@ const Wildcard string = "*"
// We'll read the default registry host from over in the addrs package, to // We'll read the default registry host from over in the addrs package, to
// avoid duplicating it. A "default" provider uses the default registry host // avoid duplicating it. A "default" provider uses the default registry host
// by definition. // by definition.
var defaultRegistryHost = addrs.NewDefaultProvider("placeholder").Hostname var defaultRegistryHost = addrs.DefaultRegistryHost
var validProviderNamePattern = regexp.MustCompile("^[a-zA-Z0-9_-]+|\\*$") func validProviderNameOrWildcard(s string) bool {
if s == Wildcard {
return true
}
_, err := addrs.ParseProviderPart(s)
return err == nil
}