-
Notifications
You must be signed in to change notification settings - Fork 876
/
pattern.go
323 lines (289 loc) · 9.39 KB
/
pattern.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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
package common
import (
"fmt"
"math"
"regexp"
"strconv"
"strings"
"github.com/go-logr/logr"
"github.com/kyverno/kyverno/pkg/engine/operator"
wildcard "github.com/kyverno/kyverno/pkg/utils/wildcard"
apiresource "k8s.io/apimachinery/pkg/api/resource"
)
type quantity int
const (
equal quantity = 0
lessThan quantity = -1
greaterThan quantity = 1
)
// ValidateValueWithPattern validates value with operators and wildcards
func ValidateValueWithPattern(log logr.Logger, value, pattern interface{}) bool {
switch typedPattern := pattern.(type) {
case bool:
typedValue, ok := value.(bool)
if !ok {
log.V(4).Info("Expected type bool", "type", fmt.Sprintf("%T", value), "value", value)
return false
}
return typedPattern == typedValue
case int:
return validateValueWithIntPattern(log, value, int64(typedPattern))
case int64:
return validateValueWithIntPattern(log, value, typedPattern)
case float64:
return validateValueWithFloatPattern(log, value, typedPattern)
case string:
return validateValueWithStringPatterns(log, value, typedPattern)
case nil:
return validateValueWithNilPattern(log, value)
case map[string]interface{}:
return validateValueWithMapPattern(log, value, typedPattern)
case []interface{}:
log.V(2).Info("arrays are not supported as patterns")
return false
default:
log.V(2).Info("Unknown type", "type", fmt.Sprintf("%T", typedPattern), "value", typedPattern)
return false
}
}
func validateValueWithMapPattern(log logr.Logger, value interface{}, typedPattern map[string]interface{}) bool {
// verify the type of the resource value is map[string]interface,
// we only check for existence of object, not the equality of content and value
_, ok := value.(map[string]interface{})
if !ok {
log.V(2).Info("Expected type map[string]interface{}", "type", fmt.Sprintf("%T", value), "value", value)
return false
}
return true
}
// Handler for int values during validation process
func validateValueWithIntPattern(log logr.Logger, value interface{}, pattern int64) bool {
switch typedValue := value.(type) {
case int:
return int64(typedValue) == pattern
case int64:
return typedValue == pattern
case float64:
// check that float has no fraction
if typedValue == math.Trunc(typedValue) {
return int64(typedValue) == pattern
}
log.V(2).Info("Expected type int", "type", fmt.Sprintf("%T", typedValue), "value", typedValue)
return false
case string:
// extract int64 from string
int64Num, err := strconv.ParseInt(typedValue, 10, 64)
if err != nil {
log.Error(err, "Failed to parse int64 from string")
return false
}
return int64Num == pattern
default:
log.V(2).Info("Expected type int", "type", fmt.Sprintf("%T", value), "value", value)
return false
}
}
// Handler for float values during validation process
func validateValueWithFloatPattern(log logr.Logger, value interface{}, pattern float64) bool {
switch typedValue := value.(type) {
case int:
// check that float has no fraction
if pattern == math.Trunc(pattern) {
return int(pattern) == value
}
log.V(2).Info("Expected type float", "type", fmt.Sprintf("%T", typedValue), "value", typedValue)
return false
case int64:
// check that float has no fraction
if pattern == math.Trunc(pattern) {
return int64(pattern) == value
}
log.V(2).Info("Expected type float", "type", fmt.Sprintf("%T", typedValue), "value", typedValue)
return false
case float64:
return typedValue == pattern
case string:
// extract float64 from string
float64Num, err := strconv.ParseFloat(typedValue, 64)
if err != nil {
log.Error(err, "Failed to parse float64 from string")
return false
}
return float64Num == pattern
default:
log.V(2).Info("Expected type float", "type", fmt.Sprintf("%T", value), "value", value)
return false
}
}
// Handler for nil values during validation process
func validateValueWithNilPattern(log logr.Logger, value interface{}) bool {
switch typed := value.(type) {
case float64:
return typed == 0.0
case int:
return typed == 0
case int64:
return typed == 0
case string:
return typed == ""
case bool:
return !typed
case nil:
return true
case map[string]interface{}, []interface{}:
log.V(2).Info("Maps and arrays could not be checked with nil pattern")
return false
default:
log.V(2).Info("Unknown type as value when checking for nil pattern", "type", fmt.Sprintf("%T", value), "value", value)
return false
}
}
// Handler for pattern values during validation process
func validateValueWithStringPatterns(log logr.Logger, value interface{}, pattern string) bool {
if value == pattern {
return true
}
conditions := strings.Split(pattern, "|")
for _, condition := range conditions {
condition = strings.Trim(condition, " ")
if checkForAndConditionsAndValidate(log, value, condition) {
return true
}
}
return false
}
func checkForAndConditionsAndValidate(log logr.Logger, value interface{}, pattern string) bool {
conditions := strings.Split(pattern, "&")
for _, condition := range conditions {
condition = strings.Trim(condition, " ")
if !validateValueWithStringPattern(log, value, condition) {
return false
}
}
return true
}
// Handler for single pattern value during validation process
// Detects if pattern has a number
func validateValueWithStringPattern(log logr.Logger, value interface{}, pattern string) bool {
operatorVariable := operator.GetOperatorFromStringPattern(pattern)
// Upon encountering InRange operator split the string by `-` and basically
// verify the result of (x >= leftEndpoint & x <= rightEndpoint)
if operatorVariable == operator.InRange {
endpoints := strings.Split(pattern, "-")
leftEndpoint, rightEndpoint := endpoints[0], endpoints[1]
gt := validateValueWithStringPattern(log, value, fmt.Sprintf(">=%s", leftEndpoint))
if !gt {
return false
}
pattern = fmt.Sprintf("<=%s", rightEndpoint)
operatorVariable = operator.LessEqual
}
// Upon encountering NotInRange operator split the string by `!-` and basically
// verify the result of (x < leftEndpoint | x > rightEndpoint)
if operatorVariable == operator.NotInRange {
endpoints := strings.Split(pattern, "!-")
leftEndpoint, rightEndpoint := endpoints[0], endpoints[1]
lt := validateValueWithStringPattern(log, value, fmt.Sprintf("<%s", leftEndpoint))
if lt {
return true
}
pattern = fmt.Sprintf(">%s", rightEndpoint)
operatorVariable = operator.More
}
pattern = pattern[len(operatorVariable):]
pattern = strings.TrimSpace(pattern)
number, str := getNumberAndStringPartsFromPattern(pattern)
if number == "" {
return validateString(log, value, str, operatorVariable)
}
return validateNumberWithStr(log, value, pattern, operatorVariable)
}
// Handler for string values
func validateString(log logr.Logger, value interface{}, pattern string, operatorVariable operator.Operator) bool {
if operator.NotEqual == operatorVariable || operator.Equal == operatorVariable {
var strValue string
var ok bool = false
switch v := value.(type) {
case float64:
strValue = strconv.FormatFloat(v, 'E', -1, 64)
ok = true
case int:
strValue = strconv.FormatInt(int64(v), 10)
ok = true
case int64:
strValue = strconv.FormatInt(v, 10)
ok = true
case string:
strValue = v
ok = true
case bool:
strValue = strconv.FormatBool(v)
ok = true
case nil:
ok = false
}
if !ok {
log.V(4).Info("unexpected type", "got", value, "expect", pattern)
return false
}
wildcardResult := wildcard.Match(pattern, strValue)
if operator.NotEqual == operatorVariable {
return !wildcardResult
}
return wildcardResult
}
log.V(2).Info("Operators >, >=, <, <= are not applicable to strings")
return false
}
// validateNumberWithStr compares quantity if pattern type is quantity
// or a wildcard match to pattern string
func validateNumberWithStr(log logr.Logger, value interface{}, pattern string, operator operator.Operator) bool {
typedValue, err := convertNumberToString(value)
if err != nil {
log.Error(err, "failed to convert to string")
return false
}
patternQuan, err := apiresource.ParseQuantity(pattern)
// 1. nil error - quantity comparison
if err == nil {
valueQuan, err := apiresource.ParseQuantity(typedValue)
if err != nil {
log.Error(err, "invalid quantity in resource", "type", fmt.Sprintf("%T", typedValue), "value", typedValue)
return false
}
return compareQuantity(valueQuan, patternQuan, operator)
}
// 2. wildcard match
if validateString(log, value, pattern, operator) {
return true
} else {
log.V(4).Info("value failed wildcard check", "type", fmt.Sprintf("%T", typedValue), "value", typedValue, "check", pattern)
return false
}
}
func compareQuantity(value, pattern apiresource.Quantity, op operator.Operator) bool {
result := value.Cmp(pattern)
switch op {
case operator.Equal:
return result == int(equal)
case operator.NotEqual:
return result != int(equal)
case operator.More:
return result == int(greaterThan)
case operator.Less:
return result == int(lessThan)
case operator.MoreEqual:
return (result == int(equal)) || (result == int(greaterThan))
case operator.LessEqual:
return (result == int(equal)) || (result == int(lessThan))
}
return false
}
// detects numerical and string parts in pattern and returns them
func getNumberAndStringPartsFromPattern(pattern string) (number, str string) {
regexpStr := `^(\d*(\.\d+)?)(.*)`
re := regexp.MustCompile(regexpStr)
matches := re.FindAllStringSubmatch(pattern, -1)
match := matches[0]
return match[1], match[3]
}