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 }