forked from hashicorp/terraform
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
plugin/discovery: use go-version instead of semver
The semver library we were using doesn't have support for a "pessimistic constraint" where e.g. the user wants to accept only minor or patch version upgrades. This is important for providers since users will generally want to pin their dependencies to not inadvertantly accept breaking changes. So here we switch to hashicorp's home-grown go-version library, which has the ~> constraint operator for this sort of constraint. Given how much the old version object was already intruding into the interface and creating dependency noise in callers, this also now wraps the "raw" go-version objects in package-local structs, thus keeping the details encapsulated and allowing callers to deal just with this package's own types.
- Loading branch information
1 parent
a8a64c6
commit a1e29ae
Showing
8 changed files
with
211 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package discovery | ||
|
||
// PluginRequirements describes a set of plugins (assumed to be of a consistent | ||
// kind) that are required to exist and have versions within the given | ||
// corresponding sets. | ||
// | ||
// PluginRequirements is a map from plugin name to VersionSet. | ||
type PluginRequirements map[string]VersionSet | ||
|
||
// Merge takes the contents of the receiver and the other given requirements | ||
// object and merges them together into a single requirements structure | ||
// that satisfies both sets of requirements. | ||
func (r PluginRequirements) Merge(other PluginRequirements) PluginRequirements { | ||
ret := make(PluginRequirements) | ||
for n, vs := range r { | ||
ret[n] = vs | ||
} | ||
for n, vs := range other { | ||
if existing, exists := ret[n]; exists { | ||
ret[n] = existing.Intersection(vs) | ||
} else { | ||
ret[n] = vs | ||
} | ||
} | ||
return ret | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package discovery | ||
|
||
import ( | ||
version "github.com/hashicorp/go-version" | ||
) | ||
|
||
// A VersionStr is a string containing a possibly-invalid representation | ||
// of a semver version number. Call Parse on it to obtain a real Version | ||
// object, or discover that it is invalid. | ||
type VersionStr string | ||
|
||
// Parse transforms a VersionStr into a Version if it is | ||
// syntactically valid. If it isn't then an error is returned instead. | ||
func (s VersionStr) Parse() (Version, error) { | ||
raw, err := version.NewVersion(string(s)) | ||
if err != nil { | ||
return Version{}, err | ||
} | ||
return Version{raw}, nil | ||
} | ||
|
||
// Version represents a version number that has been parsed from | ||
// a semver string and known to be valid. | ||
type Version struct { | ||
// We wrap this here just because it avoids a proliferation of | ||
// direct go-version imports all over the place, and keeps the | ||
// version-processing details within this package. | ||
raw *version.Version | ||
} | ||
|
||
func (v Version) String() string { | ||
return v.raw.String() | ||
} | ||
|
||
func (v Version) newerThan(other Version) bool { | ||
return v.raw.GreaterThan(other.raw) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package discovery | ||
|
||
import ( | ||
version "github.com/hashicorp/go-version" | ||
) | ||
|
||
// A ConstraintStr is a string containing a possibly-invalid representation | ||
// of a version constraint provided in configuration. Call Parse on it to | ||
// obtain a real Constraint object, or discover that it is invalid. | ||
type ConstraintStr string | ||
|
||
// Parse transforms a ConstraintStr into a VersionSet if it is | ||
// syntactically valid. If it isn't then an error is returned instead. | ||
func (s ConstraintStr) Parse() (VersionSet, error) { | ||
raw, err := version.NewConstraint(string(s)) | ||
if err != nil { | ||
return VersionSet{}, err | ||
} | ||
return VersionSet{raw}, nil | ||
} | ||
|
||
// MustParse is like Parse but it panics if the constraint string is invalid. | ||
func (s ConstraintStr) MustParse() VersionSet { | ||
ret, err := s.Parse() | ||
if err != nil { | ||
panic(err) | ||
} | ||
return ret | ||
} | ||
|
||
// VersionSet represents a set of versions which any given Version is either | ||
// a member of or not. | ||
type VersionSet struct { | ||
// Internally a version set is actually a list of constraints that | ||
// *remove* versions from the set. Thus a VersionSet with an empty | ||
// Constraints list would be one that contains *all* versions. | ||
raw version.Constraints | ||
} | ||
|
||
// Has returns true if the given version is in the receiving set. | ||
func (s VersionSet) Has(v Version) bool { | ||
return s.raw.Check(v.raw) | ||
} | ||
|
||
// Intersection combines the receving set with the given other set to produce a | ||
// set that is the intersection of both sets, which is to say that it contains | ||
// only the versions that are members of both sets. | ||
func (s VersionSet) Intersection(other VersionSet) VersionSet { | ||
raw := make(version.Constraints, 0, len(s.raw)+len(other.raw)) | ||
|
||
// Since "raw" is a list of constraints that remove versions from the set, | ||
// "Intersection" is implemented by concatenating together those lists, | ||
// thus leaving behind only the versions not removed by either list. | ||
raw = append(raw, s.raw...) | ||
raw = append(raw, other.raw...) | ||
|
||
return VersionSet{raw} | ||
} | ||
|
||
// String returns a string representation of the set members as a set | ||
// of range constraints. | ||
func (s VersionSet) String() string { | ||
return s.raw.String() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package discovery | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
) | ||
|
||
func TestVersionSet(t *testing.T) { | ||
tests := []struct { | ||
ConstraintStr string | ||
VersionStr string | ||
ShouldHave bool | ||
}{ | ||
// These test cases are not exhaustive since the underlying go-version | ||
// library is well-tested. This is mainly here just to exercise our | ||
// wrapper code, but also used as an opportunity to cover some basic | ||
// but important cases such as the ~> constraint so that we'll be more | ||
// likely to catch any accidental breaking behavior changes in the | ||
// underlying library. | ||
{ | ||
">=1.0.0", | ||
"1.0.0", | ||
true, | ||
}, | ||
{ | ||
">=1.0.0", | ||
"0.0.0", | ||
false, | ||
}, | ||
{ | ||
">=1.0.0", | ||
"1.1.0-beta1", | ||
true, | ||
}, | ||
{ | ||
"~>1.1.0", | ||
"1.1.2-beta1", | ||
true, | ||
}, | ||
{ | ||
"~>1.1.0", | ||
"1.2.0", | ||
false, | ||
}, | ||
} | ||
|
||
for _, test := range tests { | ||
t.Run(fmt.Sprintf("%s has %s", test.ConstraintStr, test.VersionStr), func(t *testing.T) { | ||
accepted, err := ConstraintStr(test.ConstraintStr).Parse() | ||
if err != nil { | ||
t.Fatalf("unwanted error parsing constraints string %q: %s", test.ConstraintStr, err) | ||
} | ||
|
||
version, err := VersionStr(test.VersionStr).Parse() | ||
if err != nil { | ||
t.Fatalf("unwanted error parsing version string %q: %s", test.VersionStr, err) | ||
} | ||
|
||
if got, want := accepted.Has(version), test.ShouldHave; got != want { | ||
t.Errorf("Has returned %#v; want %#v", got, want) | ||
} | ||
}) | ||
} | ||
} |