github.com/christarazi/controller-tools@v0.3.1-0.20210907042920-aa94049173f8/pkg/crd/markers/validation.go (about)

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package markers
    18  
    19  import (
    20  	"fmt"
    21  
    22  	"encoding/json"
    23  
    24  	apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    25  
    26  	"sigs.k8s.io/controller-tools/pkg/markers"
    27  )
    28  
    29  // ValidationMarkers lists all available markers that affect CRD schema generation,
    30  // except for the few that don't make sense as type-level markers (see FieldOnlyMarkers).
    31  // All markers start with `+kubebuilder:validation:`, and continue with their type name.
    32  // A copy is produced of all markers that describes types as well, for making types
    33  // reusable and writing complex validations on slice items.
    34  var ValidationMarkers = mustMakeAllWithPrefix("kubebuilder:validation", markers.DescribesField,
    35  
    36  	// integer markers
    37  
    38  	Maximum(0),
    39  	Minimum(0),
    40  	ExclusiveMaximum(false),
    41  	ExclusiveMinimum(false),
    42  	MultipleOf(0),
    43  
    44  	// string markers
    45  
    46  	MaxLength(0),
    47  	MinLength(0),
    48  	Pattern(""),
    49  
    50  	// slice markers
    51  
    52  	MaxItems(0),
    53  	MinItems(0),
    54  	UniqueItems(false),
    55  
    56  	// general markers
    57  
    58  	Enum(nil),
    59  	Format(""),
    60  	Type(""),
    61  	XPreserveUnknownFields{},
    62  	XEmbeddedResource{},
    63  )
    64  
    65  // FieldOnlyMarkers list field-specific validation markers (i.e. those markers that don't make
    66  // sense on a type, and thus aren't in ValidationMarkers).
    67  var FieldOnlyMarkers = []*definitionWithHelp{
    68  	must(markers.MakeDefinition("kubebuilder:validation:Required", markers.DescribesField, struct{}{})).
    69  		WithHelp(markers.SimpleHelp("CRD validation", "specifies that this field is required, if fields are optional by default.")),
    70  	must(markers.MakeDefinition("kubebuilder:validation:Optional", markers.DescribesField, struct{}{})).
    71  		WithHelp(markers.SimpleHelp("CRD validation", "specifies that this field is optional, if fields are required by default.")),
    72  	must(markers.MakeDefinition("optional", markers.DescribesField, struct{}{})).
    73  		WithHelp(markers.SimpleHelp("CRD validation", "specifies that this field is optional, if fields are required by default.")),
    74  
    75  	must(markers.MakeDefinition("kubebuilder:validation:OneOf", markers.DescribesField, struct{}{})).
    76  		WithHelp(markers.SimpleHelp("CRD validation", "specifies that this field is part of a oneOf group")),
    77  	must(markers.MakeDefinition("kubebuilder:validation:AnyOf", markers.DescribesField, struct{}{})).
    78  		WithHelp(markers.SimpleHelp("CRD validation", "specifies that this field is part of a anyOf group")),
    79  
    80  	must(markers.MakeDefinition("nullable", markers.DescribesField, Nullable{})).
    81  		WithHelp(Nullable{}.Help()),
    82  
    83  	must(markers.MakeAnyTypeDefinition("kubebuilder:default", markers.DescribesField, Default{})).
    84  		WithHelp(Default{}.Help()),
    85  
    86  	must(markers.MakeDefinition("kubebuilder:pruning:PreserveUnknownFields", markers.DescribesField, XPreserveUnknownFields{})).
    87  		WithHelp(XPreserveUnknownFields{}.Help()),
    88  	must(markers.MakeDefinition("kubebuilder:validation:EmbeddedResource", markers.DescribesField, XEmbeddedResource{})).
    89  		WithHelp(XEmbeddedResource{}.Help()),
    90  }
    91  
    92  func init() {
    93  	AllDefinitions = append(AllDefinitions, ValidationMarkers...)
    94  
    95  	for _, def := range ValidationMarkers {
    96  		newDef := *def.Definition
    97  		// copy both parts so we don't change the definition
    98  		typDef := definitionWithHelp{
    99  			Definition: &newDef,
   100  			Help:       def.Help,
   101  		}
   102  		typDef.Target = markers.DescribesType
   103  		AllDefinitions = append(AllDefinitions, &typDef)
   104  	}
   105  
   106  	AllDefinitions = append(AllDefinitions, FieldOnlyMarkers...)
   107  }
   108  
   109  // +controllertools:marker:generateHelp:category="CRD validation"
   110  // Maximum specifies the maximum numeric value that this field can have.
   111  type Maximum int
   112  
   113  // +controllertools:marker:generateHelp:category="CRD validation"
   114  // Minimum specifies the minimum numeric value that this field can have.
   115  type Minimum int
   116  
   117  // +controllertools:marker:generateHelp:category="CRD validation"
   118  // ExclusiveMinimum indicates that the minimum is "up to" but not including that value.
   119  type ExclusiveMinimum bool
   120  
   121  // +controllertools:marker:generateHelp:category="CRD validation"
   122  // ExclusiveMaximum indicates that the maximum is "up to" but not including that value.
   123  type ExclusiveMaximum bool
   124  
   125  // +controllertools:marker:generateHelp:category="CRD validation"
   126  // MultipleOf specifies that this field must have a numeric value that's a multiple of this one.
   127  type MultipleOf int
   128  
   129  // +controllertools:marker:generateHelp:category="CRD validation"
   130  // MaxLength specifies the maximum length for this string.
   131  type MaxLength int
   132  
   133  // +controllertools:marker:generateHelp:category="CRD validation"
   134  // MinLength specifies the minimum length for this string.
   135  type MinLength int
   136  
   137  // +controllertools:marker:generateHelp:category="CRD validation"
   138  // Pattern specifies that this string must match the given regular expression.
   139  type Pattern string
   140  
   141  // +controllertools:marker:generateHelp:category="CRD validation"
   142  // MaxItems specifies the maximum length for this list.
   143  type MaxItems int
   144  
   145  // +controllertools:marker:generateHelp:category="CRD validation"
   146  // MinItems specifies the minimun length for this list.
   147  type MinItems int
   148  
   149  // +controllertools:marker:generateHelp:category="CRD validation"
   150  // UniqueItems specifies that all items in this list must be unique.
   151  type UniqueItems bool
   152  
   153  // +controllertools:marker:generateHelp:category="CRD validation"
   154  // Enum specifies that this (scalar) field is restricted to the *exact* values specified here.
   155  type Enum []interface{}
   156  
   157  // +controllertools:marker:generateHelp:category="CRD validation"
   158  // Format specifies additional "complex" formatting for this field.
   159  //
   160  // For example, a date-time field would be marked as "type: string" and
   161  // "format: date-time".
   162  type Format string
   163  
   164  // +controllertools:marker:generateHelp:category="CRD validation"
   165  // Type overrides the type for this field (which defaults to the equivalent of the Go type).
   166  //
   167  // This generally must be paired with custom serialization.  For example, the
   168  // metav1.Time field would be marked as "type: string" and "format: date-time".
   169  type Type string
   170  
   171  // +controllertools:marker:generateHelp:category="CRD validation"
   172  // Nullable marks this field as allowing the "null" value.
   173  //
   174  // This is often not necessary, but may be helpful with custom serialization.
   175  type Nullable struct{}
   176  
   177  // +controllertools:marker:generateHelp:category="CRD validation"
   178  // Default sets the default value for this field.
   179  //
   180  // A default value will be accepted as any value valid for the
   181  // field. Formatting for common types include: boolean: `true`, string:
   182  // `Cluster`, numerical: `1.24`, array: `{1,2}`, object: `{policy:
   183  // "delete"}`). Defaults should be defined in pruned form, and only best-effort
   184  // validation will be performed. Full validation of a default requires
   185  // submission of the containing CRD to an apiserver.
   186  type Default struct {
   187  	Value interface{}
   188  }
   189  
   190  // +controllertools:marker:generateHelp:category="CRD processing"
   191  // PreserveUnknownFields stops the apiserver from pruning fields which are not specified.
   192  //
   193  // By default the apiserver drops unknown fields from the request payload
   194  // during the decoding step. This marker stops the API server from doing so.
   195  // It affects fields recursively, but switches back to normal pruning behaviour
   196  // if nested  properties or additionalProperties are specified in the schema.
   197  // This can either be true or undefined. False
   198  // is forbidden.
   199  type XPreserveUnknownFields struct{}
   200  
   201  // +controllertools:marker:generateHelp:category="CRD validation"
   202  // EmbeddedResource marks a fields as an embedded resource with apiVersion, kind and metadata fields.
   203  //
   204  // An embedded resource is a value that has apiVersion, kind and metadata fields.
   205  // They are validated implicitly according to the semantics of the currently
   206  // running apiserver. It is not necessary to add any additional schema for these
   207  // field, yet it is possible. This can be combined with PreserveUnknownFields.
   208  type XEmbeddedResource struct{}
   209  
   210  func (m Maximum) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
   211  	if schema.Type != "integer" {
   212  		return fmt.Errorf("must apply maximum to an integer")
   213  	}
   214  	val := float64(m)
   215  	schema.Maximum = &val
   216  	return nil
   217  }
   218  func (m Minimum) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
   219  	if schema.Type != "integer" {
   220  		return fmt.Errorf("must apply minimum to an integer")
   221  	}
   222  	val := float64(m)
   223  	schema.Minimum = &val
   224  	return nil
   225  }
   226  func (m ExclusiveMaximum) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
   227  	if schema.Type != "integer" {
   228  		return fmt.Errorf("must apply exclusivemaximum to an integer")
   229  	}
   230  	schema.ExclusiveMaximum = bool(m)
   231  	return nil
   232  }
   233  func (m ExclusiveMinimum) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
   234  	if schema.Type != "integer" {
   235  		return fmt.Errorf("must apply exclusiveminimum to an integer")
   236  	}
   237  	schema.ExclusiveMinimum = bool(m)
   238  	return nil
   239  }
   240  func (m MultipleOf) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
   241  	if schema.Type != "integer" {
   242  		return fmt.Errorf("must apply multipleof to an integer")
   243  	}
   244  	val := float64(m)
   245  	schema.MultipleOf = &val
   246  	return nil
   247  }
   248  
   249  func (m MaxLength) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
   250  	if schema.Type != "string" {
   251  		return fmt.Errorf("must apply maxlength to a string")
   252  	}
   253  	val := int64(m)
   254  	schema.MaxLength = &val
   255  	return nil
   256  }
   257  func (m MinLength) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
   258  	if schema.Type != "string" {
   259  		return fmt.Errorf("must apply minlength to a string")
   260  	}
   261  	val := int64(m)
   262  	schema.MinLength = &val
   263  	return nil
   264  }
   265  func (m Pattern) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
   266  	if schema.Type != "string" {
   267  		return fmt.Errorf("must apply pattern to a string")
   268  	}
   269  	schema.Pattern = string(m)
   270  	return nil
   271  }
   272  
   273  func (m MaxItems) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
   274  	if schema.Type != "array" {
   275  		return fmt.Errorf("must apply maxitem to an array")
   276  	}
   277  	val := int64(m)
   278  	schema.MaxItems = &val
   279  	return nil
   280  }
   281  func (m MinItems) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
   282  	if schema.Type != "array" {
   283  		return fmt.Errorf("must apply minitems to an array")
   284  	}
   285  	val := int64(m)
   286  	schema.MinItems = &val
   287  	return nil
   288  }
   289  func (m UniqueItems) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
   290  	if schema.Type != "array" {
   291  		return fmt.Errorf("must apply uniqueitems to an array")
   292  	}
   293  	schema.UniqueItems = bool(m)
   294  	return nil
   295  }
   296  
   297  func (m Enum) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
   298  	// TODO(directxman12): this is a bit hacky -- we should
   299  	// probably support AnyType better + using the schema structure
   300  	vals := make([]apiext.JSON, len(m))
   301  	for i, val := range m {
   302  		// TODO(directxman12): check actual type with schema type?
   303  		// if we're expecting a string, marshal the string properly...
   304  		// NB(directxman12): we use json.Marshal to ensure we handle JSON escaping properly
   305  		valMarshalled, err := json.Marshal(val)
   306  		if err != nil {
   307  			return err
   308  		}
   309  		vals[i] = apiext.JSON{Raw: valMarshalled}
   310  	}
   311  	schema.Enum = vals
   312  	return nil
   313  }
   314  func (m Format) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
   315  	schema.Format = string(m)
   316  	return nil
   317  }
   318  
   319  // NB(directxman12): we "typecheck" on target schema properties here,
   320  // which means the "Type" marker *must* be applied first.
   321  // TODO(directxman12): find a less hacky way to do this
   322  // (we could preserve ordering of markers, but that feels bad in its own right).
   323  
   324  func (m Type) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
   325  	schema.Type = string(m)
   326  	return nil
   327  }
   328  
   329  func (m Type) ApplyFirst() {}
   330  
   331  func (m Nullable) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
   332  	schema.Nullable = true
   333  	return nil
   334  }
   335  
   336  // Defaults are only valid CRDs created with the v1 API
   337  func (m Default) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
   338  	marshalledDefault, err := json.Marshal(m.Value)
   339  	if err != nil {
   340  		return err
   341  	}
   342  	schema.Default = &apiext.JSON{Raw: marshalledDefault}
   343  	return nil
   344  }
   345  
   346  func (m XPreserveUnknownFields) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
   347  	defTrue := true
   348  	schema.XPreserveUnknownFields = &defTrue
   349  	return nil
   350  }
   351  
   352  func (m XEmbeddedResource) ApplyToSchema(schema *apiext.JSONSchemaProps) error {
   353  	schema.XEmbeddedResource = true
   354  	return nil
   355  }