2020-01-09 19:34:43 -06:00
package getproviders
import (
2020-09-28 19:13:32 -05:00
"context"
2020-01-09 19:34:43 -06:00
"fmt"
"strings"
svchost "github.com/hashicorp/terraform-svchost"
2021-05-17 14:00:50 -05:00
"github.com/hashicorp/terraform/internal/addrs"
2020-01-09 19:34:43 -06:00
)
// MultiSource is a Source that wraps a series of other sources and combines
// their sets of available providers and provider versions.
//
// A MultiSource consists of a sequence of selectors that each specify an
// underlying source to query and a set of matching patterns to decide which
// providers can be retrieved from which sources. If multiple selectors find
// a given provider version then the earliest one in the sequence takes
// priority for deciding the package metadata for the provider.
//
// For underlying sources that make network requests, consider wrapping each
// one in a MemoizeSource so that availability information retrieved in
// AvailableVersions can be reused in PackageMeta.
type MultiSource [ ] MultiSourceSelector
var _ Source = MultiSource ( nil )
// AvailableVersions retrieves all of the versions of the given provider
// that are available across all of the underlying selectors, while respecting
// each selector's matching patterns.
2020-09-28 19:13:32 -05:00
func ( s MultiSource ) AvailableVersions ( ctx context . Context , provider addrs . Provider ) ( VersionList , Warnings , error ) {
2020-03-13 18:14:41 -05:00
if len ( s ) == 0 { // Easy case: there can be no available versions
2020-06-25 09:49:48 -05:00
return nil , nil , nil
2020-03-13 18:14:41 -05:00
}
2020-03-31 17:59:56 -05:00
// 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 { } )
2020-05-14 13:04:13 -05:00
var registryError bool
2020-06-25 09:49:48 -05:00
var warnings [ ] string
2020-03-31 17:59:56 -05:00
for _ , selector := range s {
if ! selector . CanHandleProvider ( provider ) {
continue // doesn't match the given patterns
}
2020-09-28 19:13:32 -05:00
thisSourceVersions , warningsResp , err := selector . Source . AvailableVersions ( ctx , provider )
2020-03-31 17:59:56 -05:00
switch err . ( type ) {
case nil :
2020-05-14 13:04:13 -05:00
// okay
case ErrRegistryProviderNotKnown :
registryError = true
continue // ignore, then
case ErrProviderNotFound :
2020-03-31 17:59:56 -05:00
continue // ignore, then
default :
2020-06-25 09:49:48 -05:00
return nil , nil , err
2020-03-31 17:59:56 -05:00
}
for _ , v := range thisSourceVersions {
vs [ v ] = struct { } { }
}
2020-06-25 09:49:48 -05:00
if len ( warningsResp ) > 0 {
warnings = append ( warnings , warningsResp ... )
}
2020-03-31 17:59:56 -05:00
}
if len ( vs ) == 0 {
2020-05-14 13:04:13 -05:00
if registryError {
2020-06-25 09:49:48 -05:00
return nil , nil , ErrRegistryProviderNotKnown { provider }
2020-05-14 13:04:13 -05:00
} else {
2020-06-25 09:49:48 -05:00
return nil , nil , ErrProviderNotFound { provider , s . sourcesForProvider ( provider ) }
2020-05-14 13:04:13 -05:00
}
2020-03-31 17:59:56 -05:00
}
ret := make ( VersionList , 0 , len ( vs ) )
for v := range vs {
ret = append ( ret , v )
}
ret . Sort ( )
2020-06-25 09:49:48 -05:00
return ret , warnings , nil
2020-01-09 19:34:43 -06:00
}
2020-03-31 17:59:56 -05:00
// PackageMeta retrieves the package metadata for the requested provider package
// from the first selector that indicates availability of it.
2020-09-28 19:13:32 -05:00
func ( s MultiSource ) PackageMeta ( ctx context . Context , provider addrs . Provider , version Version , target Platform ) ( PackageMeta , error ) {
2020-03-13 18:14:41 -05:00
if len ( s ) == 0 { // Easy case: no providers exist at all
2020-05-14 13:04:13 -05:00
return PackageMeta { } , ErrProviderNotFound { provider , s . sourcesForProvider ( provider ) }
2020-03-13 18:14:41 -05:00
}
2020-03-31 17:59:56 -05:00
for _ , selector := range s {
if ! selector . CanHandleProvider ( provider ) {
continue // doesn't match the given patterns
}
2020-09-28 19:13:32 -05:00
meta , err := selector . Source . PackageMeta ( ctx , provider , version , target )
2020-03-31 17:59:56 -05:00
switch err . ( type ) {
case nil :
return meta , nil
2020-05-14 13:04:13 -05:00
case ErrProviderNotFound , ErrRegistryProviderNotKnown , ErrPlatformNotSupported :
2020-03-31 17:59:56 -05:00
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 ,
}
2020-01-09 19:34:43 -06:00
}
// MultiSourceSelector is an element of the source selection configuration on
// MultiSource. A MultiSource has zero or more of these to configure which
// underlying sources it should consult for a given provider.
type MultiSourceSelector struct {
// Source is the underlying source that this selector applies to.
Source Source
// Include and Exclude are sets of provider matching patterns that
// together define which providers are eligible to be potentially
// installed from the corresponding Source.
Include , Exclude MultiSourceMatchingPatterns
}
// MultiSourceMatchingPatterns is a set of patterns that together define a
// set of providers by matching on the segments of the provider FQNs.
//
// The Provider address values in a MultiSourceMatchingPatterns are special in
// that any of Hostname, Namespace, or Type can be getproviders.Wildcard
// to indicate that any concrete value is permitted for that segment.
type MultiSourceMatchingPatterns [ ] addrs . Provider
// ParseMultiSourceMatchingPatterns parses a slice of strings containing the
2020-04-23 13:50:47 -05:00
// string form of provider matching patterns and, if all the given strings are
// valid, returns the corresponding, normalized, MultiSourceMatchingPatterns
// value.
2020-01-09 19:34:43 -06:00
func ParseMultiSourceMatchingPatterns ( strs [ ] string ) ( MultiSourceMatchingPatterns , error ) {
if len ( strs ) == 0 {
return nil , nil
}
ret := make ( MultiSourceMatchingPatterns , len ( strs ) )
for i , str := range strs {
parts := strings . Split ( str , "/" )
if len ( parts ) < 2 || len ( parts ) > 3 {
return nil , fmt . Errorf ( "invalid provider matching pattern %q: must have either two or three slash-separated segments" , str )
}
host := defaultRegistryHost
explicitHost := len ( parts ) == 3
if explicitHost {
givenHost := parts [ 0 ]
if givenHost == "*" {
host = svchost . Hostname ( Wildcard )
} else {
normalHost , err := svchost . ForComparison ( givenHost )
if err != nil {
return nil , fmt . Errorf ( "invalid hostname in provider matching pattern %q: %s" , str , err )
}
// The remaining code below deals only with the namespace/type portions.
host = normalHost
}
parts = parts [ 1 : ]
}
2020-04-23 13:50:47 -05:00
pType , err := normalizeProviderNameOrWildcard ( parts [ 1 ] )
if err != nil {
2020-01-09 19:34:43 -06:00
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 )
}
2020-04-23 13:50:47 -05:00
namespace , err := normalizeProviderNameOrWildcard ( parts [ 0 ] )
if err != nil {
2020-01-09 19:34:43 -06:00
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 )
}
ret [ i ] = addrs . Provider {
Hostname : host ,
2020-04-23 13:50:47 -05:00
Namespace : namespace ,
Type : pType ,
2020-01-09 19:34:43 -06:00
}
if ret [ i ] . Hostname == svchost . Hostname ( Wildcard ) && ! ( ret [ i ] . Namespace == Wildcard && ret [ i ] . Type == Wildcard ) {
return nil , fmt . Errorf ( "invalid provider matching pattern %q: hostname can be a wildcard only if both namespace and provider type are also wildcards" , str )
}
if ret [ i ] . Namespace == Wildcard && ret [ i ] . Type != Wildcard {
return nil , fmt . Errorf ( "invalid provider matching pattern %q: namespace can be a wildcard only if the provider type is also a wildcard" , str )
}
}
return ret , nil
}
2020-01-21 18:01:49 -06:00
// CanHandleProvider returns true if and only if the given provider address
// is both included by the selector's include patterns and _not_ excluded
// by its exclude patterns.
//
// The absense of any include patterns is treated the same as a pattern
// that matches all addresses. Exclusions take priority over inclusions.
func ( s MultiSourceSelector ) CanHandleProvider ( addr addrs . Provider ) bool {
switch {
case s . Exclude . MatchesProvider ( addr ) :
return false
case len ( s . Include ) > 0 :
return s . Include . MatchesProvider ( addr )
default :
return true
}
}
2020-01-09 19:34:43 -06:00
// MatchesProvider tests whether the receiving matching patterns match with
// the given concrete provider address.
func ( ps MultiSourceMatchingPatterns ) MatchesProvider ( addr addrs . Provider ) bool {
for _ , pattern := range ps {
hostMatch := ( pattern . Hostname == svchost . Hostname ( Wildcard ) || pattern . Hostname == addr . Hostname )
namespaceMatch := ( pattern . Namespace == Wildcard || pattern . Namespace == addr . Namespace )
typeMatch := ( pattern . Type == Wildcard || pattern . Type == addr . Type )
if hostMatch && namespaceMatch && typeMatch {
return true
}
}
return false
}
// Wildcard is a string value representing a wildcard element in the Include
// and Exclude patterns used with MultiSource. It is not valid to use Wildcard
// anywhere else.
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.
2021-05-27 13:09:28 -05:00
var defaultRegistryHost = addrs . DefaultProviderRegistryHost
2020-01-09 19:34:43 -06:00
2020-04-23 13:50:47 -05:00
func normalizeProviderNameOrWildcard ( s string ) ( string , error ) {
2020-03-31 17:59:56 -05:00
if s == Wildcard {
2020-04-23 13:50:47 -05:00
return s , nil
2020-03-31 17:59:56 -05:00
}
2020-04-23 13:50:47 -05:00
return addrs . ParseProviderPart ( s )
2020-03-31 17:59:56 -05:00
}
2020-05-14 13:04:13 -05:00
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
}