Version filtering (#1050)

* extend filtering logic and configuration to include version lists

Module filtering is very useful, but many deployments will need to
satisfy even more granular constrainsts.  Enterprises may need
approved lists specific down to the minor (or patch) version element.

Version filtering logic is similar to the module filtering, in that
it's a prefix match of the version requested against each entry in the
version filter list.

Closes #1045

* include version filtering in documentation with example

* allow filtering when version is missing

Endpoints that do not specify a version, such as "@v/list", only need
to be filtered by module rules.
This commit is contained in:
Chad Kunde
2019-02-05 00:04:09 -08:00
committed by Michal Pristas
parent adb4dbb10f
commit 7811524c22
6 changed files with 106 additions and 40 deletions
+29 -8
View File
@@ -42,7 +42,7 @@ func NewFilter(filterFilePath string) (*Filter, error) {
}
// AddRule adds rule for specified path
func (f *Filter) AddRule(path string, rule FilterRule) {
func (f *Filter) AddRule(path string, versions []string, rule FilterRule) {
f.ensurePath(path)
segments := getPathSegments(path)
@@ -62,13 +62,14 @@ func (f *Filter) AddRule(path string, rule FilterRule) {
last := segments[len(segments)-1]
rn := latest.next[last]
rn.rule = rule
rn.vers = versions
latest.next[last] = rn
}
// Rule returns the filter rule to be applied to the given path
func (f *Filter) Rule(path string) FilterRule {
func (f *Filter) Rule(path, version string) FilterRule {
segs := getPathSegments(path)
rule := f.getAssociatedRule(segs...)
rule := f.getAssociatedRule(version, segs...)
if rule == Default {
rule = Include
}
@@ -88,7 +89,7 @@ func (f *Filter) ensurePath(path string) {
}
}
func (f *Filter) getAssociatedRule(path ...string) FilterRule {
func (f *Filter) getAssociatedRule(version string, path ...string) FilterRule {
if len(path) == 0 {
return f.root.rule
}
@@ -100,7 +101,17 @@ func (f *Filter) getAssociatedRule(path ...string) FilterRule {
break
}
rn = rn.next[p]
rules = append(rules, rn.rule)
// 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 = true
break
}
}
if match || version == "" {
rules = append(rules, rn.rule)
}
}
if len(rules) == 0 {
@@ -140,7 +151,7 @@ func initFromConfig(filePath string) (*Filter, error) {
}
split := strings.Split(line, " ")
if len(split) > 2 {
if len(split) > 3 {
return nil, errors.E(op, "Invalid configuration found in filter file at the line "+strconv.Itoa(idx+1))
}
@@ -158,12 +169,22 @@ func initFromConfig(filePath string) (*Filter, error) {
}
// is root config
if len(split) == 1 {
f.AddRule("", rule)
f.AddRule("", nil, rule)
continue
}
var vers []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] += "."
}
}
}
path := strings.TrimSpace(split[1])
f.AddRule(path, rule)
f.AddRule(path, vers, rule)
}
return f, nil
}
+1
View File
@@ -3,4 +3,5 @@ package module
type ruleNode struct {
next map[string]ruleNode
rule FilterRule
vers []string
}
+50 -27
View File
@@ -52,13 +52,13 @@ func (t *FilterTests) Test_IgnoreSimple() {
f, err := NewFilter(filter)
r.NoError(err)
f.AddRule("github.com/a/b", Exclude)
f.AddRule("github.com/a/b", nil, Exclude)
r.Equal(Include, f.Rule("github.com/a"))
r.Equal(Exclude, f.Rule("github.com/a/b"))
r.Equal(Exclude, f.Rule("github.com/a/b/c"))
r.Equal(Include, f.Rule("github.com/d"))
r.Equal(Include, f.Rule("bitbucket.com/a/b"))
r.Equal(Include, f.Rule("github.com/a", ""))
r.Equal(Exclude, f.Rule("github.com/a/b", ""))
r.Equal(Exclude, f.Rule("github.com/a/b/c", ""))
r.Equal(Include, f.Rule("github.com/d", ""))
r.Equal(Include, f.Rule("bitbucket.com/a/b", ""))
}
func (t *FilterTests) Test_IgnoreParentAllowChildren() {
@@ -69,14 +69,14 @@ func (t *FilterTests) Test_IgnoreParentAllowChildren() {
f, err := NewFilter(filter)
r.NoError(err)
f.AddRule("github.com/a/b", Exclude)
f.AddRule("github.com/a/b/c", Include)
f.AddRule("github.com/a/b", nil, Exclude)
f.AddRule("github.com/a/b/c", nil, Include)
r.Equal(Include, f.Rule("github.com/a"))
r.Equal(Exclude, f.Rule("github.com/a/b"))
r.Equal(Include, f.Rule("github.com/a/b/c"))
r.Equal(Include, f.Rule("github.com/d"))
r.Equal(Include, f.Rule("bitbucket.com/a/b"))
r.Equal(Include, f.Rule("github.com/a", ""))
r.Equal(Exclude, f.Rule("github.com/a/b", ""))
r.Equal(Include, f.Rule("github.com/a/b/c", ""))
r.Equal(Include, f.Rule("github.com/d", ""))
r.Equal(Include, f.Rule("bitbucket.com/a/b", ""))
}
func (t *FilterTests) Test_OnlyAllowed() {
@@ -87,14 +87,14 @@ func (t *FilterTests) Test_OnlyAllowed() {
f, err := NewFilter(filter)
r.NoError(err)
f.AddRule("github.com/a/b", Include)
f.AddRule("", Exclude)
f.AddRule("github.com/a/b", nil, Include)
f.AddRule("", nil, Exclude)
r.Equal(Exclude, f.Rule("github.com/a"))
r.Equal(Include, f.Rule("github.com/a/b"))
r.Equal(Include, f.Rule("github.com/a/b/c"))
r.Equal(Exclude, f.Rule("github.com/d"))
r.Equal(Exclude, f.Rule("bitbucket.com/a/b"))
r.Equal(Exclude, f.Rule("github.com/a", ""))
r.Equal(Include, f.Rule("github.com/a/b", ""))
r.Equal(Include, f.Rule("github.com/a/b/c", ""))
r.Equal(Exclude, f.Rule("github.com/d", ""))
r.Equal(Exclude, f.Rule("bitbucket.com/a/b", ""))
}
func (t *FilterTests) Test_Direct() {
@@ -105,14 +105,32 @@ func (t *FilterTests) Test_Direct() {
f, err := NewFilter(filter)
r.NoError(err)
f.AddRule("github.com/a/b/c", Exclude)
f.AddRule("github.com/a/b", Direct)
f.AddRule("github.com/a", Include)
f.AddRule("", Exclude)
f.AddRule("github.com/a/b/c", nil, Exclude)
f.AddRule("github.com/a/b", nil, Direct)
f.AddRule("github.com/a", nil, Include)
f.AddRule("", nil, Exclude)
r.Equal(Include, f.Rule("github.com/a"))
r.Equal(Direct, f.Rule("github.com/a/b"))
r.Equal(Exclude, f.Rule("github.com/a/b/c/d"))
r.Equal(Include, f.Rule("github.com/a", ""))
r.Equal(Direct, f.Rule("github.com/a/b", ""))
r.Equal(Exclude, f.Rule("github.com/a/b/c/d", ""))
}
func (t *FilterTests) Test_versionFilter() {
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."}, Include)
f.AddRule("github.com/a/b/c", []string{"v1.2.", "v0.8."}, Direct)
r.Equal(Exclude, f.Rule("github.com/d/e", "v1.2.0"))
r.Equal(Exclude, f.Rule("github.com/a/b", "v10.0.0"))
r.Equal(Include, f.Rule("github.com/a/b", "v1.5.0"))
r.Equal(Direct, f.Rule("github.com/a/b/c/d", "v1.2.3"))
r.Equal(Include, f.Rule("github.com/a/b/c/d", "v1.3.4"))
}
func (t *FilterTests) Test_initFromConfig() {
@@ -133,6 +151,11 @@ func (t *FilterTests) Test_initFromConfig() {
r.Nil(f)
r.Error(err)
versionInput := []byte("+ github.com/a/b\n\n# some comment\n\n- github.com/c/d v1,v2.3.4,v3.2.*\n\nD github.com/x\n")
ioutil.WriteFile(filterFile, versionInput, 0644)
f, err = initFromConfig(filterFile)
r.NotNil(f)
r.NoError(err)
}
func tempFilterFile(t *testing.T) (path string) {