-
Notifications
You must be signed in to change notification settings - Fork 778
/
Copy pathmatch.go
223 lines (197 loc) Β· 5.82 KB
/
match.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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
package match
import (
"errors"
"strings"
corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// ApplyTo determines what GVKs items the mutation should apply to.
// Globs are not allowed.
// +kubebuilder:object:generate=true
type ApplyTo struct {
Groups []string `json:"groups,omitempty"`
Kinds []string `json:"kinds,omitempty"`
Versions []string `json:"versions,omitempty"`
}
// Flatten returns the set of GroupVersionKinds this ApplyTo matches.
// The GVKs are not guaranteed to be sorted or unique.
func (a ApplyTo) Flatten() []schema.GroupVersionKind {
var result []schema.GroupVersionKind
for _, group := range a.Groups {
for _, version := range a.Versions {
for _, kind := range a.Kinds {
gvk := schema.GroupVersionKind{
Group: group,
Version: version,
Kind: kind,
}
result = append(result, gvk)
}
}
}
return result
}
// Match selects objects to apply mutations to.
// +kubebuilder:object:generate=true
type Match struct {
Kinds []Kinds `json:"kinds,omitempty"`
Scope apiextensionsv1.ResourceScope `json:"scope,omitempty"`
Namespaces []string `json:"namespaces,omitempty"`
ExcludedNamespaces []string `json:"excludedNamespaces,omitempty"`
LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"`
NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty"`
}
// Kinds accepts a list of objects with apiGroups and kinds fields
// that list the groups/kinds of objects to which the mutation will apply.
// If multiple groups/kinds objects are specified,
// only one match is needed for the resource to be in scope.
// +kubebuilder:object:generate=true
type Kinds struct {
// APIGroups is the API groups the resources belong to. '*' is all groups.
// If '*' is present, the length of the slice must be one.
// Required.
APIGroups []string `json:"apiGroups,omitempty" protobuf:"bytes,1,rep,name=apiGroups"`
Kinds []string `json:"kinds,omitempty"`
}
// Matches verifies if the given object belonging to the given namespace
// matches the current mutator.
func Matches(match *Match, obj client.Object, ns *corev1.Namespace) (bool, error) {
if isNamespace(obj) && ns == nil {
return false, errors.New("invalid call to Matches(), ns must not be nil for Namespace objects")
}
foundMatch := false
for _, kk := range match.Kinds {
kindMatches := false
groupMatches := false
for _, k := range kk.Kinds {
if k == "*" || k == obj.GetObjectKind().GroupVersionKind().Kind {
kindMatches = true
break
}
}
if len(kk.Kinds) == 0 {
kindMatches = true
}
for _, g := range kk.APIGroups {
if g == "*" || g == obj.GetObjectKind().GroupVersionKind().Group {
groupMatches = true
break
}
}
if len(kk.APIGroups) == 0 {
groupMatches = true
}
if kindMatches && groupMatches {
foundMatch = true
}
}
if len(match.Kinds) == 0 {
foundMatch = true
}
if !foundMatch {
return false, nil
}
clusterScoped := ns == nil || isNamespace(obj)
if match.Scope == apiextensionsv1.ClusterScoped &&
!clusterScoped {
return false, nil
}
if match.Scope == apiextensionsv1.NamespaceScoped &&
clusterScoped {
return false, nil
}
if ns != nil {
found := false
for _, n := range match.Namespaces {
if ns.Name == n || prefixMatch(n, ns.Name) {
found = true
break
}
}
if !found && len(match.Namespaces) > 0 {
return false, nil
}
for _, n := range match.ExcludedNamespaces {
if ns.Name == n || prefixMatch(n, ns.Name) {
return false, nil
}
}
if match.LabelSelector != nil {
selector, err := metav1.LabelSelectorAsSelector(match.LabelSelector)
if err != nil {
return false, err
}
if !selector.Matches(labels.Set(obj.GetLabels())) {
return false, nil
}
}
if match.NamespaceSelector != nil {
selector, err := metav1.LabelSelectorAsSelector(match.NamespaceSelector)
if err != nil {
return false, err
}
switch {
case isNamespace(obj): // if the object is a namespace, namespace selector matches against the object
if !selector.Matches(labels.Set(obj.GetLabels())) {
return false, nil
}
case clusterScoped:
// cluster scoped, matches by default
case !selector.Matches(labels.Set(ns.Labels)):
return false, nil
}
}
}
return true, nil
}
// prefixMatch matches checks if the candidate contains the prefix defined in the source.
// The source is expected to end with a "*", which acts as a glob. It is removed when
// performing the prefix-based match.
func prefixMatch(source, candidate string) bool {
if !strings.HasSuffix(source, "*") {
return false
}
return strings.HasPrefix(candidate, strings.TrimSuffix(source, "*"))
}
// AppliesTo checks if any item the given slice of ApplyTo applies to the given object.
func AppliesTo(applyTo []ApplyTo, obj runtime.Object) bool {
gvk := obj.GetObjectKind().GroupVersionKind()
for _, apply := range applyTo {
matchesGroup := false
matchesVersion := false
matchesKind := false
for _, g := range apply.Groups {
if g == gvk.Group {
matchesGroup = true
break
}
}
for _, g := range apply.Versions {
if g == gvk.Version {
matchesVersion = true
break
}
}
for _, g := range apply.Kinds {
if g == gvk.Kind {
matchesKind = true
break
}
}
if matchesGroup &&
matchesVersion &&
matchesKind {
return true
}
}
return false
}
func isNamespace(obj runtime.Object) bool {
return obj.GetObjectKind().GroupVersionKind().Kind == "Namespace" &&
obj.GetObjectKind().GroupVersionKind().Group == ""
}