diff --git a/internal/getproviders/errors.go b/internal/getproviders/errors.go index 5710e5e05..784ffbfd3 100644 --- a/internal/getproviders/errors.go +++ b/internal/getproviders/errors.go @@ -145,3 +145,24 @@ func (err ErrQueryFailed) Error() string { func (err ErrQueryFailed) Unwrap() error { 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 + } +} diff --git a/internal/getproviders/multi_source.go b/internal/getproviders/multi_source.go index ef7712138..edd54786a 100644 --- a/internal/getproviders/multi_source.go +++ b/internal/getproviders/multi_source.go @@ -2,7 +2,6 @@ package getproviders import ( "fmt" - "regexp" "strings" svchost "github.com/hashicorp/terraform-svchost" @@ -34,19 +33,68 @@ func (s MultiSource) AvailableVersions(provider addrs.Provider) (VersionList, er return nil, nil } - // TODO: Implement - panic("MultiSource.AvailableVersions not yet implemented") + // 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{}) + 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 -// first selector that indicates support for it. +// PackageMeta retrieves the package metadata for the requested provider package +// 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} } - // TODO: Implement - panic("MultiSource.PackageMeta not yet implemented") + for _, selector := range s { + 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 @@ -103,10 +151,10 @@ func ParseMultiSourceMatchingPatterns(strs []string) (MultiSourceMatchingPattern 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) } - 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) } @@ -165,6 +213,12 @@ const Wildcard string = "*" // 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 // 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 +}