You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: GUIDELINES.md
+70Lines changed: 70 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -19,6 +19,7 @@ followed for Istio APIs.
19
19
-[Proto Guidelines](#proto-guidelines)
20
20
-[Style](#style)
21
21
-[Basic Proto Versioning](#basic-proto-ersioning)
22
+
-[Validation Guidelines](#validation-guidelines)
22
23
-[CRD Guidelines](#crd-guidelines)
23
24
-[Style](#crd-style)
24
25
-[Basic CRD Versioning](#basic-crd-versioning)
@@ -214,6 +215,75 @@ protos.
214
215
215
216
- Loosening validation is permitted. As a result, it is recommended to err on the side of stricter validation.
216
217
218
+
## Validation Guidelines
219
+
220
+
All types should have as strict validation specified on it as possible to rule out invalid states.
221
+
These are ultimately compiled to Kubernetes CustomResourceDefinitions, which use OpenAPI validation with some Kubernetes extras.
222
+
This is handled by our own custom [protoc-gen-crd](https://github.com/istio/tools/tree/master/cmd/protoc-gen-crd) which compiles our
223
+
protobuf definitions down to CRDs.
224
+
225
+
There are a few types of validations:
226
+
* Automatic ones, based on the protobuf type. For example, a UInt32Value automatically has a validation to check the number between `0` and `MaxUint32`
227
+
* Protobuf `field_behavior`. Currently only `[(google.api.field_behavior) = REQUIRED]` is implemented.
228
+
* Comment driven validations (see below).
229
+
230
+
Most validation is driven by comments on fields and messages.
231
+
All validations in [KubeBuilder](https://book.kubebuilder.io/reference/markers/crd-validation) are supported, as well as some extras:
232
+
233
+
- `+protoc-gen-crd:map-value-validation`: apply the validation to each *value* in a map.
234
+
Note it's not possible to apply validations to each key. You can, however, validate the entire map together with a CEL rule.
235
+
- `+protoc-gen-crd:list-value-validation`: apply the validation to each value in a list.
236
+
- `+protoc-gen-crd:duration-validation:none`: exclude the default requirement that a duration field is non-zero.
237
+
- `+protoc-gen-crd:validation:XIntOrString`: marks a field as accepting integers or strings.
238
+
- `+protoc-gen-crd:validation:IgnoreSubValidation`: if referencing a message in a field, and that message has some validation on it already, exclude the listed validations.
239
+
This is uncommon, but can be used when referencing a message in a certain context has different rules than others.
[CEL](https://cel.dev/) is a small language that allows us to write expressions to represent validation logic.
249
+
This comes with a lot of quirks!
250
+
251
+
Useful tools and references:
252
+
* [CEL playground](https://playcel.undistro.io/) allows an easy way to run CEL expressions against some types.
253
+
* [Kubernetes CEL docs](https://kubernetes.io/docs/reference/using-api/cel/).
254
+
* [CEL language definition](https://github.com/google/cel-spec/blob/master/doc/langdef.md).
255
+
256
+
The biggest challenge with CEL is the complexity limit imposed by Kubernetes.
257
+
This estimates the cost to run the function, and rejects it if it is too high.
258
+
This takes into account the cost of a function and the cost of *potential* inputs.
259
+
This makes it, typically, required to put maximum size bounds on items.
260
+
261
+
Kubernetes changes version-to-version on how it estimates cost (usually getting more lenient) and what functions are available.
262
+
We want to target the oldest version for compatibility purposes.
263
+
Our tests do not currently cover this (a prototype of doing so can be found [here](https://github.com/istio/api/pull/3275)).
264
+
A list of what features are in which versions can be found [here](https://kubernetes.io/docs/reference/using-api/cel/#cel-options-language-features-and-libraries).
265
+
266
+
Istio has some custom macros that are expanded at compile time, driven by the [celpp](https://github.com/howardjohn/celpp) package.
267
+
This extends CEL with these capabilities:
268
+
* **default**. Usage: `default(self.x, 'DEF')`.
269
+
* **oneof**. Usage: `oneof(self.x, self.y, self.z)`. This checks that 0 or 1 of these fields is set.
270
+
* **index**. Usage: `self.index({}, x, z, b)`. This does `self.x.z.b` and returns `{}` if any of these is not set.
271
+
272
+
Unlike typical Go usage, CEL does not have a concept of zero values for unset fields.
273
+
As a result, an optional field needs special care.
274
+
Do not write `self.fruit == 'apple'`, for instance, write `default(self.fruit, '') == 'apple'.
275
+
276
+
### Testing
277
+
278
+
As validation logic is really easy to get wrong, it's useful to write tests.
279
+
This is done by adding YAML files under `tests/testdata`.
280
+
Each type has a `valid` and `invalid` file to do positive and negative cases.
281
+
282
+
Aside from explicitly testing these, these also form the seed corpus for fuzzing when these are pulled into `istio/istio`.
283
+
This fuzz testing verifies the CRD validation has the same result as the webhook (Golang) validation code.
284
+
Currently, this mostly serves to ensure we do not make something overly strict.
285
+
In the future, it may show us that its safe to disable the webhook entirely, if CRD validation can cover the full validation surface.
// +kubebuilder:validation:XValidation:message="only one of targetRefs or selector can be set",rule="(has(self.selector)?1:0)+(has(self.targetRef)?1:0)+(has(self.targetRefs)?1:0)<=1"
239
+
// +kubebuilder:validation:XValidation:message="only one of targetRefs or selector can be set",rule="oneof(self.selector, self.targetRef, self.targetRefs)"
240
240
messageWasmPlugin {
241
241
// Criteria used to select the specific set of pods/VMs on which
242
242
// this plugin configuration should be applied. If omitted, this
@@ -462,7 +462,7 @@ message VmConfig {
462
462
repeatedEnvVarenv=1;
463
463
}
464
464
465
-
// +kubebuilder:validation:XValidation:message="value may only be set when valueFrom is INLINE",rule="(has(self.valueFrom) ? self.valueFrom : '') != 'HOST' || !has(self.value)"
465
+
// +kubebuilder:validation:XValidation:message="value may only be set when valueFrom is INLINE",rule="default(self.valueFrom, '') != 'HOST' || !has(self.value)"
0 commit comments