github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/internal/getproviders/multi_source.go (about)

     1  package getproviders
     2  
     3  import (
     4  	"fmt"
     5  	"regexp"
     6  	"strings"
     7  
     8  	svchost "github.com/hashicorp/terraform-svchost"
     9  
    10  	"github.com/hashicorp/terraform/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(provider addrs.Provider) (VersionList, error) {
    33  	// TODO: Implement
    34  	panic("MultiSource.AvailableVersions not yet implemented")
    35  }
    36  
    37  // PackageMeta retrieves the package metadata for the given provider from the
    38  // first selector that indicates support for it.
    39  func (s MultiSource) PackageMeta(provider addrs.Provider, version Version, target Platform) (PackageMeta, error) {
    40  	// TODO: Implement
    41  	panic("MultiSource.PackageMeta not yet implemented")
    42  }
    43  
    44  // MultiSourceSelector is an element of the source selection configuration on
    45  // MultiSource. A MultiSource has zero or more of these to configure which
    46  // underlying sources it should consult for a given provider.
    47  type MultiSourceSelector struct {
    48  	// Source is the underlying source that this selector applies to.
    49  	Source Source
    50  
    51  	// Include and Exclude are sets of provider matching patterns that
    52  	// together define which providers are eligible to be potentially
    53  	// installed from the corresponding Source.
    54  	Include, Exclude MultiSourceMatchingPatterns
    55  }
    56  
    57  // MultiSourceMatchingPatterns is a set of patterns that together define a
    58  // set of providers by matching on the segments of the provider FQNs.
    59  //
    60  // The Provider address values in a MultiSourceMatchingPatterns are special in
    61  // that any of Hostname, Namespace, or Type can be getproviders.Wildcard
    62  // to indicate that any concrete value is permitted for that segment.
    63  type MultiSourceMatchingPatterns []addrs.Provider
    64  
    65  // ParseMultiSourceMatchingPatterns parses a slice of strings containing the
    66  // string form of provider matching patterns and, if all the given strings
    67  // are valid, returns the corresponding MultiSourceMatchingPatterns value.
    68  func ParseMultiSourceMatchingPatterns(strs []string) (MultiSourceMatchingPatterns, error) {
    69  	if len(strs) == 0 {
    70  		return nil, nil
    71  	}
    72  
    73  	ret := make(MultiSourceMatchingPatterns, len(strs))
    74  	for i, str := range strs {
    75  		parts := strings.Split(str, "/")
    76  		if len(parts) < 2 || len(parts) > 3 {
    77  			return nil, fmt.Errorf("invalid provider matching pattern %q: must have either two or three slash-separated segments", str)
    78  		}
    79  		host := defaultRegistryHost
    80  		explicitHost := len(parts) == 3
    81  		if explicitHost {
    82  			givenHost := parts[0]
    83  			if givenHost == "*" {
    84  				host = svchost.Hostname(Wildcard)
    85  			} else {
    86  				normalHost, err := svchost.ForComparison(givenHost)
    87  				if err != nil {
    88  					return nil, fmt.Errorf("invalid hostname in provider matching pattern %q: %s", str, err)
    89  				}
    90  
    91  				// The remaining code below deals only with the namespace/type portions.
    92  				host = normalHost
    93  			}
    94  
    95  			parts = parts[1:]
    96  		}
    97  
    98  		if !validProviderNamePattern.MatchString(parts[1]) {
    99  			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)
   100  		}
   101  		if !validProviderNamePattern.MatchString(parts[0]) {
   102  			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)
   103  		}
   104  
   105  		ret[i] = addrs.Provider{
   106  			Hostname:  host,
   107  			Namespace: parts[0],
   108  			Type:      parts[1],
   109  		}
   110  
   111  		if ret[i].Hostname == svchost.Hostname(Wildcard) && !(ret[i].Namespace == Wildcard && ret[i].Type == Wildcard) {
   112  			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)
   113  		}
   114  		if ret[i].Namespace == Wildcard && ret[i].Type != Wildcard {
   115  			return nil, fmt.Errorf("invalid provider matching pattern %q: namespace can be a wildcard only if the provider type is also a wildcard", str)
   116  		}
   117  	}
   118  	return ret, nil
   119  }
   120  
   121  // CanHandleProvider returns true if and only if the given provider address
   122  // is both included by the selector's include patterns and _not_ excluded
   123  // by its exclude patterns.
   124  //
   125  // The absense of any include patterns is treated the same as a pattern
   126  // that matches all addresses. Exclusions take priority over inclusions.
   127  func (s MultiSourceSelector) CanHandleProvider(addr addrs.Provider) bool {
   128  	switch {
   129  	case s.Exclude.MatchesProvider(addr):
   130  		return false
   131  	case len(s.Include) > 0:
   132  		return s.Include.MatchesProvider(addr)
   133  	default:
   134  		return true
   135  	}
   136  }
   137  
   138  // MatchesProvider tests whether the receiving matching patterns match with
   139  // the given concrete provider address.
   140  func (ps MultiSourceMatchingPatterns) MatchesProvider(addr addrs.Provider) bool {
   141  	for _, pattern := range ps {
   142  		hostMatch := (pattern.Hostname == svchost.Hostname(Wildcard) || pattern.Hostname == addr.Hostname)
   143  		namespaceMatch := (pattern.Namespace == Wildcard || pattern.Namespace == addr.Namespace)
   144  		typeMatch := (pattern.Type == Wildcard || pattern.Type == addr.Type)
   145  		if hostMatch && namespaceMatch && typeMatch {
   146  			return true
   147  		}
   148  	}
   149  	return false
   150  }
   151  
   152  // Wildcard is a string value representing a wildcard element in the Include
   153  // and Exclude patterns used with MultiSource. It is not valid to use Wildcard
   154  // anywhere else.
   155  const Wildcard string = "*"
   156  
   157  // We'll read the default registry host from over in the addrs package, to
   158  // avoid duplicating it. A "default" provider uses the default registry host
   159  // by definition.
   160  var defaultRegistryHost = addrs.NewDefaultProvider("placeholder").Hostname
   161  
   162  var validProviderNamePattern = regexp.MustCompile("^[a-zA-Z0-9_-]+|\\*$")