1package getproviders 2 3import ( 4 "fmt" 5 "runtime" 6 "sort" 7 "strings" 8 9 "github.com/apparentlymart/go-versions/versions" 10 "github.com/apparentlymart/go-versions/versions/constraints" 11 12 "github.com/hashicorp/terraform/internal/addrs" 13) 14 15// Version represents a particular single version of a provider. 16type Version = versions.Version 17 18// UnspecifiedVersion is the zero value of Version, representing the absense 19// of a version number. 20var UnspecifiedVersion Version = versions.Unspecified 21 22// VersionList represents a list of versions. It is a []Version with some 23// extra methods for convenient filtering. 24type VersionList = versions.List 25 26// VersionSet represents a set of versions, usually describing the acceptable 27// versions that can be selected under a particular version constraint provided 28// by the end-user. 29type VersionSet = versions.Set 30 31// VersionConstraints represents a set of version constraints, which can 32// define the membership of a VersionSet by exclusion. 33type VersionConstraints = constraints.IntersectionSpec 34 35// Warnings represents a list of warnings returned by a Registry source. 36type Warnings = []string 37 38// Requirements gathers together requirements for many different providers 39// into a single data structure, as a convenient way to represent the full 40// set of requirements for a particular configuration or state or both. 41// 42// If an entry in a Requirements has a zero-length VersionConstraints then 43// that indicates that the provider is required but that any version is 44// acceptable. That's different than a provider being absent from the map 45// altogether, which means that it is not required at all. 46type Requirements map[addrs.Provider]VersionConstraints 47 48// Merge takes the requirements in the receiever and the requirements in the 49// other given value and produces a new set of requirements that combines 50// all of the requirements of both. 51// 52// The resulting requirements will permit only selections that both of the 53// source requirements would've allowed. 54func (r Requirements) Merge(other Requirements) Requirements { 55 ret := make(Requirements) 56 for addr, constraints := range r { 57 ret[addr] = constraints 58 } 59 for addr, constraints := range other { 60 ret[addr] = append(ret[addr], constraints...) 61 } 62 return ret 63} 64 65// Selections gathers together version selections for many different providers. 66// 67// This is the result of provider installation: a specific version selected 68// for each provider given in the requested Requirements, selected based on 69// the given version constraints. 70type Selections map[addrs.Provider]Version 71 72// ParseVersion parses a "semver"-style version string into a Version value, 73// which is the version syntax we use for provider versions. 74func ParseVersion(str string) (Version, error) { 75 return versions.ParseVersion(str) 76} 77 78// MustParseVersion is a variant of ParseVersion that panics if it encounters 79// an error while parsing. 80func MustParseVersion(str string) Version { 81 ret, err := ParseVersion(str) 82 if err != nil { 83 panic(err) 84 } 85 return ret 86} 87 88// ParseVersionConstraints parses a "Ruby-like" version constraint string 89// into a VersionConstraints value. 90func ParseVersionConstraints(str string) (VersionConstraints, error) { 91 return constraints.ParseRubyStyleMulti(str) 92} 93 94// MustParseVersionConstraints is a variant of ParseVersionConstraints that 95// panics if it encounters an error while parsing. 96func MustParseVersionConstraints(str string) VersionConstraints { 97 ret, err := ParseVersionConstraints(str) 98 if err != nil { 99 panic(err) 100 } 101 return ret 102} 103 104// MeetingConstraints returns a version set that contains all of the versions 105// that meet the given constraints, specified using the Spec type from the 106// constraints package. 107func MeetingConstraints(vc VersionConstraints) VersionSet { 108 return versions.MeetingConstraints(vc) 109} 110 111// Platform represents a target platform that a provider is or might be 112// available for. 113type Platform struct { 114 OS, Arch string 115} 116 117func (p Platform) String() string { 118 return p.OS + "_" + p.Arch 119} 120 121// LessThan returns true if the receiver should sort before the other given 122// Platform in an ordered list of platforms. 123// 124// The ordering is lexical first by OS and then by Architecture. 125// This ordering is primarily just to ensure that results of 126// functions in this package will be deterministic. The ordering is not 127// intended to have any semantic meaning and is subject to change in future. 128func (p Platform) LessThan(other Platform) bool { 129 switch { 130 case p.OS != other.OS: 131 return p.OS < other.OS 132 default: 133 return p.Arch < other.Arch 134 } 135} 136 137// ParsePlatform parses a string representation of a platform, like 138// "linux_amd64", or returns an error if the string is not valid. 139func ParsePlatform(str string) (Platform, error) { 140 underPos := strings.Index(str, "_") 141 if underPos < 1 || underPos >= len(str)-2 { 142 return Platform{}, fmt.Errorf("must be two words separated by an underscore") 143 } 144 145 os, arch := str[:underPos], str[underPos+1:] 146 if strings.ContainsAny(os, " \t\n\r") { 147 return Platform{}, fmt.Errorf("OS portion must not contain whitespace") 148 } 149 if strings.ContainsAny(arch, " \t\n\r") { 150 return Platform{}, fmt.Errorf("architecture portion must not contain whitespace") 151 } 152 153 return Platform{ 154 OS: os, 155 Arch: arch, 156 }, nil 157} 158 159// CurrentPlatform is the platform where the current program is running. 160// 161// If attempting to install providers for use on the same system where the 162// installation process is running, this is the right platform to use. 163var CurrentPlatform = Platform{ 164 OS: runtime.GOOS, 165 Arch: runtime.GOARCH, 166} 167 168// PackageMeta represents the metadata related to a particular downloadable 169// provider package targeting a single platform. 170// 171// Package findproviders does no signature verification or protocol version 172// compatibility checking of its own. A caller receving a PackageMeta must 173// verify that it has a correct signature and supports a protocol version 174// accepted by the current version of Terraform before trying to use the 175// described package. 176type PackageMeta struct { 177 Provider addrs.Provider 178 Version Version 179 180 ProtocolVersions VersionList 181 TargetPlatform Platform 182 183 Filename string 184 Location PackageLocation 185 186 // Authentication, if non-nil, is a request from the source that produced 187 // this meta for verification of the target package after it has been 188 // retrieved from the indicated Location. 189 // 190 // Different sources will support different authentication strategies -- 191 // or possibly no strategies at all -- depending on what metadata they 192 // have available to them, such as checksums provided out-of-band by the 193 // original package author, expected signing keys, etc. 194 // 195 // If Authentication is non-nil then no authentication is requested. 196 // This is likely appropriate only for packages that are already available 197 // on the local system. 198 Authentication PackageAuthentication 199} 200 201// LessThan returns true if the receiver should sort before the given other 202// PackageMeta in a sorted list of PackageMeta. 203// 204// Sorting preference is given first to the provider address, then to the 205// taget platform, and the to the version number (using semver precedence). 206// Packages that differ only in semver build metadata have no defined 207// precedence and so will always return false. 208// 209// This ordering is primarily just to maximize the chance that results of 210// functions in this package will be deterministic. The ordering is not 211// intended to have any semantic meaning and is subject to change in future. 212func (m PackageMeta) LessThan(other PackageMeta) bool { 213 switch { 214 case m.Provider != other.Provider: 215 return m.Provider.LessThan(other.Provider) 216 case m.TargetPlatform != other.TargetPlatform: 217 return m.TargetPlatform.LessThan(other.TargetPlatform) 218 case m.Version != other.Version: 219 return m.Version.LessThan(other.Version) 220 default: 221 return false 222 } 223} 224 225// UnpackedDirectoryPath determines the path under the given base 226// directory where SearchLocalDirectory or the FilesystemMirrorSource would 227// expect to find an unpacked copy of the receiving PackageMeta. 228// 229// The result always uses forward slashes as path separator, even on Windows, 230// to produce a consistent result on all platforms. Windows accepts both 231// direction of slash as long as each individual path string is self-consistent. 232func (m PackageMeta) UnpackedDirectoryPath(baseDir string) string { 233 return UnpackedDirectoryPathForPackage(baseDir, m.Provider, m.Version, m.TargetPlatform) 234} 235 236// PackedFilePath determines the path under the given base 237// directory where SearchLocalDirectory or the FilesystemMirrorSource would 238// expect to find packed copy (a .zip archive) of the receiving PackageMeta. 239// 240// The result always uses forward slashes as path separator, even on Windows, 241// to produce a consistent result on all platforms. Windows accepts both 242// direction of slash as long as each individual path string is self-consistent. 243func (m PackageMeta) PackedFilePath(baseDir string) string { 244 return PackedFilePathForPackage(baseDir, m.Provider, m.Version, m.TargetPlatform) 245} 246 247// AcceptableHashes returns a set of hashes that could be recorded for 248// comparison to future results for the same provider version, to implement a 249// "trust on first use" scheme. 250// 251// The AcceptableHashes result is a platform-agnostic set of hashes, with the 252// intent that in most cases it will be used as an additional cross-check in 253// addition to a platform-specific hash check made during installation. However, 254// there are some situations (such as verifying an already-installed package 255// that's on local disk) where Terraform would check only against the results 256// of this function, meaning that it would in principle accept another 257// platform's package as a substitute for the correct platform. That's a 258// pragmatic compromise to allow lock files derived from the result of this 259// method to be portable across platforms. 260// 261// Callers of this method should typically also verify the package using the 262// object in the Authentication field, and consider how much trust to give 263// the result of this method depending on the authentication result: an 264// unauthenticated result or one that only verified a checksum could be 265// considered less trustworthy than one that checked the package against 266// a signature provided by the origin registry. 267// 268// The AcceptableHashes result is actually provided by the object in the 269// Authentication field. AcceptableHashes therefore returns an empty result 270// for a PackageMeta that has no authentication object, or has one that does 271// not make use of hashes. 272func (m PackageMeta) AcceptableHashes() []Hash { 273 auth, ok := m.Authentication.(PackageAuthenticationHashes) 274 if !ok { 275 return nil 276 } 277 return auth.AcceptableHashes() 278} 279 280// PackageLocation represents a location where a provider distribution package 281// can be obtained. A value of this type contains one of the following 282// concrete types: PackageLocalArchive, PackageLocalDir, or PackageHTTPURL. 283type PackageLocation interface { 284 packageLocation() 285 String() string 286} 287 288// PackageLocalArchive is the location of a provider distribution archive file 289// in the local filesystem. Its value is a local filesystem path using the 290// syntax understood by Go's standard path/filepath package on the operating 291// system where Terraform is running. 292type PackageLocalArchive string 293 294func (p PackageLocalArchive) packageLocation() {} 295func (p PackageLocalArchive) String() string { return string(p) } 296 297// PackageLocalDir is the location of a directory containing an unpacked 298// provider distribution archive in the local filesystem. Its value is a local 299// filesystem path using the syntax understood by Go's standard path/filepath 300// package on the operating system where Terraform is running. 301type PackageLocalDir string 302 303func (p PackageLocalDir) packageLocation() {} 304func (p PackageLocalDir) String() string { return string(p) } 305 306// PackageHTTPURL is a provider package location accessible via HTTP. 307// Its value is a URL string using either the http: scheme or the https: scheme. 308type PackageHTTPURL string 309 310func (p PackageHTTPURL) packageLocation() {} 311func (p PackageHTTPURL) String() string { return string(p) } 312 313// PackageMetaList is a list of PackageMeta. It's just []PackageMeta with 314// some methods for convenient sorting and filtering. 315type PackageMetaList []PackageMeta 316 317func (l PackageMetaList) Len() int { 318 return len(l) 319} 320 321func (l PackageMetaList) Less(i, j int) bool { 322 return l[i].LessThan(l[j]) 323} 324 325func (l PackageMetaList) Swap(i, j int) { 326 l[i], l[j] = l[j], l[i] 327} 328 329// Sort performs an in-place, stable sort on the contents of the list, using 330// the ordering given by method Less. This ordering is primarily to help 331// encourage deterministic results from functions and does not have any 332// semantic meaning. 333func (l PackageMetaList) Sort() { 334 sort.Stable(l) 335} 336 337// FilterPlatform constructs a new PackageMetaList that contains only the 338// elements of the receiver that are for the given target platform. 339// 340// Pass CurrentPlatform to filter only for packages targeting the platform 341// where this code is running. 342func (l PackageMetaList) FilterPlatform(target Platform) PackageMetaList { 343 var ret PackageMetaList 344 for _, m := range l { 345 if m.TargetPlatform == target { 346 ret = append(ret, m) 347 } 348 } 349 return ret 350} 351 352// FilterProviderExactVersion constructs a new PackageMetaList that contains 353// only the elements of the receiver that relate to the given provider address 354// and exact version. 355// 356// The version matching for this function is exact, including matching on 357// semver build metadata, because it's intended for handling a single exact 358// version selected by the caller from a set of available versions. 359func (l PackageMetaList) FilterProviderExactVersion(provider addrs.Provider, version Version) PackageMetaList { 360 var ret PackageMetaList 361 for _, m := range l { 362 if m.Provider == provider && m.Version == version { 363 ret = append(ret, m) 364 } 365 } 366 return ret 367} 368 369// FilterProviderPlatformExactVersion is a combination of both 370// FilterPlatform and FilterProviderExactVersion that filters by all three 371// criteria at once. 372func (l PackageMetaList) FilterProviderPlatformExactVersion(provider addrs.Provider, platform Platform, version Version) PackageMetaList { 373 var ret PackageMetaList 374 for _, m := range l { 375 if m.Provider == provider && m.Version == version && m.TargetPlatform == platform { 376 ret = append(ret, m) 377 } 378 } 379 return ret 380} 381 382// VersionConstraintsString returns a canonical string representation of 383// a VersionConstraints value. 384func VersionConstraintsString(spec VersionConstraints) string { 385 // (we have our own function for this because the upstream versions 386 // library prefers to use npm/cargo-style constraint syntax, but 387 // Terraform prefers Ruby-like. Maybe we can upstream a "RubyLikeString") 388 // function to do this later, but having this in here avoids blocking on 389 // that and this is the sort of thing that is unlikely to need ongoing 390 // maintenance because the version constraint syntax is unlikely to change.) 391 // 392 // ParseVersionConstraints allows some variations for convenience, but the 393 // return value from this function serves as the normalized form of a 394 // particular version constraint, which is the form we require in dependency 395 // lock files. Therefore the canonical forms produced here are a compatibility 396 // constraint for the dependency lock file parser. 397 398 if len(spec) == 0 { 399 return "" 400 } 401 402 // VersionConstraints values are typically assembled by combining together 403 // the version constraints from many separate declarations throughout 404 // a configuration, across many modules. As a consequence, they typically 405 // contain duplicates and the terms inside are in no particular order. 406 // For our canonical representation we'll both deduplicate the items 407 // and sort them into a consistent order. 408 sels := make(map[constraints.SelectionSpec]struct{}) 409 for _, sel := range spec { 410 // The parser allows writing abbreviated version (such as 2) which 411 // end up being represented in memory with trailing unconstrained parts 412 // (for example 2.*.*). For the purpose of serialization with Ruby 413 // style syntax, these unconstrained parts can all be represented as 0 414 // with no loss of meaning, so we make that conversion here. Doing so 415 // allows us to deduplicate equivalent constraints, such as >= 2.0 and 416 // >= 2.0.0. 417 normalizedSel := constraints.SelectionSpec{ 418 Operator: sel.Operator, 419 Boundary: sel.Boundary.ConstrainToZero(), 420 } 421 sels[normalizedSel] = struct{}{} 422 } 423 selsOrder := make([]constraints.SelectionSpec, 0, len(sels)) 424 for sel := range sels { 425 selsOrder = append(selsOrder, sel) 426 } 427 sort.Slice(selsOrder, func(i, j int) bool { 428 is, js := selsOrder[i], selsOrder[j] 429 boundaryCmp := versionSelectionBoundaryCompare(is.Boundary, js.Boundary) 430 if boundaryCmp == 0 { 431 // The operator is the decider, then. 432 return versionSelectionOperatorLess(is.Operator, js.Operator) 433 } 434 return boundaryCmp < 0 435 }) 436 437 var b strings.Builder 438 for i, sel := range selsOrder { 439 if i > 0 { 440 b.WriteString(", ") 441 } 442 switch sel.Operator { 443 case constraints.OpGreaterThan: 444 b.WriteString("> ") 445 case constraints.OpLessThan: 446 b.WriteString("< ") 447 case constraints.OpGreaterThanOrEqual: 448 b.WriteString(">= ") 449 case constraints.OpGreaterThanOrEqualPatchOnly, constraints.OpGreaterThanOrEqualMinorOnly: 450 // These two differ in how the version is written, not in the symbol. 451 b.WriteString("~> ") 452 case constraints.OpLessThanOrEqual: 453 b.WriteString("<= ") 454 case constraints.OpEqual: 455 b.WriteString("") 456 case constraints.OpNotEqual: 457 b.WriteString("!= ") 458 default: 459 // The above covers all of the operators we support during 460 // parsing, so we should not get here. 461 b.WriteString("??? ") 462 } 463 464 // We use a different constraint operator to distinguish between the 465 // two types of pessimistic constraint: minor-only and patch-only. For 466 // minor-only constraints, we always want to display only the major and 467 // minor version components, so we special-case that operator below. 468 // 469 // One final edge case is a minor-only constraint specified with only 470 // the major version, such as ~> 2. We treat this the same as ~> 2.0, 471 // because a major-only pessimistic constraint does not exist: it is 472 // logically identical to >= 2.0.0. 473 if sel.Operator == constraints.OpGreaterThanOrEqualMinorOnly { 474 // The minor-pessimistic syntax uses only two version components. 475 fmt.Fprintf(&b, "%s.%s", sel.Boundary.Major, sel.Boundary.Minor) 476 } else { 477 fmt.Fprintf(&b, "%s.%s.%s", sel.Boundary.Major, sel.Boundary.Minor, sel.Boundary.Patch) 478 } 479 if sel.Boundary.Prerelease != "" { 480 b.WriteString("-" + sel.Boundary.Prerelease) 481 } 482 if sel.Boundary.Metadata != "" { 483 b.WriteString("+" + sel.Boundary.Metadata) 484 } 485 } 486 return b.String() 487} 488 489// Our sort for selection operators is somewhat arbitrary and mainly motivated 490// by consistency rather than meaning, but this ordering does at least try 491// to make it so "simple" constraint sets will appear how a human might 492// typically write them, with the lower bounds first and the upper bounds 493// last. Weird mixtures of different sorts of constraints will likely seem 494// less intuitive, but they'd be unintuitive no matter the ordering. 495var versionSelectionsBoundaryPriority = map[constraints.SelectionOp]int{ 496 // We skip zero here so that if we end up seeing an invalid 497 // operator (which the string function would render as "???") 498 // then it will have index zero and thus appear first. 499 constraints.OpGreaterThan: 1, 500 constraints.OpGreaterThanOrEqual: 2, 501 constraints.OpEqual: 3, 502 constraints.OpGreaterThanOrEqualPatchOnly: 4, 503 constraints.OpGreaterThanOrEqualMinorOnly: 5, 504 constraints.OpLessThanOrEqual: 6, 505 constraints.OpLessThan: 7, 506 constraints.OpNotEqual: 8, 507} 508 509func versionSelectionOperatorLess(i, j constraints.SelectionOp) bool { 510 iPrio := versionSelectionsBoundaryPriority[i] 511 jPrio := versionSelectionsBoundaryPriority[j] 512 return iPrio < jPrio 513} 514 515func versionSelectionBoundaryCompare(i, j constraints.VersionSpec) int { 516 // In the Ruby-style constraint syntax, unconstrained parts appear 517 // only for omitted portions of a version string, like writing 518 // "2" instead of "2.0.0". For sorting purposes we'll just 519 // consider those as zero, which also matches how we serialize them 520 // to strings. 521 i, j = i.ConstrainToZero(), j.ConstrainToZero() 522 523 // Once we've removed any unconstrained parts, we can safely 524 // convert to our main Version type so we can use its ordering. 525 iv := Version{ 526 Major: i.Major.Num, 527 Minor: i.Minor.Num, 528 Patch: i.Patch.Num, 529 Prerelease: versions.VersionExtra(i.Prerelease), 530 Metadata: versions.VersionExtra(i.Metadata), 531 } 532 jv := Version{ 533 Major: j.Major.Num, 534 Minor: j.Minor.Num, 535 Patch: j.Patch.Num, 536 Prerelease: versions.VersionExtra(j.Prerelease), 537 Metadata: versions.VersionExtra(j.Metadata), 538 } 539 if iv.Same(jv) { 540 // Although build metadata doesn't normally weigh in to 541 // precedence choices, we'll use it for our visual 542 // ordering just because we need to pick _some_ order. 543 switch { 544 case iv.Metadata.Raw() == jv.Metadata.Raw(): 545 return 0 546 case iv.Metadata.LessThan(jv.Metadata): 547 return -1 548 default: 549 return 1 // greater, by elimination 550 } 551 } 552 switch { 553 case iv.LessThan(jv): 554 return -1 555 default: 556 return 1 // greater, by elimination 557 } 558} 559