-
Notifications
You must be signed in to change notification settings - Fork 858
/
numeric.go
218 lines (204 loc) · 7.93 KB
/
numeric.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
package operator
import (
"fmt"
"strconv"
"github.com/blang/semver/v4"
"github.com/go-logr/logr"
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/engine/context"
"k8s.io/apimachinery/pkg/api/resource"
)
//NewNumericOperatorHandler returns handler to manage the provided numeric operations (>, >=, <=, <)
func NewNumericOperatorHandler(log logr.Logger, ctx context.EvalInterface, op kyverno.ConditionOperator) OperatorHandler {
return NumericOperatorHandler{
ctx: ctx,
log: log,
condition: op,
}
}
//NumericOperatorHandler provides implementation to handle Numeric Operations associated with policies
type NumericOperatorHandler struct {
ctx context.EvalInterface
log logr.Logger
condition kyverno.ConditionOperator
}
// compareByCondition compares a float64 key with a float64 value on the basis of the provided operator
func compareByCondition(key float64, value float64, op kyverno.ConditionOperator, log logr.Logger) bool {
switch op {
case kyverno.ConditionOperators["GreaterThanOrEquals"]:
return key >= value
case kyverno.ConditionOperators["GreaterThan"]:
return key > value
case kyverno.ConditionOperators["LessThanOrEquals"]:
return key <= value
case kyverno.ConditionOperators["LessThan"]:
return key < value
default:
log.Info(fmt.Sprintf("Expected operator, one of [GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, Equals, NotEquals], found %s", op))
return false
}
}
func compareVersionByCondition(key semver.Version, value semver.Version, op kyverno.ConditionOperator, log logr.Logger) bool {
switch op {
case kyverno.ConditionOperators["GreaterThanOrEquals"]:
return key.GTE(value)
case kyverno.ConditionOperators["GreaterThan"]:
return key.GT(value)
case kyverno.ConditionOperators["LessThanOrEquals"]:
return key.LTE(value)
case kyverno.ConditionOperators["LessThan"]:
return key.LT(value)
default:
log.Info(fmt.Sprintf("Expected operator, one of [GreaterThanOrEquals, GreaterThan, LessThanOrEquals, LessThan, Equals, NotEquals], found %s", op))
return false
}
}
func (noh NumericOperatorHandler) Evaluate(key, value interface{}) bool {
switch typedKey := key.(type) {
case int:
return noh.validateValueWithIntPattern(int64(typedKey), value)
case int64:
return noh.validateValueWithIntPattern(typedKey, value)
case float64:
return noh.validateValueWithFloatPattern(typedKey, value)
case string:
return noh.validateValueWithStringPattern(typedKey, value)
default:
noh.log.Info("Unsupported type", "value", typedKey, "type", fmt.Sprintf("%T", typedKey))
return false
}
}
func (noh NumericOperatorHandler) validateValueWithIntPattern(key int64, value interface{}) bool {
switch typedValue := value.(type) {
case int:
return compareByCondition(float64(key), float64(typedValue), noh.condition, noh.log)
case int64:
return compareByCondition(float64(key), float64(typedValue), noh.condition, noh.log)
case float64:
return compareByCondition(float64(key), typedValue, noh.condition, noh.log)
case string:
durationKey, durationValue, err := parseDuration(key, value)
if err == nil {
return compareByCondition(float64(durationKey.Seconds()), float64(durationValue.Seconds()), noh.condition, noh.log)
}
// extract float64 and (if that fails) then, int64 from the string
float64val, err := strconv.ParseFloat(typedValue, 64)
if err == nil {
return compareByCondition(float64(key), float64val, noh.condition, noh.log)
}
int64val, err := strconv.ParseInt(typedValue, 10, 64)
if err == nil {
return compareByCondition(float64(key), float64(int64val), noh.condition, noh.log)
}
noh.log.Error(fmt.Errorf("parse error: "), "Failed to parse both float64 and int64 from the string value")
return false
default:
noh.log.Info("Expected type int", "value", value, "type", fmt.Sprintf("%T", value))
return false
}
}
func (noh NumericOperatorHandler) validateValueWithFloatPattern(key float64, value interface{}) bool {
switch typedValue := value.(type) {
case int:
return compareByCondition(key, float64(typedValue), noh.condition, noh.log)
case int64:
return compareByCondition(key, float64(typedValue), noh.condition, noh.log)
case float64:
return compareByCondition(key, typedValue, noh.condition, noh.log)
case string:
durationKey, durationValue, err := parseDuration(key, value)
if err == nil {
return compareByCondition(float64(durationKey.Seconds()), float64(durationValue.Seconds()), noh.condition, noh.log)
}
float64val, err := strconv.ParseFloat(typedValue, 64)
if err == nil {
return compareByCondition(key, float64val, noh.condition, noh.log)
}
int64val, err := strconv.ParseInt(typedValue, 10, 64)
if err == nil {
return compareByCondition(key, float64(int64val), noh.condition, noh.log)
}
noh.log.Error(fmt.Errorf("parse error: "), "Failed to parse both float64 and int64 from the string value")
return false
default:
noh.log.Info("Expected type float", "value", value, "type", fmt.Sprintf("%T", value))
return false
}
}
func (noh NumericOperatorHandler) validateValueWithVersionPattern(key semver.Version, value interface{}) bool {
switch typedValue := value.(type) {
case string:
versionValue, err := semver.Parse(typedValue)
if err != nil {
noh.log.Error(fmt.Errorf("parse error: "), "Failed to parse value type doesn't match key type")
return false
}
return compareVersionByCondition(key, versionValue, noh.condition, noh.log)
default:
noh.log.Info("Expected type string", "value", value, "type", fmt.Sprintf("%T", value))
return false
}
}
func (noh NumericOperatorHandler) validateValueWithStringPattern(key string, value interface{}) bool {
// We need to check duration first as it's the only type that can be compared to a different type
durationKey, durationValue, err := parseDuration(key, value)
if err == nil {
return compareByCondition(float64(durationKey.Seconds()), float64(durationValue.Seconds()), noh.condition, noh.log)
}
// attempt to extract resource quantity from string before parsing floats/ints as resources can also be ints/floats represented as string type
resourceKey, resourceValue, err := parseQuantity(key, value)
if err == nil {
return compareByCondition(float64(resourceKey.Cmp(resourceValue)), 0, noh.condition, noh.log)
}
// extracting float64 from the string key
float64key, err := strconv.ParseFloat(key, 64)
if err == nil {
return noh.validateValueWithFloatPattern(float64key, value)
}
// extracting int64 from the string because float64 extraction failed
int64key, err := strconv.ParseInt(key, 10, 64)
if err == nil {
return noh.validateValueWithIntPattern(int64key, value)
}
// attempt to extract version from string
versionKey, err := semver.Parse(key)
if err == nil {
return noh.validateValueWithVersionPattern(versionKey, value)
}
noh.log.Error(err, "Failed to parse from the string key, value is not float, int nor resource quantity")
return false
}
func parseQuantity(key, value interface{}) (parsedKey, parsedValue resource.Quantity, err error) {
switch typedKey := key.(type) {
case string:
parsedKey, err = resource.ParseQuantity(typedKey)
if err != nil {
return
}
default:
err = fmt.Errorf("key is not a quantity")
return
}
switch typedValue := value.(type) {
case string:
parsedValue, err = resource.ParseQuantity(typedValue)
if err != nil {
return
}
default:
err = fmt.Errorf("value is not a quantity")
return
}
return
}
// the following functions are unreachable because the key is strictly supposed to be numeric
// still the following functions are just created to make NumericOperatorHandler struct implement OperatorHandler interface
func (noh NumericOperatorHandler) validateValueWithBoolPattern(key bool, value interface{}) bool {
return false
}
func (noh NumericOperatorHandler) validateValueWithMapPattern(key map[string]interface{}, value interface{}) bool {
return false
}
func (noh NumericOperatorHandler) validateValueWithSlicePattern(key []interface{}, value interface{}) bool {
return false
}