github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-internal/getproviders/multi_source.go (about)

     1  package getproviders
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  
     8  	svchost "github.com/hashicorp/terraform-svchost"
     9  
    10  	"github.com/muratcelep/terraform/not-internal/addrs"
    11  )
    12  
    13  // MultiSource is a Source that wraps a series of other sources and combines
    14  // their sets of available providers and provider versions.
    15  //
    16  // A MultiSource consists of a sequence of selectors that each specify an
    17  // underlying source to query and a set of matching patterns to decide which
    18  // providers can be retrieved from which sources. If multiple selectors find
    19  // a given provider version then the earliest one in the sequence takes
    20  // priority for deciding the package metadata for the provider.
    21  //
    22  // For underlying sources that make network requests, consider wrapping each
    23  // one in a MemoizeSource so that availability information retrieved in
    24  // AvailableVersions can be reused in PackageMeta.
    25  type MultiSource []MultiSourceSelector
    26  
    27  var _ Source = MultiSource(nil)
    28  
    29  // AvailableVersions retrieves all of the versions of the given provider
    30  // that are available across all of the underlying selectors, while respecting
    31  // each selector's matching patterns.
    32  func (s MultiSource) AvailableVersions(ctx context.Context, provider addrs.Provider) (VersionList, Warnings, error) {
    33  	if len(s) == 0 { // Easy case: there can be no available versions
    34  		return nil, nil, nil
    35  	}
    36  
    37  	// We will return the union of all versions reported by the nested
    38  	// sources that have matching patterns that accept the given provider.
    39  	vs := make(map[Version]struct{})
    40  	var registryError bool
    41  	var warnings []string
    42  	for _, selector := range s {
    43  		if !selector.CanHandleProvider(provider) {
    44  			continue // doesn't match the given patterns
    45  		}
    46  		thisSourceVersions, warningsResp, err := selector.Source.AvailableVersions(ctx, provider)
    47  		switch err.(type) {
    48  		case nil:
    49  		// okay
    50  		case ErrRegistryProviderNotKnown:
    51  			registryError = true
    52  			continue // ignore, then
    53  		case ErrProviderNotFound:
    54  			continue // ignore, then
    55  		default:
    56  			return nil, nil, err
    57  		}
    58  		for _, v := range thisSourceVersions {
    59  			vs[v] = struct{}{}
    60  		}
    61  		if len(warningsResp) > 0 {
    62  			warnings = append(warnings, warningsResp...)
    63  		}
    64  	}
    65  
    66  	if len(vs) == 0 {
    67  		if registryError {
    68  			return nil, nil, ErrRegistryProviderNotKnown{provider}
    69  		} else {
    70  			return nil, nil, ErrProviderNotFound{provider, s.sourcesForProvider(provider)}
    71  		}
    72  	}
    73  	ret := make(VersionList, 0, len(vs))
    74  	for v := range vs {
    75  		ret = append(ret, v)
    76  	}
    77  	ret.Sort()
    78  
    79  	return ret, warnings, nil
    80  }
    81  
    82  // PackageMeta retrieves the package metadata for the requested provider package
    83  // from the first selector that indicates availability of it.
    84  func (s MultiSource) PackageMeta(ctx context.Context, provider addrs.Provider, version Version, target Platform) (PackageMeta, error) {
    85  	if len(s) == 0 { // Easy case: no providers exist at all
    86  		return PackageMeta{}, ErrProviderNotFound{provider, s.sourcesForProvider(provider)}
    87  	}
    88  
    89  	for _, selector := range s {
    90  		if !selector.CanHandleProvider(provider) {
    91  			continue // doesn't match the given patterns
    92  		}
    93  		meta, err := selector.Source.PackageMeta(ctx, provider, version, target)
    94  		switch err.(type) {
    95  		case nil:
    96  			return meta, nil
    97  		case ErrProviderNotFound, ErrRegistryProviderNotKnown, ErrPlatformNotSupported:
    98  			continue // ignore, then
    99  		default:
   100  			return PackageMeta{}, err
   101  		}
   102  	}
   103  
   104  	// If we fall out here then none of the sources have the requested
   105  	// package.
   106  	return PackageMeta{}, ErrPlatformNotSupported{
   107  		Provider: provider,
   108  		Version:  version,
   109  		Platform: target,
   110  	}
   111  }
   112  
   113  // MultiSourceSelector is an element of the source selection configuration on
   114  // MultiSource. A MultiSource has zero or more of these to configure which
   115  // underlying sources it should consult for a given provider.
   116  type MultiSourceSelector struct {
   117  	// Source is the underlying source that this selector applies to.
   118  	Source Source
   119  
   120  	// Include and Exclude are sets of provider matching patterns that
   121  	// together define which providers are eligible to be potentially
   122  	// installed from the corresponding Source.
   123  	Include, Exclude MultiSourceMatchingPatterns
   124  }
   125  
   126  // MultiSourceMatchingPatterns is a set of patterns that together define a
   127  // set of providers by matching on the segments of the provider FQNs.
   128  //
   129  // The Provider address values in a MultiSourceMatchingPatterns are special in
   130  // that any of Hostname, Namespace, or Type can be getproviders.Wildcard
   131  // to indicate that any concrete value is permitted for that segment.
   132  type MultiSourceMatchingPatterns []addrs.Provider
   133  
   134  // ParseMultiSourceMatchingPatterns parses a slice of strings containing the
   135  // string form of provider matching patterns and, if all the given strings are
   136  // valid, returns the corresponding, normalized, MultiSourceMatchingPatterns
   137  // value.
   138  func ParseMultiSourceMatchingPatterns(strs []string) (MultiSourceMatchingPatterns, error) {
   139  	if len(strs) == 0 {
   140  		return nil, nil
   141  	}
   142  
   143  	ret := make(MultiSourceMatchingPatterns, len(strs))
   144  	for i, str := range strs {
   145  		parts := strings.Split(str, "/")
   146  		if len(parts) < 2 || len(parts) > 3 {
   147  			return nil, fmt.Errorf("invalid provider matching pattern %q: must have either two or three slash-separated segments", str)
   148  		}
   149  		host := defaultRegistryHost
   150  		explicitHost := len(parts) == 3
   151  		if explicitHost {
   152  			givenHost := parts[0]
   153  			if givenHost == "*" {
   154  				host = svchost.Hostname(Wildcard)
   155  			} else {
   156  				normalHost, err := svchost.ForComparison(givenHost)
   157  				if err != nil {
   158  					return nil, fmt.Errorf("invalid hostname in provider matching pattern %q: %s", str, err)
   159  				}
   160  
   161  				// The remaining code below deals only with the namespace/type portions.
   162  				host = normalHost
   163  			}
   164  
   165  			parts = parts[1:]
   166  		}
   167  
   168  		pType, err := normalizeProviderNameOrWildcard(parts[1])
   169  		if err != nil {
   170  			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)
   171  		}
   172  		namespace, err := normalizeProviderNameOrWildcard(parts[0])
   173  		if err != nil {
   174  			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)
   175  		}
   176  
   177  		ret[i] = addrs.Provider{
   178  			Hostname:  host,
   179  			Namespace: namespace,
   180  			Type:      pType,
   181  		}
   182  
   183  		if ret[i].Hostname == svchost.Hostname(Wildcard) && !(ret[i].Namespace == Wildcard && ret[i].Type == Wildcard) {
   184  			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)
   185  		}
   186  		if ret[i].Namespace == Wildcard && ret[i].Type != Wildcard {
   187  			return nil, fmt.Errorf("invalid provider matching pattern %q: namespace can be a wildcard only if the provider type is also a wildcard", str)
   188  		}
   189  	}
   190  	return ret, nil
   191  }
   192  
   193  // CanHandleProvider returns true if and only if the given provider address
   194  // is both included by the selector's include patterns and _not_ excluded
   195  // by its exclude patterns.
   196  //
   197  // The absense of any include patterns is treated the same as a pattern
   198  // that matches all addresses. Exclusions take priority over inclusions.
   199  func (s MultiSourceSelector) CanHandleProvider(addr addrs.Provider) bool {
   200  	switch {
   201  	case s.Exclude.MatchesProvider(addr):
   202  		return false
   203  	case len(s.Include) > 0:
   204  		return s.Include.MatchesProvider(addr)
   205  	default:
   206  		return true
   207  	}
   208  }
   209  
   210  // MatchesProvider tests whether the receiving matching patterns match with
   211  // the given concrete provider address.
   212  func (ps MultiSourceMatchingPatterns) MatchesProvider(addr addrs.Provider) bool {
   213  	for _, pattern := range ps {
   214  		hostMatch := (pattern.Hostname == svchost.Hostname(Wildcard) || pattern.Hostname == addr.Hostname)
   215  		namespaceMatch := (pattern.Namespace == Wildcard || pattern.Namespace == addr.Namespace)
   216  		typeMatch := (pattern.Type == Wildcard || pattern.Type == addr.Type)
   217  		if hostMatch && namespaceMatch && typeMatch {
   218  			return true
   219  		}
   220  	}
   221  	return false
   222  }
   223  
   224  // Wildcard is a string value representing a wildcard element in the Include
   225  // and Exclude patterns used with MultiSource. It is not valid to use Wildcard
   226  // anywhere else.
   227  const Wildcard string = "*"
   228  
   229  // We'll read the default registry host from over in the addrs package, to
   230  // avoid duplicating it. A "default" provider uses the default registry host
   231  // by definition.
   232  var defaultRegistryHost = addrs.DefaultProviderRegistryHost
   233  
   234  func normalizeProviderNameOrWildcard(s string) (string, error) {
   235  	if s == Wildcard {
   236  		return s, nil
   237  	}
   238  	return addrs.ParseProviderPart(s)
   239  }
   240  
   241  func (s MultiSource) ForDisplay(provider addrs.Provider) string {
   242  	return strings.Join(s.sourcesForProvider(provider), "\n")
   243  }
   244  
   245  // sourcesForProvider returns a list of source display strings configured for a
   246  // given provider, taking into account any `Exclude` statements.
   247  func (s MultiSource) sourcesForProvider(provider addrs.Provider) []string {
   248  	ret := make([]string, 0)
   249  	for _, selector := range s {
   250  		if !selector.CanHandleProvider(provider) {
   251  			continue // doesn't match the given patterns
   252  		}
   253  		ret = append(ret, selector.Source.ForDisplay(provider))
   254  	}
   255  	return ret
   256  }