-
Notifications
You must be signed in to change notification settings - Fork 303
/
object_selector.go
156 lines (131 loc) · 4.64 KB
/
object_selector.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package k8s
import (
"fmt"
"regexp"
"github.com/google/go-cmp/cmp"
"github.com/pkg/errors"
"github.com/tilt-dev/tilt/internal/sliceutils"
"github.com/tilt-dev/tilt/pkg/apis/core/v1alpha1"
)
// A selector matches an entity if all non-empty selector fields match the corresponding entity fields
type ObjectSelector struct {
spec v1alpha1.ObjectSelector
apiVersion *regexp.Regexp
kind *regexp.Regexp
name *regexp.Regexp
namespace *regexp.Regexp
}
func ParseObjectSelector(spec v1alpha1.ObjectSelector) (ObjectSelector, error) {
ret := ObjectSelector{spec: spec}
var err error
ret.apiVersion, err = regexp.Compile(spec.APIVersionRegexp)
if err != nil {
return ObjectSelector{}, errors.Wrap(err, "error parsing apiVersion regexp")
}
ret.kind, err = regexp.Compile(spec.KindRegexp)
if err != nil {
return ObjectSelector{}, errors.Wrap(err, "error parsing kind regexp")
}
ret.name, err = regexp.Compile(spec.NameRegexp)
if err != nil {
return ObjectSelector{}, errors.Wrap(err, "error parsing name regexp")
}
ret.namespace, err = regexp.Compile(spec.NamespaceRegexp)
if err != nil {
return ObjectSelector{}, errors.Wrap(err, "error parsing namespace regexp")
}
return ret, nil
}
var splitOptions = sliceutils.NewEscapeSplitOptions()
func SelectorStringFromParts(parts []string) string {
return sliceutils.EscapeAndJoin(parts, splitOptions)
}
// format is <name:required>:<kind:optional>:<namespace:optional>
func SelectorFromString(s string) (ObjectSelector, error) {
parts, err := sliceutils.UnescapeAndSplit(s, splitOptions)
if err != nil {
return ObjectSelector{}, err
}
if len(s) == 0 {
return ObjectSelector{}, fmt.Errorf("selector can't be empty")
}
if len(parts) == 1 {
return NewFullmatchCaseInsensitiveObjectSelector("", "", parts[0], "")
}
if len(parts) == 2 {
return NewFullmatchCaseInsensitiveObjectSelector("", parts[1], parts[0], "")
}
if len(parts) == 3 {
return NewFullmatchCaseInsensitiveObjectSelector("", parts[1], parts[0], parts[2])
}
return ObjectSelector{}, fmt.Errorf("Too many parts in selector. Selectors must contain between 1 and 3 parts (colon separated), found %d parts in %s", len(parts), s)
}
// TODO(dmiller): this function and newPartialMatchK8sObjectSelector
// should be written in to a form that can be used like this
// x := re{pattern: name, ignoreCase: true, fullMatch: true}
// x.compile()
// rather than passing around and mutating regex strings
// Creates a new ObjectSelector
// If an arg is an empty string it will become an empty regex that matches all input
// Otherwise the arg must match the input exactly
func NewFullmatchCaseInsensitiveObjectSelector(apiVersion string, kind string, name string, namespace string) (ObjectSelector, error) {
return ParseObjectSelector(v1alpha1.ObjectSelector{
APIVersionRegexp: exactOrEmptyRegex(apiVersion),
KindRegexp: exactOrEmptyRegex(kind),
NameRegexp: exactOrEmptyRegex(name),
NamespaceRegexp: exactOrEmptyRegex(namespace),
})
}
func makeCaseInsensitive(s string) string {
if s == "" {
return s
} else {
return "(?i)" + s
}
}
func exactOrEmptyRegex(s string) string {
if s != "" {
s = fmt.Sprintf("^%s$", makeCaseInsensitive(regexp.QuoteMeta(s)))
}
return s
}
// Create a selector that matches the Kind. Useful for testing.
func MustKindSelector(kind string) ObjectSelector {
sel, err := NewFullmatchCaseInsensitiveObjectSelector("", kind, "", "")
if err != nil {
panic(err)
}
return sel
}
// Create a selector that matches the Name. Useful for testing.
func MustNameSelector(name string) ObjectSelector {
sel, err := NewFullmatchCaseInsensitiveObjectSelector("", "", name, "")
if err != nil {
panic(err)
}
return sel
}
// Creates a new ObjectSelector
// If an arg is an empty string, it will become an empty regex that matches all input
// Otherwise the arg will match input from the beginning (prefix matching)
func NewPartialMatchObjectSelector(apiVersion string, kind string, name string, namespace string) (ObjectSelector, error) {
return ParseObjectSelector(v1alpha1.ObjectSelector{
APIVersionRegexp: makeCaseInsensitive(apiVersion),
KindRegexp: makeCaseInsensitive(kind),
NameRegexp: makeCaseInsensitive(name),
NamespaceRegexp: makeCaseInsensitive(namespace),
})
}
func (o1 ObjectSelector) EqualsSelector(o2 ObjectSelector) bool {
return cmp.Equal(o1.spec, o2.spec)
}
func (k ObjectSelector) Matches(e K8sEntity) bool {
gvk := e.GVK()
return k.apiVersion.MatchString(gvk.GroupVersion().String()) &&
k.kind.MatchString(gvk.Kind) &&
k.name.MatchString(e.Name()) &&
k.namespace.MatchString(e.Namespace().String())
}
func (k ObjectSelector) ToSpec() v1alpha1.ObjectSelector {
return k.spec
}