Skip to content
This repository has been archived by the owner on Dec 7, 2023. It is now read-only.

Commit

Permalink
Merge pull request #138 from weaveworks/filter-exact
Browse files Browse the repository at this point in the history
Fix the filtering framework to perform exact matches again
  • Loading branch information
luxas authored Jul 11, 2019
2 parents 4ef5592 + d7529a5 commit 8cec097
Show file tree
Hide file tree
Showing 9 changed files with 1,531 additions and 1,467 deletions.
2,840 changes: 1,420 additions & 1,420 deletions docs/dependencies.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions pkg/filter/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ func NewAllFilter() *AllFilter {
return &AllFilter{}
}

func (f *AllFilter) Filter(object meta.Object) (meta.Object, error) {
return object, nil
func (f *AllFilter) Filter(object meta.Object) (filterer.Match, error) {
return filterer.NewMatch(object, false), nil
}

// The AllFilter shouldn't be used to match single Objects
func (f *AllFilter) AmbiguousError() *filterer.AmbiguousError {
func (f *AllFilter) AmbiguousError(_ []filterer.Match) *filterer.AmbiguousError {
return filterer.NewAmbiguousError("ambiguous query: AllFilter used to match single Object")
}

Expand Down
37 changes: 25 additions & 12 deletions pkg/filter/idname.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@ import (
"github.com/weaveworks/ignite/pkg/util"
)

type IDNameMatch struct {
*filterer.GenericMatch
matches []string
}

var _ filterer.Match = &IDNameMatch{}

// The IDNameFilter is the basic filter matching objects by their ID/name
type IDNameFilter struct {
prefix string
matches []string
kind meta.Kind
prefix string
kind meta.Kind
}

var _ filterer.MetaFilter = &IDNameFilter{}
Expand All @@ -23,14 +29,16 @@ func NewIDNameFilter(p string) *IDNameFilter {
}
}

func (f *IDNameFilter) FilterMeta(object meta.Object) (meta.Object, error) {
func (f *IDNameFilter) FilterMeta(object meta.Object) (filterer.Match, error) {
if len(f.kind) == 0 {
f.kind = object.GetKind() // reflect.Indirect(reflect.ValueOf(object)).Type().Name()
}

if matches := util.MatchPrefix(f.prefix, string(object.GetUID()), object.GetName()); len(matches) > 0 {
f.matches = append(f.matches, matches...)
return object, nil
if matches, exact := util.MatchPrefix(f.prefix, string(object.GetUID()), object.GetName()); len(matches) > 0 {
return &IDNameMatch{
filterer.NewMatch(object, exact),
matches,
}, nil
}

return nil, nil
Expand All @@ -40,19 +48,24 @@ func (f *IDNameFilter) SetKind(k meta.Kind) {
f.kind = k
}

func (f *IDNameFilter) AmbiguousError() *filterer.AmbiguousError {
return filterer.NewAmbiguousError("ambiguous %s query: %q matched the following IDs/names: %s", f.kind, f.prefix, formatMatches(f.matches))
func (f *IDNameFilter) AmbiguousError(matches []filterer.Match) *filterer.AmbiguousError {
return filterer.NewAmbiguousError("ambiguous %s query: %q matched the following IDs/names: %s", f.kind, f.prefix, formatMatches(matches))
}

func (f *IDNameFilter) NonexistentError() *filterer.NonexistentError {
return filterer.NewNonexistentError("can't find %s: no ID/name matches for %q", f.kind, f.prefix)
}

func formatMatches(matches []string) string {
func formatMatches(input []filterer.Match) string {
var sb strings.Builder
var matches []string

for _, match := range input {
matches = append(matches, match.(*IDNameMatch).matches...)
}

for i, match := range matches {
sb.WriteString(match)
for i, str := range matches {
sb.WriteString(str)

if i+1 < len(matches) {
sb.WriteString(", ")
Expand Down
18 changes: 6 additions & 12 deletions pkg/filter/name.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ import (

// The NameFilter matches Objects by their exact name
type NameFilter struct {
name string
matches uint64
kind meta.Kind
name string
kind meta.Kind
}

var _ filterer.MetaFilter = &NameFilter{}
Expand All @@ -20,14 +19,9 @@ func NewNameFilter(n string) *NameFilter {
}
}

func (f *NameFilter) FilterMeta(object meta.Object) (meta.Object, error) {
if len(f.kind) == 0 {
f.kind = object.GetKind() // reflect.Indirect(reflect.ValueOf(object)).Type().Name()
}

func (f *NameFilter) FilterMeta(object meta.Object) (filterer.Match, error) {
if object.GetName() == f.name {
f.matches++
return object, nil
return filterer.NewMatch(object, true), nil
}

return nil, nil
Expand All @@ -37,8 +31,8 @@ func (f *NameFilter) SetKind(k meta.Kind) {
f.kind = k
}

func (f *NameFilter) AmbiguousError() *filterer.AmbiguousError {
return filterer.NewAmbiguousError("ambiguous %s query: %q matched %d names", f.kind, f.name, f.matches)
func (f *NameFilter) AmbiguousError(_ []filterer.Match) *filterer.AmbiguousError {
return filterer.NewAmbiguousError("ambiguous %s query: %q matched multiple names", f.kind, f.name)
}

func (f *NameFilter) NonexistentError() *filterer.NonexistentError {
Expand Down
2 changes: 1 addition & 1 deletion pkg/filter/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func NewVMFilterAll(p string, all bool) *VMFilter {
}
}

func (f *VMFilter) Filter(object meta.Object) (meta.Object, error) {
func (f *VMFilter) Filter(object meta.Object) (filterer.Match, error) {
// Option to list just running VMs
if !f.all {
vm, ok := object.(*api.VM)
Expand Down
11 changes: 7 additions & 4 deletions pkg/storage/filterer/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import (
type BaseFilter interface {
// AmbiguousError specifies what to error if
// a single request returned multiple matches
AmbiguousError() *AmbiguousError
// The matches are given as an argument
AmbiguousError([]Match) *AmbiguousError
// NonexistentError specifies what to error if
// a single request returned no matches
NonexistentError() *NonexistentError
Expand All @@ -22,8 +23,9 @@ type BaseFilter interface {
type ObjectFilter interface {
BaseFilter
// Every Object to be filtered is passed though Filter, which should
// return the Object on match, or nil if it doesn't match
Filter(meta.Object) (meta.Object, error)
// return the Object on match, or nil if it doesn't match.
// The boolean indicates an exact match.
Filter(meta.Object) (Match, error)
}

// MetaFilter implementations operate on meta.APIType objects,
Expand All @@ -33,7 +35,8 @@ type MetaFilter interface {
// Every Object to be filtered is passed though FilterMeta, which should
// return the Object on match, or nil if it doesn't match. The Objects
// given to FilterMeta are of type meta.APIType, stripped of other contents.
FilterMeta(meta.Object) (meta.Object, error)
// The boolean indicates an exact match.
FilterMeta(meta.Object) (Match, error)
}

type AmbiguousError struct {
Expand Down
33 changes: 25 additions & 8 deletions pkg/storage/filterer/filterer.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ func NewFilterer(storage storage.Storage) *Filterer {
}
}

type filterFunc func(meta.Object) (meta.Object, error)
type filterFunc func(meta.Object) (Match, error)

// Find a single meta.Object of the given kind using the given filter
func (f *Filterer) Find(kind meta.Kind, filter BaseFilter) (meta.Object, error) {
var result meta.Object
var results []Match
var exactMatch Match

// Fetch the sources, correct filtering method and if we're dealing with meta.APIType objects
sources, filterFunc, metaObjects, err := f.parseFilter(kind, filter)
Expand All @@ -34,16 +35,32 @@ func (f *Filterer) Find(kind meta.Kind, filter BaseFilter) (meta.Object, error)
if match, err := filterFunc(object); err != nil { // The filter returns meta.Object if it matches, otherwise nil
return nil, err
} else if match != nil {
if result != nil {
return nil, filter.AmbiguousError()
if match.Exact() {
if exactMatch != nil {
// We have multiple exact matches, the user has done something wrong
return nil, filter.AmbiguousError([]Match{exactMatch, match})
} else {
exactMatch = match
}
} else {
result = match
results = append(results, match)
}
}
}

if result == nil {
return nil, filter.NonexistentError()
var result meta.Object

// If we have an exact result, select it
if exactMatch != nil {
result = exactMatch.Object()
} else {
if len(results) == 0 {
return nil, filter.NonexistentError()
} else if len(results) > 1 {
return nil, filter.AmbiguousError(results)
}

result = results[0].Object()
}

// If we're filtering meta.APIType objects, load the full Object to be returned
Expand All @@ -69,7 +86,7 @@ func (f *Filterer) FindAll(kind meta.Kind, filter BaseFilter) ([]meta.Object, er
if match, err := filterFunc(object); err != nil { // The filter returns meta.Object if it matches, otherwise nil
return nil, err
} else if match != nil {
results = append(results, match)
results = append(results, match.Object())
}
}

Expand Down
38 changes: 38 additions & 0 deletions pkg/storage/filterer/match.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package filterer

import (
meta "github.com/weaveworks/ignite/pkg/apis/meta/v1alpha1"
)

// Match describes the result of filtering an Object
// If the Object to be filtered didn't match, return nil
type Match interface {
// Get the matched Object
Object() meta.Object
// Check if the match was exact
Exact() bool
}

// GenericMatch is the simplest implementation
// of Match, carrying no additional data
type GenericMatch struct {
object meta.Object
exact bool
}

var _ Match = &GenericMatch{}

func NewMatch(object meta.Object, exact bool) *GenericMatch {
return &GenericMatch{
object: object,
exact: exact,
}
}

func (m *GenericMatch) Object() meta.Object {
return m.object
}

func (m *GenericMatch) Exact() bool {
return m.exact
}
13 changes: 6 additions & 7 deletions pkg/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,25 +96,24 @@ func RandomName() string {
return namegenerator.NewNameGenerator(time.Now().UTC().UnixNano()).Generate()
}

func MatchPrefix(prefix string, fields ...string) []string {
func MatchPrefix(prefix string, fields ...string) ([]string, bool) {
var prefixMatches, exactMatches []string

for _, str := range fields {
if strings.HasPrefix(str, prefix) {
prefixMatches = append(prefixMatches, str)
}

if str == prefix {
exactMatches = append(exactMatches, str)
} else if strings.HasPrefix(str, prefix) {
prefixMatches = append(prefixMatches, str)
}
}

// If we have exact matches, return them
// and set the exact match boolean
if len(exactMatches) > 0 {
return exactMatches
return exactMatches, true
}

return prefixMatches
return prefixMatches, false
}

func TestRoot() (bool, error) {
Expand Down

0 comments on commit 8cec097

Please sign in to comment.