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_-]+|\\*$")