mirror of
https://github.com/gomods/athens
synced 2026-02-03 12:10:32 +00:00
Filter (#1075)
* Working tests * More tests, a bugfix (yay tests) and docs * Changed filter description adding v prefix to versions * Updated docs to be more precise * More robust, changed docs * Fixed copy paste bug
This commit is contained in:
committed by
Aaron Schlesinger
parent
2816e01ce5
commit
04c6bbed90
@@ -68,3 +68,26 @@ D enterprise.github.com/company
|
||||
</pre>
|
||||
|
||||
In the above example, any module not in the rules will be excluded. All modules from `enterprise.github.com/company` are fetched directly from the source. The `github.com/gomods/athens` module will be stored in the proxy storage, but only for version `v0.4.1` and any patch versions under `v0.1` and `v0.2` minor versions.
|
||||
|
||||
### Versions Filter Modifiers
|
||||
|
||||
Athens provides advanced filter modifiers to cover cases such as API compatibility or when a given dependency changes its license from a given versions. The modifiers are intended to be used in the pattern list of the filter file.
|
||||
|
||||
<pre>
|
||||
-
|
||||
# external dependency approved list
|
||||
+ github.com/gomods/athens <v1.2.3
|
||||
</pre>
|
||||
|
||||
The currently supported modifiers are
|
||||
|
||||
* `~1.2.3` will enable all patch versions from 1.2.3 and above (e.g. 1.2.3, 1.2.4, 1.2.5)
|
||||
* Formally, `1.2.x` where `x >= 3`
|
||||
|
||||
* `^1.2.3` will enable all patch and minor versions from 1.2.3 and above (e.g. 1.2.4, 1.3.0 and 1.4.5)
|
||||
* Formally, `1.x.y` where `x >= 2` and `y >= 3`
|
||||
|
||||
* `<1.2.3` will enable all versions lower than 1.2.3 (e.g. 1.2.2, 1.0.0 and 0.58.9)
|
||||
* Formally, `x.y.z` where `x <= 1`, `y < = 2` and `z < 3`
|
||||
|
||||
This kind of modifiers will work only if a three parts semantic version is specified. For example, `~4.5.6` will work while `~4.5` won't.
|
||||
+101
-15
@@ -10,7 +10,8 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
pathSeparator = "/"
|
||||
pathSeparator = "/"
|
||||
versionSeparator = "."
|
||||
)
|
||||
|
||||
// Filter is a filter of modules
|
||||
@@ -42,7 +43,7 @@ func NewFilter(filterFilePath string) (*Filter, error) {
|
||||
}
|
||||
|
||||
// AddRule adds rule for specified path
|
||||
func (f *Filter) AddRule(path string, versions []string, rule FilterRule) {
|
||||
func (f *Filter) AddRule(path string, qualifiers []string, rule FilterRule) {
|
||||
f.ensurePath(path)
|
||||
|
||||
segments := getPathSegments(path)
|
||||
@@ -62,7 +63,7 @@ func (f *Filter) AddRule(path string, versions []string, rule FilterRule) {
|
||||
last := segments[len(segments)-1]
|
||||
rn := latest.next[last]
|
||||
rn.rule = rule
|
||||
rn.vers = versions
|
||||
rn.qualifiers = qualifiers
|
||||
latest.next[last] = rn
|
||||
}
|
||||
|
||||
@@ -102,9 +103,9 @@ func (f *Filter) getAssociatedRule(version string, path ...string) FilterRule {
|
||||
}
|
||||
rn = rn.next[p]
|
||||
// default to true if no version filter, false otherwise
|
||||
match := len(rn.vers) == 0
|
||||
for _, ver := range rn.vers {
|
||||
if strings.HasPrefix(version, ver) {
|
||||
match := len(rn.qualifiers) == 0
|
||||
for _, q := range rn.qualifiers {
|
||||
if matches(version, q) {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
@@ -172,30 +173,115 @@ func initFromConfig(filePath string) (*Filter, error) {
|
||||
f.AddRule("", nil, rule)
|
||||
continue
|
||||
}
|
||||
var vers []string
|
||||
var qual []string
|
||||
if len(split) == 3 {
|
||||
vers = strings.Split(split[2], ",")
|
||||
for i := range vers {
|
||||
vers[i] = strings.TrimRight(vers[i], "*")
|
||||
if vers[i][len(vers[i])-1] != '.' && strings.Count(vers[i], ".") < 2 {
|
||||
vers[i] += "."
|
||||
qual = strings.Split(split[2], ",")
|
||||
for i := range qual {
|
||||
qual[i] = strings.TrimRight(qual[i], "*")
|
||||
if qual[i][len(qual[i])-1] != '.' && strings.Count(qual[i], ".") < 2 {
|
||||
qual[i] += "."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
path := strings.TrimSpace(split[1])
|
||||
f.AddRule(path, vers, rule)
|
||||
f.AddRule(path, qual, rule)
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// matches checks if the given version matches the given qualifier.
|
||||
// Qualifiers can be:
|
||||
// - plain versions
|
||||
// - v1.2.3 enables v1.2.3
|
||||
// - ~1.2.3: enables 1.2.x which are at least 1.2.3
|
||||
// - ^1.2.3: enables 1.x.x which are at least 1.2.3
|
||||
// - <1.2.3: enables everything lower than 1.2.3 includes 1.2.2 and 0.58.9 as well
|
||||
func matches(version, qualifier string) bool {
|
||||
if len(qualifier) < 2 || len(version) < 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
prefix := qualifier[0]
|
||||
first := qualifier[1]
|
||||
|
||||
// v1.2.3 means we accept every version starting with v1.2.3
|
||||
// handle this special case first, then go for ~v1.2.3 and similar
|
||||
if prefix == 'v' && first >= '0' && first <= '9' { // a number
|
||||
return strings.HasPrefix(version, qualifier)
|
||||
}
|
||||
|
||||
v, err := getVersionSegments(version[1:])
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
q, err := getVersionSegments(qualifier[2:])
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(v) != len(q) {
|
||||
return false
|
||||
}
|
||||
// no semver
|
||||
if len(v) != 3 || len(q) != 3 {
|
||||
return false
|
||||
}
|
||||
|
||||
switch prefix {
|
||||
case '~':
|
||||
if v[0] == q[0] && v[1] == q[1] && v[2] >= q[2] {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
case '^':
|
||||
if v[0] == q[0] && v[1] > q[1] {
|
||||
return true
|
||||
}
|
||||
if v[0] == q[0] && v[1] == q[1] && v[2] >= q[2] {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
case '<':
|
||||
if v[0] < q[0] {
|
||||
return true
|
||||
}
|
||||
if v[0] == q[0] && v[1] < q[1] {
|
||||
return true
|
||||
}
|
||||
if v[0] == q[0] && v[1] == q[1] && v[2] <= q[2] {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getPathSegments(path string) []string {
|
||||
return getSegments(path, pathSeparator)
|
||||
}
|
||||
|
||||
func getVersionSegments(path string) ([]int, error) {
|
||||
vv := getSegments(path, versionSeparator)
|
||||
res := make([]int, len(vv))
|
||||
for i, v := range vv {
|
||||
n, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res[i] = n
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func getSegments(path, separator string) []string {
|
||||
path = strings.TrimSpace(path)
|
||||
path = strings.Trim(path, pathSeparator)
|
||||
path = strings.Trim(path, separator)
|
||||
if path == "" {
|
||||
return []string{}
|
||||
}
|
||||
return strings.Split(path, pathSeparator)
|
||||
return strings.Split(path, separator)
|
||||
}
|
||||
|
||||
func newRule(r FilterRule) ruleNode {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package module
|
||||
|
||||
type ruleNode struct {
|
||||
next map[string]ruleNode
|
||||
rule FilterRule
|
||||
vers []string
|
||||
next map[string]ruleNode
|
||||
rule FilterRule
|
||||
qualifiers []string
|
||||
}
|
||||
|
||||
@@ -133,6 +133,75 @@ func (t *FilterTests) Test_versionFilter() {
|
||||
r.Equal(Include, f.Rule("github.com/a/b/c/d", "v1.3.4"))
|
||||
}
|
||||
|
||||
func (t *FilterTests) Test_versionFilterMinor() {
|
||||
r := t.Require()
|
||||
filter := tempFilterFile(t.T())
|
||||
defer os.Remove(filter)
|
||||
|
||||
f, err := NewFilter(filter)
|
||||
r.NoError(err)
|
||||
f.AddRule("", nil, Exclude)
|
||||
f.AddRule("github.com/a/b", []string{"~v1.2.3", "~v2.3.40"}, Include)
|
||||
r.Equal(Include, f.Rule("github.com/a/b", "v1.2.3"))
|
||||
r.Equal(Include, f.Rule("github.com/a/b", "v1.2.5"))
|
||||
r.Equal(Exclude, f.Rule("github.com/a/b", "v1.2.2"))
|
||||
r.Equal(Exclude, f.Rule("github.com/a/b", "v1.3.3"))
|
||||
r.Equal(Include, f.Rule("github.com/a/b", "v2.3.45"))
|
||||
r.Equal(Exclude, f.Rule("github.com/a/b", "v2.2.45"))
|
||||
r.Equal(Exclude, f.Rule("github.com/a/b", "v2.3.20"))
|
||||
}
|
||||
|
||||
func (t *FilterTests) Test_versionFilterMiddle() {
|
||||
r := t.Require()
|
||||
filter := tempFilterFile(t.T())
|
||||
defer os.Remove(filter)
|
||||
|
||||
f, err := NewFilter(filter)
|
||||
r.NoError(err)
|
||||
f.AddRule("", nil, Exclude)
|
||||
f.AddRule("github.com/a/b", []string{"^v1.2.3", "^v2.3.40"}, Include)
|
||||
r.Equal(Include, f.Rule("github.com/a/b", "v1.2.3"))
|
||||
r.Equal(Include, f.Rule("github.com/a/b", "v1.2.5"))
|
||||
r.Equal(Include, f.Rule("github.com/a/b", "v1.4.2"))
|
||||
r.Equal(Exclude, f.Rule("github.com/a/b", "v1.2.1"))
|
||||
r.Equal(Include, f.Rule("github.com/a/b", "v2.3.45"))
|
||||
r.Equal(Include, f.Rule("github.com/a/b", "v2.4.1"))
|
||||
r.Equal(Exclude, f.Rule("github.com/a/b", "v2.2.45"))
|
||||
}
|
||||
|
||||
func (t *FilterTests) Test_versionFilterLess() {
|
||||
r := t.Require()
|
||||
filter := tempFilterFile(t.T())
|
||||
defer os.Remove(filter)
|
||||
|
||||
f, err := NewFilter(filter)
|
||||
r.NoError(err)
|
||||
f.AddRule("", nil, Exclude)
|
||||
f.AddRule("github.com/a/b", []string{"<v2.3.40"}, Include)
|
||||
r.Equal(Include, f.Rule("github.com/a/b", "v1.2.3"))
|
||||
r.Equal(Include, f.Rule("github.com/a/b", "v1.4.2"))
|
||||
r.Equal(Include, f.Rule("github.com/a/b", "v1.2.1"))
|
||||
r.Equal(Include, f.Rule("github.com/a/b", "v2.3.39"))
|
||||
r.Equal(Include, f.Rule("github.com/a/b", "v2.2.45"))
|
||||
r.Equal(Exclude, f.Rule("github.com/a/b", "v2.4.1"))
|
||||
}
|
||||
|
||||
func (t *FilterTests) Test_versionFilterRobust() {
|
||||
r := t.Require()
|
||||
filter := tempFilterFile(t.T())
|
||||
defer os.Remove(filter)
|
||||
|
||||
f, err := NewFilter(filter)
|
||||
r.NoError(err)
|
||||
f.AddRule("", nil, Exclude)
|
||||
f.AddRule("github.com/a/b", []string{"abcd"}, Include)
|
||||
f.AddRule("github.com/c/d", []string{"e"}, Include)
|
||||
|
||||
r.Equal(Exclude, f.Rule("github.com/a/b", "a"))
|
||||
r.Equal(Exclude, f.Rule("github.com/c/d", "fg"))
|
||||
|
||||
}
|
||||
|
||||
func (t *FilterTests) Test_initFromConfig() {
|
||||
r := t.Require()
|
||||
filterFile := tempFilterFile(t.T())
|
||||
|
||||
Reference in New Issue
Block a user