Declarative API validation

Declarative validation behavior, rollout, feature gates, and validation tags for Kubernetes APIs.

Declarative validation lets Kubernetes API authors put common validation rules next to the versioned API types they apply to. Instead of hand-writing every basic check in validation.go, API authors add +k8s: tags to types.go, and validation-gen turns those tags into Go validation code.

This does not completely replace handwritten validation as there are complex/bespoke validation rules that cannot easily be expressed as tags for which the best practice is to still use handwritten validation code. The goal is to move the simple, repeated, field-local rules into a form that is easier to review and harder to accidentally drift.

TLDR of declarative validation usage

  • Put validation tags on versioned API types.
  • Run hack/update-codegen.sh after adding or changing tags.

Rollout and feature gates

Declarative validation has two separate concerns:

  • whether generated validation code runs at all
  • whether a particular generated error is returned to the user or only compared against handwritten validation.

The current rollout uses these feature gates:

Feature gateStageDefaultWhat it does
DeclarativeValidationGA in v1.36true, locked to defaultRuns declarative validation logic where it has been wired in. DeclarativeValidationBeta toggles whether hand-written or declarative validation is authoritative. This only “turns DV” on for migration cases, it is always on for cases w/ no hand-written fallback logic like in new APIs
DeclarativeValidationBetaBetatrueControls +k8s:beta validation rules. When enabled, Beta rules reject invalid requests. When disabled, Beta rules fall back to shadow mode.
DeclarativeValidationTakeoverDeprecated in v1.36n/aPreviously controlled whether declarative validation was authoritative. It is no longer honored, but may still be accepted to avoid unknown-gate errors.

Migrated validation rules (vs net new declarative validation rules) are staged with lifecycle wrappers:

Rule shapeEnforcement behavior
+k8s:alpha(since: "1.N")=<validator>Always shadowed. Handwritten validation wins. Mismatches are reported through metrics.
+k8s:beta(since: "1.N")=<validator>DV authoritative when DeclarativeValidationBeta=true, shadowed when the gate is disabled. Mismateches are reported through metrics.
<validator>Stable/unwrapped. DV always authoritative.

Disabling DeclarativeValidationBeta

Cluster administrators can set DeclarativeValidationBeta=false to move +k8s:beta rules back to shadow mode. This is mostly a rollback valve.

Reasons to consider disabling it:

  • Beta declarative validation rejects requests that should still be valid.
  • Beta declarative validation allows objects that handwritten validation would have rejected.

For feature gate mechanics, see the Kubernetes feature gates documentation.

Tag catalog

Descriptions and stability levels in this table come from validation-gen TagDoc values when the tag is a validator. A few generator wiring tags do not have validator TagDoc values, those are marked as metadata.

TagDescriptionStability
+k8s:alphaMarks the given payload validation as a Alpha validation of the handwritten validation code. An optional Kubernetes version can be specified.Beta
+k8s:betaMarks the given payload validation as a Beta validation of the handwritten validation code. An optional Kubernetes version can be specified.Beta
+k8s:customUniqueIndicates that uniqueness validation for this list is implemented via custom, handwritten validation. This disables generation of uniqueness validation for this list.Beta
+k8s:eachKeyDeclares a validation for each value in a map or list.Beta
+k8s:eachValDeclares a validation for each value in a map or list.Alpha
+k8s:enumIndicates that a string type is an enum. All constant values of this type are considered values in the enum unless excluded using +k8s:enumExclude.Stable
+k8s:enumExcludeIndicates that an constant value is not part of an enum, even if the constant’s type is tagged with k8s:enum. May be conditionally excluded via +k8s:ifEnabled(Option)=+k8s:enumExclude or +k8s:ifDisabled(Option)=+k8s:enumExclude. If multiple +k8s:ifEnabled/+k8s:ifDisabled tags are used, the value is excluded if any of the exclude conditions are met.Alpha
+k8s:forbiddenIndicates that a field may not be specified.Beta
+k8s:formatIndicates that a string field has a particular format.Stable
+k8s:ifDisabledDeclares a validation that only applies when an option is disabled.Beta
+k8s:ifEnabledDeclares a validation that only applies when an option is enabled.Beta
+k8s:ifModeIndicates that this field’s validation depends on a mode discriminator.Beta
+k8s:immutableIndicates that a field may not be updated.Beta
+k8s:isSubresourceMark a type as validation for a specific subresource.Metadata
+k8s:itemDeclares a validation for an item of a slice declared as a +k8s:listType=map. The item to match is declared by providing field-value pair arguments. All key fields must be specified.Stable
+k8s:listMapKeyDeclares a named sub-field of a list’s value-type to be part of the list-map key.Stable
+k8s:listTypeDeclares a list field’s semantic type and ownership behavior. atomic: single ownership, set: shared ownership with uniqueness, map: shared ownership with key-based uniqueness.Stable
+k8s:maxBytesIndicates that a string field has a limit on its length in bytes. This could only allow as few as N/4 multi-byte characters. If you want to limit length of characters specifically, use maxLength.Beta
+k8s:maxItemsIndicates that a list has a limit on its size.Stable
+k8s:maxLengthIndicates that a string field has a limit on its length in characters. This could allow up to 4*N bytes if multi-byte characters are used. If you want to limit length of bytes specifically, use maxBytes.Stable
+k8s:maxPropertiesmaxProperties provides a limit on properties of an object as defined by JSON schema. In Kubernetes it may only be used to constrain the number of elements on a field defined as a golang map.Stable
+k8s:maximumIndicates that a numeric field has a maximum value.Stable
+k8s:minItemsIndicates that a list has a minimum size.Stable
+k8s:minLengthIndicates that a string field has a minimum length for its value in characters. This means that the minimum size in bytes is a range from X to 4X if multi-byte characters are allowed.Stable
+k8s:minPropertiesminProperties provides a limit on properties of an object as defined by JSON schema. In Kubernetes it may only be used to constrain the number of elements on a field defined as a golang map.Stable
+k8s:minimumIndicates that a numeric field has a minimum value.Stable
+k8s:modeDiscriminatorIndicates that this field is a discriminator for state-based validation.Beta
+k8s:neqVerifies the field’s value is not equal to a specific disallowed value. Supports string, integer, and boolean types.Alpha
+k8s:opaqueTypeIndicates that any validations declared on the referenced type will be ignored. If a referenced type’s package is not included in the generator’s current flags, this tag must be set, or code generation will fail (preventing silent mistakes). If the validations should not be ignored, add the type’s package to the generator using the –readonly-pkg flag.Alpha
+k8s:optionalIndicates that a field is optional to clients.Stable
+k8s:requiredIndicates that a field must be specified by clients.Stable
+k8s:subfieldDeclares a validation for a subfield of a struct.Stable
+k8s:supportsSubresourceDeclare supported validation subresources for a type.Metadata
+k8s:unionDiscriminatorIndicates that this field is the discriminator for a union.Beta
+k8s:unionMemberIndicates that this field is a member of a union.Stable
+k8s:uniqueDeclares that a list field’s elements are unique. This tag can be used with listType=atomic to add uniqueness constraints, or independently to specify uniqueness semantics.Beta
+k8s:updateProvides constraints on the allowed update operations of a field. Constraints: NoSet (prevents unset->set transitions), NoUnset (prevents set->unset transitions), NoModify (prevents value changes but allows set/unset transitions), NoAddItem (prevents adding items to a slice or map), NoRemoveItem (prevents removing items from a slice or map). Multiple constraints can be specified using multiple tags. For non-pointer structs, NoSet and NoUnset have no effect as these fields cannot be unset. For slice and map fields, ‘unset’ means len == 0. Slice item identity for NoAddItem/NoRemoveItem comes from +k8s:listType/+k8s:listMapKey/+k8s:unique, for maps the key is the item identity. NoModify is not supported on slices or maps, use +k8s:eachVal=+k8s:update=NoModify for per-item immutability. On lists, +k8s:eachVal=+k8s:update=NoModify requires listType=map or unique=map, otherwise content changes are not detectable. Examples: +k8s:update=NoModify +k8s:update=NoUnset for set-once fields, +k8s:update=NoSet for fields that must be set at creation or never, +k8s:update=NoAddItem +k8s:update=NoRemoveItem on a listType=map field to freeze the structural shape of the list.Beta
+k8s:validateErrorAlways fails code generation (useful for testing).Alpha
+k8s:validateFalseAlways fails validation (useful for testing).Alpha
+k8s:validateTrueAlways passes validation (useful for testing).Alpha
+k8s:validateTrueAlphaAlways passes validation (useful for testing).Alpha
+k8s:validateTrueBetaAlways passes validation (useful for testing).Beta
+k8s:zeroOrOneOfMemberIndicates that this field is a member of a zero-or-one-of union.Stable

Tag reference

+k8s:alpha

Marks the given payload validation as a Alpha validation of the handwritten validation code. An optional Kubernetes version can be specified.

  • Argument since: The Kubernetes version (e.g. 1.34) at which this validation was added.
  • Payload <validation-tag>: The validation tag to evaluate as a Alpha validation.
type MyStruct struct {
    // +k8s:alpha(since: "1.36")=+k8s:minimum=1
    MyField int `json:"myField"`
}

Do not use this to try to disable handwritten validation. It only controls the lifecycle stage of the generated rule.

+k8s:beta

Marks the given payload validation as a Beta validation of the handwritten validation code. An optional Kubernetes version can be specified.

  • Argument since: The Kubernetes version (e.g. 1.34) at which this validation was added.
  • Payload <validation-tag>: The validation tag to evaluate as a Beta validation.
type MyStruct struct {
    // +k8s:beta(since: "1.37")=+k8s:minimum=1
    MyField int `json:"myField"`
}

+k8s:customUnique

Indicates that uniqueness validation for this list is implemented via custom, handwritten validation. This disables generation of uniqueness validation for this list.

type MyStruct struct {
    // +k8s:listType=set
    // +k8s:customUnique
    Names []string `json:"names"`
}

+k8s:eachKey

Declares a validation for each value in a map or list.

  • Payload <validation-tag>: The tag to evaluate for each key.
type MyStruct struct {
    // +k8s:eachKey=+k8s:minimum=1
    MyMap map[int]string `json:"myMap"`
}

In this case every key in MyMap must be at least 1.

+k8s:eachVal

Declares a validation for each value in a map or list.

  • Payload <validation-tag>: The tag to evaluate for each value.
type MyStruct struct {
    // +k8s:eachVal=+k8s:minimum=1
    MyMap map[string]int `json:"myMap"`
}

In this case every value in MyMap must be at least 1.

+k8s:enum

Indicates that a string type is an enum. All constant values of this type are considered values in the enum unless excluded using +k8s:enumExclude.

// +k8s:enum
type MyEnum string

const (
    MyEnumA MyEnum = "A"
    MyEnumB MyEnum = "B"
)

type MyStruct struct {
    MyField MyEnum `json:"myField"`
}

Generated validation rejects values outside the declared constants.

+k8s:enumExclude

Indicates that an constant value is not part of an enum, even if the constant’s type is tagged with k8s:enum. May be conditionally excluded via +k8s:ifEnabled(Option)=+k8s:enumExclude or +k8s:ifDisabled(Option)=+k8s:enumExclude. If multiple +k8s:ifEnabled/+k8s:ifDisabled tags are used, the value is excluded if any of the exclude conditions are met.

// +k8s:enum
type MyEnum string

const (
    MyEnumA MyEnum = "A"
    MyEnumB MyEnum = "B"

    // +k8s:enumExclude
    MyEnumInternal MyEnum = "Internal"
)

+k8s:forbidden

Indicates that a field may not be specified.

type MyStruct struct {
    // +k8s:forbidden
    MyField string `json:"myField"`
}

+k8s:format

Indicates that a string field has a particular format.

Supported payloads:

  • k8s-extended-resource-name: This field holds a Kubernetes extended resource name. This is a domain-prefixed name that must not have a kubernetes.io or requests. prefix. When requests. is prepended, the result must be a valid label key, as used by quota.
  • k8s-label-key: This field holds a Kubernetes label key.
  • k8s-label-value: This field holds a Kubernetes label value.
  • k8s-long-name: This field holds a Kubernetes “long name”, aka a “DNS subdomain” value.
  • k8s-long-name-caseless: Deprecated: This field holds a case-insensitive Kubernetes “long name”, aka a “DNS subdomain” value.
  • k8s-path-segment-name: This field holds a Kubernetes “path segment name” value.
  • k8s-resource-fully-qualified-name: This field holds a Kubernetes resource “fully qualified name” value. A fully qualified name must not be empty and must be composed of a prefix and a name, separated by a slash (e.g., “prefix/name”). The prefix must be a DNS subdomain, and the name part must be a C identifier with no more than 32 characters.
  • k8s-resource-pool-name: This field holds value with one or more Kubernetes “long name” parts separated by / and no longer than 253 characters.
  • k8s-short-name: This field holds a Kubernetes “short name”, aka a “DNS label” value.
  • k8s-uuid: This field holds a Kubernetes UUID, which conforms to RFC 4122.
type MyStruct struct {
    // +k8s:format=k8s-label-key
    LabelKey string `json:"labelKey"`

    // +k8s:format=k8s-uuid
    UID string `json:"uid"`
}

+k8s:ifDisabled

Declares a validation that only applies when an option is disabled.

  • Argument: <option>.
  • Payload <validation-tag>: This validation tag will be evaluated only if the validation option is disabled.
type MyStruct struct {
    // +k8s:ifDisabled(MyFeature)=+k8s:required
    MyField string `json:"myField"`
}

+k8s:ifEnabled

Declares a validation that only applies when an option is enabled.

  • Argument: <option>.
  • Payload <validation-tag>: This validation tag will be evaluated only if the validation option is enabled.
type MyStruct struct {
    // +k8s:ifEnabled(MyFeature)=+k8s:required
    MyField string `json:"myField"`
}

+k8s:ifMode

Indicates that this field’s validation depends on a mode discriminator.

  • Positional argument <string>: the value of the mode discriminator for which this validation applies.
  • Argument modality (<string>): the name of the discriminator group.
  • Argument mode (<string>): the mode value for which this validation applies.
  • Payload: <validation-tag>.
type MyStruct struct {
    // +k8s:modeDiscriminator
    Mode string `json:"mode"`

    // +k8s:ifMode("External")=+k8s:required
    External *ExternalConfig `json:"external,omitempty"`
}

+k8s:immutable

Indicates that a field may not be updated.

type MyStruct struct {
    // +k8s:immutable
    Name string `json:"name"`
}

+k8s:isSubresource

Marks a type as the validation shape for a specific subresource.

It depends on +k8s:supportsSubresource on the root resource type. Without that matching support declaration, generated subresource validation code may exist but not be reachable through the dispatcher.

  • Payload: subresource path, such as "/status" or "/scale".

Root resource type:

// +k8s:supportsSubresource="/scale"
type MyResource struct {
    Spec   MySpec   `json:"spec,omitempty"`
    Status MyStatus `json:"status,omitempty"`
}

Subresource type:

// +k8s:isSubresource="/scale"
type MyResourceScale struct {
    Spec   ScaleSpec   `json:"spec,omitempty"`
    Status ScaleStatus `json:"status,omitempty"`
}

Use this when subresource validation needs to live separately from root-object validation.

+k8s:item

Declares a validation for an item of a slice declared as a +k8s:listType=map. The item to match is declared by providing field-value pair arguments. All key fields must be specified.

Arguments must be named with the JSON names of the list-map key fields. Values can be strings, integers, or booleans. For example: +k8s:item(name: “myname”, priority: 10, enabled: true)=

  • Payload <validation-tag>: The tag to evaluate for the matching list item.
type MyStruct struct {
    // +k8s:listType=map
    // +k8s:listMapKey=type
    // +k8s:item(type: "Approved")=+k8s:zeroOrOneOfMember
    // +k8s:item(type: "Denied")=+k8s:zeroOrOneOfMember
    MyConditions []MyCondition `json:"conditions"`
}

type MyCondition struct {
    Type   string `json:"type"`
    Status string `json:"status"`
}

Here only the items with type: "Approved" or type: "Denied" get the nested validation.

+k8s:listMapKey

Declares a named sub-field of a list’s value-type to be part of the list-map key.

  • Payload <field-json-name>: The name of the field.
// +k8s:listType=map
// +k8s:listMapKey=keyFieldOne
// +k8s:listMapKey=keyFieldTwo
type MyList []MyStruct

type MyStruct struct {
    KeyFieldOne string `json:"keyFieldOne"`
    KeyFieldTwo string `json:"keyFieldTwo"`
    ValueField   string `json:"valueField"`
}

+k8s:listType

Declares a list field’s semantic type and ownership behavior. atomic: single ownership, set: shared ownership with uniqueness, map: shared ownership with key-based uniqueness.

  • Payload <type>: atomic | map | set.
// +k8s:listType=map
// +k8s:listMapKey=keyField
type MyList []MyStruct

type MyStruct struct {
    KeyField   string `json:"keyField"`
    ValueField string `json:"valueField"`
}

+k8s:maxBytes

Indicates that a string field has a limit on its length in bytes. This could only allow as few as N/4 multi-byte characters. If you want to limit length of characters specifically, use maxLength.

  • Payload <non-negative integer>: This field must be no more than X bytes long.
type MyStruct struct {
    // +k8s:maxBytes=64
    Token string `json:"token"`
}

+k8s:maxItems

Indicates that a list has a limit on its size.

  • Payload <non-negative integer>: This list must be no more than X items long.
type MyStruct struct {
    // +k8s:maxItems=5
    MyList []string `json:"myList"`
}

+k8s:maxLength

Indicates that a string field has a limit on its length in characters. This could allow up to 4*N bytes if multi-byte characters are used. If you want to limit length of bytes specifically, use maxBytes.

  • Payload <non-negative integer>: This field must be no more than X characters long.
type MyStruct struct {
    // +k8s:maxLength=10
    MyString string `json:"myString"`
}

+k8s:maxProperties

maxProperties provides a limit on properties of an object as defined by JSON schema. In Kubernetes it may only be used to constrain the number of elements on a field defined as a golang map.

  • Payload <non-negative integer>: This map must have no more than X properties (where X <= 100000).
type MyStruct struct {
    // +k8s:maxProperties=8
    Labels map[string]string `json:"labels"`
}

+k8s:maximum

Indicates that a numeric field has a maximum value.

  • Payload <integer>: This field must be less than or equal to X.
type MyStruct struct {
    // +k8s:maximum=10
    Replicas int `json:"replicas"`
}

+k8s:minItems

Indicates that a list has a minimum size.

  • Payload <non-negative integer>: This list must be at least X items long.
type MyStruct struct {
    // +k8s:minItems=1
    Names []string `json:"names"`
}

+k8s:minLength

Indicates that a string field has a minimum length for its value in characters. This means that the minimum size in bytes is a range from X to 4X if multi-byte characters are allowed.

  • Payload <integer>: This field must be at least X characters long.
type MyStruct struct {
    // +k8s:minLength=1
    Name string `json:"name"`
}

+k8s:minProperties

minProperties provides a limit on properties of an object as defined by JSON schema. In Kubernetes it may only be used to constrain the number of elements on a field defined as a golang map.

  • Payload <non-negative integer>: This map must have at least X properties (where X <= 100000).
type MyStruct struct {
    // +k8s:minProperties=1
    Selectors map[string]string `json:"selectors"`
}

+k8s:minimum

Indicates that a numeric field has a minimum value.

  • Payload <integer>: This field must be greater than or equal to X.
type MyStruct struct {
    // +k8s:minimum=0
    MyInt int `json:"myInt"`
}

+k8s:modeDiscriminator

Indicates that this field is a discriminator for state-based validation.

  • Argument modality (<string>): the name of the discriminator group, if more than one exists.
type MyStruct struct {
    // +k8s:modeDiscriminator
    Mode string `json:"mode"`

    // +k8s:ifMode("File")=+k8s:required
    File *FileSource `json:"file,omitempty"`

    // +k8s:ifMode("URL")=+k8s:required
    URL *URLSource `json:"url,omitempty"`
}

+k8s:neq

Verifies the field’s value is not equal to a specific disallowed value. Supports string, integer, and boolean types.

  • Payload <value>: The disallowed value. The parser will infer the type (string, int, bool).
type MyStruct struct {
    // +k8s:neq="disallowed"
    MyString string `json:"myString"`

    // +k8s:neq=0
    MyInt int `json:"myInt"`

    // +k8s:neq=true
    MyBool bool `json:"myBool"`
}

+k8s:opaqueType

Indicates that any validations declared on the referenced type will be ignored. If a referenced type’s package is not included in the generator’s current flags, this tag must be set, or code generation will fail (preventing silent mistakes). If the validations should not be ignored, add the type’s package to the generator using the –readonly-pkg flag.

import "some/external/package"

type MyStruct struct {
    // +k8s:opaqueType
    ExternalField package.ExternalType `json:"externalField"`
}

+k8s:optional

Indicates that a field is optional to clients.

type MyStruct struct {
    // +k8s:optional
    MyField string `json:"myField"`
}

+k8s:required

Indicates that a field must be specified by clients.

type MyStruct struct {
    // +k8s:required
    MyField string `json:"myField"`
}

+k8s:subfield

Declares a validation for a subfield of a struct.

The named subfield must be a direct field of the struct, or of an embedded struct.

  • Argument <field-json-name>.
  • Payload <validation-tag>: The tag to evaluate for the subfield.
type Wrapper struct {
    // +k8s:subfield(name)=+k8s:required
    Metadata ObjectMeta `json:"metadata"`
}

type ObjectMeta struct {
    Name string `json:"name"`
}

+k8s:supportsSubresource

Declares which subresources the validation dispatcher should recognize for a root resource type.

  • Payload: subresource path, such as "/status" or "/scale".
  • May be repeated for multiple subresources.
// +k8s:supportsSubresource="/status"
// +k8s:supportsSubresource="/scale"
type MyResource struct {
    Spec   MySpec   `json:"spec,omitempty"`
    Status MyStatus `json:"status,omitempty"`
}

If a type has no +k8s:supportsSubresource tags, generated validation is only wired for the root resource.

+k8s:unionDiscriminator

Indicates that this field is the discriminator for a union.

  • Argument union (<string>): the name of the union, if more than one exists.
type MyStruct struct {
    // +k8s:unionDiscriminator
    Type MyType `json:"type"`

    // +k8s:unionMember
    // +k8s:optional
    OptionA *OptionA `json:"optionA"`

    // +k8s:unionMember
    // +k8s:optional
    OptionB *OptionB `json:"optionB"`
}

+k8s:unionMember

Indicates that this field is a member of a union.

  • Argument union (<string>): the name of the union, if more than one exists.
  • Argument memberName (<string>): the discriminator value for this member.
type MyStruct struct {
    // +k8s:unionMember(union: "backend", memberName: "service")
    // +k8s:optional
    Service *ServiceBackend `json:"service"`

    // +k8s:unionMember(union: "backend", memberName: "resource")
    // +k8s:optional
    Resource *ResourceBackend `json:"resource"`
}

+k8s:unique

Declares that a list field’s elements are unique. This tag can be used with listType=atomic to add uniqueness constraints, or independently to specify uniqueness semantics.

  • Payload <type>: map | set.
type MyStruct struct {
    // +k8s:listType=atomic
    // +k8s:unique=map
    // +k8s:listMapKey=name
    Ports []Port `json:"ports"`
}

type Port struct {
    Name string `json:"name"`
    Port int32  `json:"port"`
}

+k8s:update

Provides constraints on the allowed update operations of a field. Constraints: NoSet (prevents unset->set transitions), NoUnset (prevents set->unset transitions), NoModify (prevents value changes but allows set/unset transitions), NoAddItem (prevents adding items to a slice or map), NoRemoveItem (prevents removing items from a slice or map). Multiple constraints can be specified using multiple tags. For non-pointer structs, NoSet and NoUnset have no effect as these fields cannot be unset. For slice and map fields, ‘unset’ means len == 0. Slice item identity for NoAddItem/NoRemoveItem comes from +k8s:listType/+k8s:listMapKey/+k8s:unique, for maps the key is the item identity. NoModify is not supported on slices or maps, use +k8s:eachVal=+k8s:update=NoModify for per-item immutability. On lists, +k8s:eachVal=+k8s:update=NoModify requires listType=map or unique=map, otherwise content changes are not detectable. Examples: +k8s:update=NoModify +k8s:update=NoUnset for set-once fields, +k8s:update=NoSet for fields that must be set at creation or never, +k8s:update=NoAddItem +k8s:update=NoRemoveItem on a listType=map field to freeze the structural shape of the list.

type MyStruct struct {
    // +k8s:update=NoModify
    // +k8s:update=NoUnset
    Token *string `json:"token,omitempty"`

    // +k8s:listType=map
    // +k8s:listMapKey=name
    // +k8s:update=NoAddItem
    // +k8s:update=NoRemoveItem
    Ports []Port `json:"ports"`
}

type Port struct {
    Name string `json:"name"`
    Port int32  `json:"port"`
}
type MyStruct struct {
    // +k8s:listType=map
    // +k8s:listMapKey=name
    // +k8s:eachVal=+k8s:update=NoModify
    Ports []Port `json:"ports"`
}

+k8s:zeroOrOneOfMember

Indicates that this field is a member of a zero-or-one-of union.

A zero-or-one-of union allows at most one member to be set. Unlike regular unions, having no members set is valid.

Warning: This tag should only be used on sets of list items, and never on struct fields directly.

  • Argument union (<string>): the name of the union, if more than one exists.
  • Argument memberName (<string>): the custom member name for this member.
type MyStruct struct {
    // +k8s:listType=map
    // +k8s:listMapKey=type
    // +k8s:item(type: "Foo")=+k8s:zeroOrOneOfMember
    // +k8s:item(type: "Bar")=+k8s:zeroOrOneOfMember
    Conditions []Condition `json:"conditions"`
}

type Condition struct {
    Type   string `json:"type"`
    Status string `json:"status"`
}

Testing-only tags

validation-gen also registers a few tags that are only useful in generator tests:

  • +k8s:validateError: Always fails code generation (useful for testing).
  • +k8s:validateFalse: Always fails validation (useful for testing).
  • +k8s:validateTrue: Always passes validation (useful for testing).
  • +k8s:validateTrueAlpha: Always passes validation (useful for testing).
  • +k8s:validateTrueBeta: Always passes validation (useful for testing).

+k8s:validateError payload:

  • Payload <string>: This string will be included in the error message.

+k8s:validateFalse, +k8s:validateTrue, +k8s:validateTrueAlpha, and +k8s:validateTrueBeta arguments and payloads:

  • Argument flags (<comma-separated-list-of-flag-string>): values: ShortCircuit, NonError.
  • Argument typeArg (<string>): The type arg in generated code (must be the value-type, not pointer).
  • Argument cohort (<string>): An optional cohort name to group multiple validations.
  • Payload <none>.
  • Payload <string>: The generated code will include this string.
type FixtureStruct struct {
    // +k8s:validateFalse="field FixtureStruct.Name"
    Name string `json:"name"`
}

Review checklist

Use this as a quick pass when reviewing a PR that adds declarative validation:

  • Are the tags on the versioned API types?
  • Did the PR regenerate zz_generated.validations.go?
  • Did the PR regenerate test/declarative_validation/<group>/<kind>/zz_generated.validations.main_test.go?
  • Did the PR regenerate test/declarative_validation/<group>/<kind>/zz_generated.validations.<api-version-*>_test.go?
  • For migrations, does handwritten validation still match the generated result?
  • For lifecycle wrappers, is since: set to the version where the rule entered that stage?
  • For cases where the field is uses as a subresource (ex: spec.Status.* fields), is the necessary +k8s:supportsSubresource or +k8s:isSubresource set?