@@ -427,6 +427,8 @@ type VariableSchema struct {
427427 OpenAPIV3Schema JSONSchemaProps `json:"openAPIV3Schema"`
428428}
429429
430+ // Adapted from https://github.com/kubernetes/apiextensions-apiserver/blob/v0.28.5/pkg/apis/apiextensions/v1/types_jsonschema.go#L40
431+
430432// JSONSchemaProps is a JSON-Schema following Specification Draft 4 (http://json-schema.org/).
431433// This struct has been initially copied from apiextensionsv1.JSONSchemaProps, but all fields
432434// which are not supported in CAPI have been removed.
@@ -461,6 +463,16 @@ type JSONSchemaProps struct {
461463 // +kubebuilder:validation:Schemaless
462464 AdditionalProperties * JSONSchemaProps `json:"additionalProperties,omitempty"`
463465
466+ // MaxProperties is the maximum amount of entries in a map or properties in an object.
467+ // NOTE: Can only be set if type is object.
468+ // +optional
469+ MaxProperties * int64 `json:"maxProperties,omitempty"`
470+
471+ // MinProperties is the minimum amount of entries in a map or properties in an object.
472+ // NOTE: Can only be set if type is object.
473+ // +optional
474+ MinProperties * int64 `json:"minProperties,omitempty"`
475+
464476 // Required specifies which fields of an object are required.
465477 // NOTE: Can only be set if type is object.
466478 // +optional
@@ -551,7 +563,123 @@ type JSONSchemaProps struct {
551563 // NOTE: Can be set for all types.
552564 // +optional
553565 Default * apiextensionsv1.JSON `json:"default,omitempty"`
554- }
566+
567+ // XValidations describes a list of validation rules written in the CEL expression language.
568+ // +optional
569+ // +listType=map
570+ // +listMapKey=rule
571+ XValidations []ValidationRule `json:"x-kubernetes-validations,omitempty"`
572+ }
573+
574+ // ValidationRule describes a validation rule written in the CEL expression language.
575+ type ValidationRule struct {
576+ // Rule represents the expression which will be evaluated by CEL.
577+ // ref: https://github.com/google/cel-spec
578+ // The Rule is scoped to the location of the x-kubernetes-validations extension in the schema.
579+ // The `self` variable in the CEL expression is bound to the scoped value.
580+ // If the Rule is scoped to an object with properties, the accessible properties of the object are field selectable
581+ // via `self.field` and field presence can be checked via `has(self.field)`.
582+ // If the Rule is scoped to an object with additionalProperties (i.e. a map) the value of the map
583+ // are accessible via `self[mapKey]`, map containment can be checked via `mapKey in self` and all entries of the map
584+ // are accessible via CEL macros and functions such as `self.all(...)`.
585+ // If the Rule is scoped to an array, the elements of the array are accessible via `self[i]` and also by macros and
586+ // functions.
587+ // If the Rule is scoped to a scalar, `self` is bound to the scalar value.
588+ // Examples:
589+ // - Rule scoped to a map of objects: {"rule": "self.components['Widget'].priority < 10"}
590+ // - Rule scoped to a list of integers: {"rule": "self.values.all(value, value >= 0 && value < 100)"}
591+ // - Rule scoped to a string value: {"rule": "self.startsWith('kube')"}
592+ //
593+ // Unknown data preserved in custom resources via x-kubernetes-preserve-unknown-fields is not accessible in CEL
594+ // expressions. This includes:
595+ // - Unknown field values that are preserved by object schemas with x-kubernetes-preserve-unknown-fields.
596+ // - Object properties where the property schema is of an "unknown type". An "unknown type" is recursively defined as:
597+ // - A schema with no type and x-kubernetes-preserve-unknown-fields set to true
598+ // - An array where the items schema is of an "unknown type"
599+ // - An object where the additionalProperties schema is of an "unknown type"
600+ //
601+ // Only property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible.
602+ // Accessible property names are escaped according to the following rules when accessed in the expression:
603+ // - '__' escapes to '__underscores__'
604+ // - '.' escapes to '__dot__'
605+ // - '-' escapes to '__dash__'
606+ // - '/' escapes to '__slash__'
607+ // - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:
608+ // "true", "false", "null", "in", "as", "break", "const", "continue", "else", "for", "function", "if",
609+ // "import", "let", "loop", "package", "namespace", "return".
610+ // Examples:
611+ // - Rule accessing a property named "namespace": {"rule": "self.__namespace__ > 0"}
612+ // - Rule accessing a property named "x-prop": {"rule": "self.x__dash__prop > 0"}
613+ // - Rule accessing a property named "redact__d": {"rule": "self.redact__underscores__d > 0"}
614+ //
615+ //
616+ // If `rule` makes use of the `oldSelf` variable it is implicitly a
617+ // `transition rule`.
618+ //
619+ // By default, the `oldSelf` variable is the same type as `self`.
620+ //
621+ // Transition rules by default are applied only on UPDATE requests and are
622+ // skipped if an old value could not be found.
623+ //
624+ // +kubebuilder:validation:Required
625+ Rule string `json:"rule"`
626+ // Message represents the message displayed when validation fails. The message is required if the Rule contains
627+ // line breaks. The message must not contain line breaks.
628+ // If unset, the message is "failed rule: {Rule}".
629+ // e.g. "must be a URL with the host matching spec.host"
630+ // +optional
631+ Message string `json:"message,omitempty"`
632+ // MessageExpression declares a CEL expression that evaluates to the validation failure message that is returned when this rule fails.
633+ // Since messageExpression is used as a failure message, it must evaluate to a string.
634+ // If both message and messageExpression are present on a rule, then messageExpression will be used if validation
635+ // fails. If messageExpression results in a runtime error, the validation failure message is produced
636+ // as if the messageExpression field were unset. If messageExpression evaluates to an empty string, a string with only spaces, or a string
637+ // that contains line breaks, then the validation failure message will also be produced as if the messageExpression field were unset.
638+ // messageExpression has access to all the same variables as the rule; the only difference is the return type.
639+ // Example:
640+ // "x must be less than max ("+string(self.max)+")"
641+ // +optional
642+ MessageExpression string `json:"messageExpression,omitempty"`
643+ // Reason provides a machine-readable validation failure reason that is returned to the caller when a request fails this validation rule.
644+ // The currently supported reasons are: "FieldValueInvalid", "FieldValueForbidden", "FieldValueRequired", "FieldValueDuplicate".
645+ // If not set, default to use "FieldValueInvalid".
646+ // All future added reasons must be accepted by clients when reading this value and unknown reasons should be treated as FieldValueInvalid.
647+ // +optional
648+ // +kubebuilder:validation:Enum=FieldValueInvalid;FieldValueForbidden;FieldValueRequired;FieldValueDuplicate
649+ // +kubebuilder:default=FieldValueInvalid
650+ // +default=ref(sigs.k8s.io/cluster-api/api/v1beta1.FieldValueInvalid)
651+ Reason FieldValueErrorReason `json:"reason,omitempty"`
652+ // FieldPath represents the field path returned when the validation fails.
653+ // It must be a relative JSON path (i.e. with array notation) scoped to the location of this x-kubernetes-validations extension in the schema and refer to an existing field.
654+ // e.g. when validation checks if a specific attribute `foo` under a map `testMap`, the fieldPath could be set to `.testMap.foo`
655+ // If the validation checks two lists must have unique attributes, the fieldPath could be set to either of the list: e.g. `.testList`
656+ // It does not support list numeric index.
657+ // It supports child operation to refer to an existing field currently. Refer to [JSONPath support in Kubernetes](https://kubernetes.io/docs/reference/kubectl/jsonpath/) for more info.
658+ // Numeric index of array is not supported.
659+ // For field name which contains special characters, use `['specialName']` to refer the field name.
660+ // e.g. for attribute `foo.34$` appears in a list `testList`, the fieldPath could be set to `.testList['foo.34$']`
661+ // +optional
662+ FieldPath string `json:"fieldPath,omitempty"`
663+ }
664+
665+ // FieldValueErrorReason is a machine-readable value providing more detail about why a field failed the validation.
666+ type FieldValueErrorReason string
667+
668+ const (
669+ // FieldValueRequired is used to report required values that are not
670+ // provided (e.g. empty strings, null values, or empty arrays).
671+ FieldValueRequired FieldValueErrorReason = "FieldValueRequired"
672+ // FieldValueDuplicate is used to report collisions of values that must be
673+ // unique (e.g. unique IDs).
674+ FieldValueDuplicate FieldValueErrorReason = "FieldValueDuplicate"
675+ // FieldValueInvalid is used to report malformed values (e.g. failed regex
676+ // match, too long, out of bounds).
677+ FieldValueInvalid FieldValueErrorReason = "FieldValueInvalid"
678+ // FieldValueForbidden is used to report valid (as per formatting rules)
679+ // values which would be accepted under some conditions, but which are not
680+ // permitted by the current conditions (such as security policy).
681+ FieldValueForbidden FieldValueErrorReason = "FieldValueForbidden"
682+ )
555683
556684// ClusterClassPatch defines a patch which is applied to customize the referenced templates.
557685type ClusterClassPatch struct {
0 commit comments