From a0d0d219baecd83833f8a4b773522ec01befb8fe Mon Sep 17 00:00:00 2001 From: Alexander Yastrebov Date: Tue, 26 Mar 2024 19:08:59 +0100 Subject: [PATCH] pkg/crd: support validating internal list items on list types For #342 Signed-off-by: Alexander Yastrebov --- pkg/crd/markers/validation.go | 19 ++++++++-- pkg/crd/testdata/cronjob_types.go | 24 +++++++++++++ .../testdata.kubebuilder.io_cronjobs.yaml | 36 +++++++++++++++++++ 3 files changed, 76 insertions(+), 3 deletions(-) diff --git a/pkg/crd/markers/validation.go b/pkg/crd/markers/validation.go index 48c89e6d0..ffac71339 100644 --- a/pkg/crd/markers/validation.go +++ b/pkg/crd/markers/validation.go @@ -288,6 +288,15 @@ func isIntegral(value float64) bool { return value == math.Trunc(value) && !math.IsNaN(value) && !math.IsInf(value, 0) } +func thisOrItemsSchema(schema *apiext.JSONSchemaProps) *apiext.JSONSchemaProps { + if schema.Type == "array" && + schema.Items != nil && + schema.Items.Schema != nil { + return schema.Items.Schema + } + return schema +} + // +controllertools:marker:generateHelp:category="CRD validation" // XValidation marks a field as requiring a value for which a given // expression evaluates to true. @@ -359,8 +368,9 @@ func (m MultipleOf) ApplyToSchema(schema *apiext.JSONSchemaProps) error { } func (m MaxLength) ApplyToSchema(schema *apiext.JSONSchemaProps) error { + schema = thisOrItemsSchema(schema) if schema.Type != "string" { - return fmt.Errorf("must apply maxlength to a string") + return fmt.Errorf("must apply maxlength to a string or a string array") } val := int64(m) schema.MaxLength = &val @@ -368,8 +378,9 @@ func (m MaxLength) ApplyToSchema(schema *apiext.JSONSchemaProps) error { } func (m MinLength) ApplyToSchema(schema *apiext.JSONSchemaProps) error { + schema = thisOrItemsSchema(schema) if schema.Type != "string" { - return fmt.Errorf("must apply minlength to a string") + return fmt.Errorf("must apply minlength to a string or a string array") } val := int64(m) schema.MinLength = &val @@ -380,8 +391,9 @@ func (m Pattern) ApplyToSchema(schema *apiext.JSONSchemaProps) error { // Allow string types or IntOrStrings. An IntOrString will still // apply the pattern validation when a string is detected, the pattern // will not apply to ints though. + schema = thisOrItemsSchema(schema) if schema.Type != "string" && !schema.XIntOrString { - return fmt.Errorf("must apply pattern to a `string` or `IntOrString`") + return fmt.Errorf("must apply pattern to a string or IntOrString or an array of them") } schema.Pattern = string(m) return nil @@ -510,6 +522,7 @@ func (m XEmbeddedResource) ApplyToSchema(schema *apiext.JSONSchemaProps) error { // which means the "XIntOrString" marker *must* be applied first. func (m XIntOrString) ApplyToSchema(schema *apiext.JSONSchemaProps) error { + schema = thisOrItemsSchema(schema) schema.XIntOrString = true return nil } diff --git a/pkg/crd/testdata/cronjob_types.go b/pkg/crd/testdata/cronjob_types.go index d36beeab2..06474903c 100644 --- a/pkg/crd/testdata/cronjob_types.go +++ b/pkg/crd/testdata/cronjob_types.go @@ -252,6 +252,24 @@ type CronJobSpec struct { // Checks that arrays work when the type contains a composite literal ArrayUsingCompositeLiteral [len(struct{ X [3]int }{}.X)]string `json:"arrayUsingCompositeLiteral,omitempty"` + + // This tests string slice item validation. + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=255 + // +kubebuilder:validation:Pattern=^[a-z0-9]([-a-z0-9]*[a-z0-9])?([.][a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + Hosts []string `json:"hosts,omitempty"` + + HostsAlias Hosts `json:"hostsAlias,omitempty"` + + // This tests string alias slice item validation. + LongerStringArray []LongerString `json:"longerStringArray,omitempty"` + + // This tests that a slice of IntOrString can also have a pattern attached to it. + // This can be useful if you want to limit the string to a perecentage or integer. + // The XIntOrString marker is a requirement for having a pattern on this type. + // +kubebuilder:validation:XIntOrString + // +kubebuilder:validation:Pattern="^((100|[0-9]{1,2})%|[0-9]+)$" + IntOrStringArrayWithAPattern []*intstr.IntOrString `json:"intOrStringArrayWithAPattern,omitempty"` } type ContainsNestedMap struct { @@ -360,6 +378,12 @@ type LongerString string // TotallyABool is a bool that serializes as a string. type TotallyABool bool +// This tests string array item validation. +// +kubebuilder:validation:MinLength=1 +// +kubebuilder:validation:MaxLength=255 +// +kubebuilder:validation:Pattern=^[a-z0-9]([-a-z0-9]*[a-z0-9])?([.][a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ +type Hosts []string + func (t TotallyABool) MarshalJSON() ([]byte, error) { if t { return []byte(`"true"`), nil diff --git a/pkg/crd/testdata/testdata.kubebuilder.io_cronjobs.yaml b/pkg/crd/testdata/testdata.kubebuilder.io_cronjobs.yaml index 83d11978b..6a9ae6f19 100644 --- a/pkg/crd/testdata/testdata.kubebuilder.io_cronjobs.yaml +++ b/pkg/crd/testdata/testdata.kubebuilder.io_cronjobs.yaml @@ -196,12 +196,40 @@ spec: description: This tests that exported fields are not skipped in the schema generation type: string + hosts: + description: This tests string slice item validation. + items: + maxLength: 255 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?([.][a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + type: array + hostsAlias: + description: This tests string array item validation. + items: + maxLength: 255 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?([.][a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + type: array int32WithValidations: format: int32 maximum: 2 minimum: -2 multipleOf: 2 type: integer + intOrStringArrayWithAPattern: + description: |- + This tests that a slice of IntOrString can also have a pattern attached to it. + This can be useful if you want to limit the string to a perecentage or integer. + The XIntOrString marker is a requirement for having a pattern on this type. + items: + anyOf: + - type: integer + - type: string + pattern: ^((100|[0-9]{1,2})%|[0-9]+)$ + x-kubernetes-int-or-string: true + type: array intOrStringWithAPattern: anyOf: - type: integer @@ -6609,6 +6637,14 @@ spec: - bar - foo type: object + longerStringArray: + description: This tests string alias slice item validation. + items: + description: This tests that markers that are allowed on both fields + and types are applied to types + minLength: 4 + type: string + type: array mapOfArraysOfFloats: additionalProperties: items: