mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-27 21:47:07 +00:00
Fix validation and add many tests
This commit is contained in:
parent
c0617933d4
commit
cf24968309
@ -87,7 +87,7 @@ type JSONSchemaProps struct {
|
|||||||
// - ... zero or more
|
// - ... zero or more
|
||||||
XIntOrString bool
|
XIntOrString bool
|
||||||
|
|
||||||
// x-kubernetes-list-map-keys annotates lists with the x-kubernetes-list-type `map` by specifying the keys used
|
// x-kubernetes-list-map-keys annotates an array with the x-kubernetes-list-type `map` by specifying the keys used
|
||||||
// as the index of the map.
|
// as the index of the map.
|
||||||
//
|
//
|
||||||
// This tag MUST only be used on lists that have the "x-kubernetes-list-type"
|
// This tag MUST only be used on lists that have the "x-kubernetes-list-type"
|
||||||
@ -95,19 +95,19 @@ type JSONSchemaProps struct {
|
|||||||
// be a scalar typed field of the child structure (no nesting is supported).
|
// be a scalar typed field of the child structure (no nesting is supported).
|
||||||
XListMapKeys []string
|
XListMapKeys []string
|
||||||
|
|
||||||
// x-kubernetes-list-type annotates a list to further describe its topology.
|
// x-kubernetes-list-type annotates an array to further describe its topology.
|
||||||
// This extension must only be used on lists and may have 3 possible values:
|
// This extension must only be used on lists and may have 3 possible values:
|
||||||
//
|
//
|
||||||
// 1) `atomic`: the list is treated as a single entity, like a scalar.
|
// 1) `atomic`: the list is treated as a single entity, like a scalar.
|
||||||
// Atomic lists will be entirely replaced when updated. This extension
|
// Atomic lists will be entirely replaced when updated. This extension
|
||||||
// may be used on any type of list (struct, scalar, ...).
|
// may be used on any type of list (struct, scalar, ...).
|
||||||
// 2) `set`:
|
// 2) `set`:
|
||||||
// Sets are lists that must not have multiple times the same value. Each
|
// Sets are lists that must not have multiple items with the same value. Each
|
||||||
// value must be a scalar (or another atomic type).
|
// value must be a scalar (or another atomic type).
|
||||||
// 3) `map`:
|
// 3) `map`:
|
||||||
// These lists are like maps in that their elements have a non-index key
|
// These lists are like maps in that their elements have a non-index key
|
||||||
// used to identify them. Order is preserved upon merge. The map tag
|
// used to identify them. Order is preserved upon merge. The map tag
|
||||||
// must only be used on a list with struct elements.
|
// must only be used on a list with elements of type object.
|
||||||
XListType *string
|
XListType *string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,3 +59,47 @@ func SetDefaults_ServiceReference(obj *ServiceReference) {
|
|||||||
obj.Port = utilpointer.Int32Ptr(443)
|
obj.Port = utilpointer.Int32Ptr(443)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetDefaults_JSONSchemaProps sets the defaults for JSONSchemaProps
|
||||||
|
func SetDefaults_JSONSchemaProps(obj *JSONSchemaProps) {
|
||||||
|
if obj == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if obj.Type == "array" && obj.XListType == nil {
|
||||||
|
obj.XListType = utilpointer.StringPtr("atomic")
|
||||||
|
}
|
||||||
|
if obj.Items != nil {
|
||||||
|
SetDefaults_JSONSchemaProps(obj.Items.Schema)
|
||||||
|
defaultJSONSchemaPropsArray(obj.Items.JSONSchemas)
|
||||||
|
}
|
||||||
|
defaultJSONSchemaPropsArray(obj.AllOf)
|
||||||
|
defaultJSONSchemaPropsArray(obj.OneOf)
|
||||||
|
defaultJSONSchemaPropsArray(obj.AnyOf)
|
||||||
|
SetDefaults_JSONSchemaProps(obj.Not)
|
||||||
|
defaultJSONSchemaPropsMap(obj.Properties)
|
||||||
|
if obj.AdditionalProperties != nil {
|
||||||
|
SetDefaults_JSONSchemaProps(obj.AdditionalProperties.Schema)
|
||||||
|
}
|
||||||
|
defaultJSONSchemaPropsMap(obj.PatternProperties)
|
||||||
|
for i := range obj.Dependencies {
|
||||||
|
SetDefaults_JSONSchemaProps(obj.Dependencies[i].Schema)
|
||||||
|
}
|
||||||
|
if obj.AdditionalItems != nil {
|
||||||
|
SetDefaults_JSONSchemaProps(obj.AdditionalItems.Schema)
|
||||||
|
}
|
||||||
|
defaultJSONSchemaPropsMap(map[string]JSONSchemaProps(obj.Definitions))
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultJSONSchemaPropsArray(obj []JSONSchemaProps) {
|
||||||
|
for i := range obj {
|
||||||
|
SetDefaults_JSONSchemaProps(&obj[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultJSONSchemaPropsMap(obj map[string]JSONSchemaProps) {
|
||||||
|
for i := range obj {
|
||||||
|
props := obj[i]
|
||||||
|
SetDefaults_JSONSchemaProps(&props)
|
||||||
|
obj[i] = props
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -124,6 +124,47 @@ func TestDefaults(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "list type extension defaults",
|
||||||
|
original: &CustomResourceDefinition{
|
||||||
|
Spec: CustomResourceDefinitionSpec{
|
||||||
|
Scope: NamespaceScoped,
|
||||||
|
Conversion: &CustomResourceConversion{Strategy: NoneConverter},
|
||||||
|
Versions: []CustomResourceDefinitionVersion{
|
||||||
|
{
|
||||||
|
Name: "v1",
|
||||||
|
Storage: true,
|
||||||
|
Schema: &CustomResourceValidation{
|
||||||
|
OpenAPIV3Schema: &JSONSchemaProps{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &CustomResourceDefinition{
|
||||||
|
Spec: CustomResourceDefinitionSpec{
|
||||||
|
Scope: NamespaceScoped,
|
||||||
|
Conversion: &CustomResourceConversion{Strategy: NoneConverter},
|
||||||
|
Versions: []CustomResourceDefinitionVersion{
|
||||||
|
{
|
||||||
|
Name: "v1",
|
||||||
|
Storage: true,
|
||||||
|
Schema: &CustomResourceValidation{
|
||||||
|
OpenAPIV3Schema: &JSONSchemaProps{
|
||||||
|
Type: "array",
|
||||||
|
XListType: utilpointer.StringPtr("atomic"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Status: CustomResourceDefinitionStatus{
|
||||||
|
StoredVersions: []string{"v1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
@ -91,27 +91,31 @@ type JSONSchemaProps struct {
|
|||||||
// - ... zero or more
|
// - ... zero or more
|
||||||
XIntOrString bool `json:"x-kubernetes-int-or-string,omitempty" protobuf:"bytes,40,opt,name=xKubernetesIntOrString"`
|
XIntOrString bool `json:"x-kubernetes-int-or-string,omitempty" protobuf:"bytes,40,opt,name=xKubernetesIntOrString"`
|
||||||
|
|
||||||
// x-kubernetes-list-map-keys annotates lists with the x-kubernetes-list-type `map` by specifying the keys used
|
// x-kubernetes-list-map-keys annotates an array with the x-kubernetes-list-type `map` by specifying the keys used
|
||||||
// as the index of the map.
|
// as the index of the map.
|
||||||
//
|
//
|
||||||
// This tag MUST only be used on lists that have the "x-kubernetes-list-type"
|
// This tag MUST only be used on lists that have the "x-kubernetes-list-type"
|
||||||
// extension set to "map". Also, the values specified for this attribute must
|
// extension set to "map". Also, the values specified for this attribute must
|
||||||
// be a scalar typed field of the child structure (no nesting is supported).
|
// be a scalar typed field of the child structure (no nesting is supported).
|
||||||
|
//
|
||||||
|
// +optional
|
||||||
XListMapKeys []string `json:"x-kubernetes-list-map-keys,omitempty" protobuf:"bytes,41,rep,name=xKubernetesListMapKeys"`
|
XListMapKeys []string `json:"x-kubernetes-list-map-keys,omitempty" protobuf:"bytes,41,rep,name=xKubernetesListMapKeys"`
|
||||||
|
|
||||||
// x-kubernetes-list-type annotates a list to further describe its topology.
|
// x-kubernetes-list-type annotates an array to further describe its topology.
|
||||||
// This extension must only be used on lists and may have 3 possible values:
|
// This extension must only be used on lists and may have 3 possible values:
|
||||||
//
|
//
|
||||||
// 1) `atomic`: the list is treated as a single entity, like a scalar.
|
// 1) `atomic`: the list is treated as a single entity, like a scalar.
|
||||||
// Atomic lists will be entirely replaced when updated. This extension
|
// Atomic lists will be entirely replaced when updated. This extension
|
||||||
// may be used on any type of list (struct, scalar, ...).
|
// may be used on any type of list (struct, scalar, ...).
|
||||||
// 2) `set`:
|
// 2) `set`:
|
||||||
// Sets are lists that must not have multiple times the same value. Each
|
// Sets are lists that must not have multiple items with the same value. Each
|
||||||
// value must be a scalar (or another atomic type).
|
// value must be a scalar (or another atomic type).
|
||||||
// 3) `map`:
|
// 3) `map`:
|
||||||
// These lists are like maps in that their elements have a non-index key
|
// These lists are like maps in that their elements have a non-index key
|
||||||
// used to identify them. Order is preserved upon merge. The map tag
|
// used to identify them. Order is preserved upon merge. The map tag
|
||||||
// must only be used on a list with struct elements.
|
// must only be used on a list with elements of type object.
|
||||||
|
// Defaults to atomic for arrays.
|
||||||
|
// +optional
|
||||||
XListType *string `json:"x-kubernetes-list-type,omitempty" protobuf:"bytes,42,opt,name=xKubernetesListType"`
|
XListType *string `json:"x-kubernetes-list-type,omitempty" protobuf:"bytes,42,opt,name=xKubernetesListType"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,3 +80,47 @@ func SetDefaults_ServiceReference(obj *ServiceReference) {
|
|||||||
obj.Port = utilpointer.Int32Ptr(443)
|
obj.Port = utilpointer.Int32Ptr(443)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetDefaults_JSONSchemaProps sets the defaults for JSONSchemaProps
|
||||||
|
func SetDefaults_JSONSchemaProps(obj *JSONSchemaProps) {
|
||||||
|
if obj == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if obj.Type == "array" && obj.XListType == nil {
|
||||||
|
obj.XListType = utilpointer.StringPtr("atomic")
|
||||||
|
}
|
||||||
|
if obj.Items != nil {
|
||||||
|
SetDefaults_JSONSchemaProps(obj.Items.Schema)
|
||||||
|
defaultJSONSchemaPropsArray(obj.Items.JSONSchemas)
|
||||||
|
}
|
||||||
|
defaultJSONSchemaPropsArray(obj.AllOf)
|
||||||
|
defaultJSONSchemaPropsArray(obj.OneOf)
|
||||||
|
defaultJSONSchemaPropsArray(obj.AnyOf)
|
||||||
|
SetDefaults_JSONSchemaProps(obj.Not)
|
||||||
|
defaultJSONSchemaPropsMap(obj.Properties)
|
||||||
|
if obj.AdditionalProperties != nil {
|
||||||
|
SetDefaults_JSONSchemaProps(obj.AdditionalProperties.Schema)
|
||||||
|
}
|
||||||
|
defaultJSONSchemaPropsMap(obj.PatternProperties)
|
||||||
|
for i := range obj.Dependencies {
|
||||||
|
SetDefaults_JSONSchemaProps(obj.Dependencies[i].Schema)
|
||||||
|
}
|
||||||
|
if obj.AdditionalItems != nil {
|
||||||
|
SetDefaults_JSONSchemaProps(obj.AdditionalItems.Schema)
|
||||||
|
}
|
||||||
|
defaultJSONSchemaPropsMap(map[string]JSONSchemaProps(obj.Definitions))
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultJSONSchemaPropsArray(obj []JSONSchemaProps) {
|
||||||
|
for i := range obj {
|
||||||
|
SetDefaults_JSONSchemaProps(&obj[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultJSONSchemaPropsMap(obj map[string]JSONSchemaProps) {
|
||||||
|
for i := range obj {
|
||||||
|
props := obj[i]
|
||||||
|
SetDefaults_JSONSchemaProps(&props)
|
||||||
|
obj[i] = props
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -129,6 +129,34 @@ func TestDefaults(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "list type extension defaults",
|
||||||
|
original: &CustomResourceDefinition{
|
||||||
|
Spec: CustomResourceDefinitionSpec{
|
||||||
|
Scope: NamespaceScoped,
|
||||||
|
Conversion: &CustomResourceConversion{Strategy: NoneConverter},
|
||||||
|
PreserveUnknownFields: utilpointer.BoolPtr(true),
|
||||||
|
Validation: &CustomResourceValidation{
|
||||||
|
OpenAPIV3Schema: &JSONSchemaProps{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &CustomResourceDefinition{
|
||||||
|
Spec: CustomResourceDefinitionSpec{
|
||||||
|
Scope: NamespaceScoped,
|
||||||
|
Conversion: &CustomResourceConversion{Strategy: NoneConverter},
|
||||||
|
PreserveUnknownFields: utilpointer.BoolPtr(true),
|
||||||
|
Validation: &CustomResourceValidation{
|
||||||
|
OpenAPIV3Schema: &JSONSchemaProps{
|
||||||
|
Type: "array",
|
||||||
|
XListType: utilpointer.StringPtr("atomic"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
@ -91,27 +91,31 @@ type JSONSchemaProps struct {
|
|||||||
// - ... zero or more
|
// - ... zero or more
|
||||||
XIntOrString bool `json:"x-kubernetes-int-or-string,omitempty" protobuf:"bytes,40,opt,name=xKubernetesIntOrString"`
|
XIntOrString bool `json:"x-kubernetes-int-or-string,omitempty" protobuf:"bytes,40,opt,name=xKubernetesIntOrString"`
|
||||||
|
|
||||||
// x-kubernetes-list-map-keys annotates lists with the x-kubernetes-list-type `map` by specifying the keys used
|
// x-kubernetes-list-map-keys annotates an array with the x-kubernetes-list-type `map` by specifying the keys used
|
||||||
// as the index of the map.
|
// as the index of the map.
|
||||||
//
|
//
|
||||||
// This tag MUST only be used on lists that have the "x-kubernetes-list-type"
|
// This tag MUST only be used on lists that have the "x-kubernetes-list-type"
|
||||||
// extension set to "map". Also, the values specified for this attribute must
|
// extension set to "map". Also, the values specified for this attribute must
|
||||||
// be a scalar typed field of the child structure (no nesting is supported).
|
// be a scalar typed field of the child structure (no nesting is supported).
|
||||||
|
//
|
||||||
|
// +optional
|
||||||
XListMapKeys []string `json:"x-kubernetes-list-map-keys,omitempty" protobuf:"bytes,41,rep,name=xKubernetesListMapKeys"`
|
XListMapKeys []string `json:"x-kubernetes-list-map-keys,omitempty" protobuf:"bytes,41,rep,name=xKubernetesListMapKeys"`
|
||||||
|
|
||||||
// x-kubernetes-list-type annotates a list to further describe its topology.
|
// x-kubernetes-list-type annotates an array to further describe its topology.
|
||||||
// This extension must only be used on lists and may have 3 possible values:
|
// This extension must only be used on lists and may have 3 possible values:
|
||||||
//
|
//
|
||||||
// 1) `atomic`: the list is treated as a single entity, like a scalar.
|
// 1) `atomic`: the list is treated as a single entity, like a scalar.
|
||||||
// Atomic lists will be entirely replaced when updated. This extension
|
// Atomic lists will be entirely replaced when updated. This extension
|
||||||
// may be used on any type of list (struct, scalar, ...).
|
// may be used on any type of list (struct, scalar, ...).
|
||||||
// 2) `set`:
|
// 2) `set`:
|
||||||
// Sets are lists that must not have multiple times the same value. Each
|
// Sets are lists that must not have multiple items with the same value. Each
|
||||||
// value must be a scalar (or another atomic type).
|
// value must be a scalar (or another atomic type).
|
||||||
// 3) `map`:
|
// 3) `map`:
|
||||||
// These lists are like maps in that their elements have a non-index key
|
// These lists are like maps in that their elements have a non-index key
|
||||||
// used to identify them. Order is preserved upon merge. The map tag
|
// used to identify them. Order is preserved upon merge. The map tag
|
||||||
// must only be used on a list with struct elements.
|
// must only be used on a list with elements of type object.
|
||||||
|
// Defaults to atomic for arrays.
|
||||||
|
// +optional
|
||||||
XListType *string `json:"x-kubernetes-list-type,omitempty" protobuf:"bytes,42,opt,name=xKubernetesListType"`
|
XListType *string `json:"x-kubernetes-list-type,omitempty" protobuf:"bytes,42,opt,name=xKubernetesListType"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -796,8 +796,12 @@ func ValidateCustomResourceDefinitionOpenAPISchema(schema *apiextensions.JSONSch
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if schema.XListType == nil && schema.Type == "array" {
|
||||||
|
allErrs = append(allErrs, field.Required(fldPath.Child("x-kubernetes-list-type"), "must be set if type is array"))
|
||||||
|
}
|
||||||
|
|
||||||
if schema.XListType != nil && *schema.XListType != "atomic" && *schema.XListType != "set" && *schema.XListType != "map" {
|
if schema.XListType != nil && *schema.XListType != "atomic" && *schema.XListType != "set" && *schema.XListType != "map" {
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("x-kubernetes-list-type"), *schema.XListType, "must be one of 'atomic', 'set', 'map', or unset"))
|
allErrs = append(allErrs, field.NotSupported(fldPath.Child("x-kubernetes-list-type"), *schema.XListType, []string{"atomic", "set", "map"}))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(schema.XListMapKeys) > 0 && (schema.XListType == nil || *schema.XListType != "map") {
|
if len(schema.XListMapKeys) > 0 && (schema.XListType == nil || *schema.XListType != "map") {
|
||||||
@ -808,6 +812,35 @@ func ValidateCustomResourceDefinitionOpenAPISchema(schema *apiextensions.JSONSch
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(schema.XListMapKeys) == 0 && schema.XListType != nil && *schema.XListType == "map" {
|
||||||
|
allErrs = append(allErrs, field.Required(fldPath.Child("x-kubernetes-list-map-keys"), "must not be empty if x-kubernetes-list-type is map"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if schema.Items != nil && schema.Items.Schema == nil && schema.XListType != nil && *schema.XListType == "map" {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("items"), schema.Items, "must only have a single schema if x-kubernetes-list-type is map"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if schema.Items != nil && schema.Items.Schema != nil && schema.Items.Schema.Type != "object" && schema.XListType != nil && *schema.XListType == "map" {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("items").Child("type"), schema.Items.Schema.Type, "must be object if parent array's x-kubernetes-list-type is map"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if schema.Items != nil && schema.Items.Schema != nil && schema.Items.Schema.Type == "object" && schema.XListType != nil && *schema.XListType == "map" {
|
||||||
|
keys := map[string]struct{}{}
|
||||||
|
for _, k := range schema.XListMapKeys {
|
||||||
|
if s, ok := schema.Items.Schema.Properties[k]; ok {
|
||||||
|
if s.Type == "array" || s.Type == "object" {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("items").Child("properties").Child(k).Child("type"), schema.Items.Schema.Type, "must be a scalar type if parent array's x-kubernetes-list-type is map"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("x-kubernetes-list-map-keys"), schema.XListMapKeys, "entries must all be names of item properties"))
|
||||||
|
}
|
||||||
|
if _, ok := keys[k]; ok {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("x-kubernetes-list-map-keys"), schema.XListMapKeys, "must not contain duplicate entries"))
|
||||||
|
}
|
||||||
|
keys[k] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6510,6 +6510,9 @@ func TestValidateCustomResourceDefinitionValidation(t *testing.T) {
|
|||||||
"array": {
|
"array": {
|
||||||
Type: "array",
|
Type: "array",
|
||||||
Nullable: true,
|
Nullable: true,
|
||||||
|
|
||||||
|
// This value is defaulted
|
||||||
|
XListType: strPtr("atomic"),
|
||||||
},
|
},
|
||||||
"number": {
|
"number": {
|
||||||
Type: "number",
|
Type: "number",
|
||||||
@ -6577,7 +6580,7 @@ func TestValidateCustomResourceDefinitionValidation(t *testing.T) {
|
|||||||
input: apiextensions.CustomResourceValidation{
|
input: apiextensions.CustomResourceValidation{
|
||||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
||||||
Type: "object",
|
Type: "object",
|
||||||
XListType: strPtr("map"),
|
XListType: strPtr("set"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantError: true,
|
wantError: true,
|
||||||
@ -6586,13 +6589,32 @@ func TestValidateCustomResourceDefinitionValidation(t *testing.T) {
|
|||||||
name: "unset type with list type extension set",
|
name: "unset type with list type extension set",
|
||||||
input: apiextensions.CustomResourceValidation{
|
input: apiextensions.CustomResourceValidation{
|
||||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
||||||
XListType: strPtr("map"),
|
XListType: strPtr("set"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantError: true,
|
wantError: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid list type extension with list map keys extension set",
|
name: "unset list type extension with type array",
|
||||||
|
input: apiextensions.CustomResourceValidation{
|
||||||
|
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
||||||
|
Type: "array",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid list type extension",
|
||||||
|
input: apiextensions.CustomResourceValidation{
|
||||||
|
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
||||||
|
Type: "array",
|
||||||
|
XListType: strPtr("invalid"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid list type extension with list map keys extension non-empty",
|
||||||
input: apiextensions.CustomResourceValidation{
|
input: apiextensions.CustomResourceValidation{
|
||||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
||||||
Type: "array",
|
Type: "array",
|
||||||
@ -6603,25 +6625,142 @@ func TestValidateCustomResourceDefinitionValidation(t *testing.T) {
|
|||||||
wantError: true,
|
wantError: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "unset list type extension with list map keys extension set",
|
name: "unset list type extension with list map keys extension non-empty",
|
||||||
input: apiextensions.CustomResourceValidation{
|
input: apiextensions.CustomResourceValidation{
|
||||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
||||||
Type: "array",
|
|
||||||
XListMapKeys: []string{"key"},
|
XListMapKeys: []string{"key"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantError: true,
|
wantError: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid list type",
|
name: "empty list map keys extension with list type extension map",
|
||||||
input: apiextensions.CustomResourceValidation{
|
input: apiextensions.CustomResourceValidation{
|
||||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
||||||
Type: "array",
|
Type: "array",
|
||||||
XListType: strPtr("invalid"),
|
XListType: strPtr("map"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantError: true,
|
wantError: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "multiple schema items with list type extension map",
|
||||||
|
input: apiextensions.CustomResourceValidation{
|
||||||
|
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
||||||
|
Type: "array",
|
||||||
|
XListType: strPtr("map"),
|
||||||
|
XListMapKeys: []string{"key"},
|
||||||
|
Items: &apiextensions.JSONSchemaPropsOrArray{
|
||||||
|
JSONSchemas: []apiextensions.JSONSchemaProps{
|
||||||
|
{
|
||||||
|
Type: "string",
|
||||||
|
}, {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non object item with list type extension map",
|
||||||
|
input: apiextensions.CustomResourceValidation{
|
||||||
|
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
||||||
|
Type: "array",
|
||||||
|
XListType: strPtr("map"),
|
||||||
|
XListMapKeys: []string{"key"},
|
||||||
|
Items: &apiextensions.JSONSchemaPropsOrArray{
|
||||||
|
Schema: &apiextensions.JSONSchemaProps{
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "items with key missing from properties with list type extension map",
|
||||||
|
input: apiextensions.CustomResourceValidation{
|
||||||
|
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
||||||
|
Type: "array",
|
||||||
|
XListType: strPtr("map"),
|
||||||
|
XListMapKeys: []string{"key"},
|
||||||
|
Items: &apiextensions.JSONSchemaPropsOrArray{
|
||||||
|
Schema: &apiextensions.JSONSchemaProps{
|
||||||
|
Type: "object",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "items with non scalar key property type with list type extension map",
|
||||||
|
input: apiextensions.CustomResourceValidation{
|
||||||
|
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
||||||
|
Type: "array",
|
||||||
|
XListType: strPtr("map"),
|
||||||
|
XListMapKeys: []string{"key"},
|
||||||
|
Items: &apiextensions.JSONSchemaPropsOrArray{
|
||||||
|
Schema: &apiextensions.JSONSchemaProps{
|
||||||
|
Type: "object",
|
||||||
|
Properties: map[string]apiextensions.JSONSchemaProps{
|
||||||
|
"key": {
|
||||||
|
Type: "object",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "duplicate map keys with list type extension map",
|
||||||
|
input: apiextensions.CustomResourceValidation{
|
||||||
|
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
||||||
|
Type: "array",
|
||||||
|
XListType: strPtr("map"),
|
||||||
|
XListMapKeys: []string{"key", "key"},
|
||||||
|
Items: &apiextensions.JSONSchemaPropsOrArray{
|
||||||
|
Schema: &apiextensions.JSONSchemaProps{
|
||||||
|
Type: "object",
|
||||||
|
Properties: map[string]apiextensions.JSONSchemaProps{
|
||||||
|
"key": {
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "allowed schema with list type extension map",
|
||||||
|
input: apiextensions.CustomResourceValidation{
|
||||||
|
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
||||||
|
Type: "array",
|
||||||
|
XListType: strPtr("map"),
|
||||||
|
XListMapKeys: []string{"keyA", "keyB"},
|
||||||
|
Items: &apiextensions.JSONSchemaPropsOrArray{
|
||||||
|
Schema: &apiextensions.JSONSchemaProps{
|
||||||
|
Type: "object",
|
||||||
|
Properties: map[string]apiextensions.JSONSchemaProps{
|
||||||
|
"keyA": {
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
"keyB": {
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantError: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@ -655,7 +655,11 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd
|
|||||||
}
|
}
|
||||||
specs = append(specs, s)
|
specs = append(specs, s)
|
||||||
}
|
}
|
||||||
mergedOpenAPI := builder.MergeSpecs(r.staticOpenAPISpec, specs...)
|
mergedOpenAPI, err := builder.MergeSpecs(r.staticOpenAPISpec, specs...)
|
||||||
|
if err != nil {
|
||||||
|
utilruntime.HandleError(err)
|
||||||
|
return nil, fmt.Errorf("the server could not properly merge the CR schema")
|
||||||
|
}
|
||||||
openAPIModels, err = utilopenapi.ToProtoModels(mergedOpenAPI)
|
openAPIModels, err = utilopenapi.ToProtoModels(mergedOpenAPI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utilruntime.HandleError(err)
|
utilruntime.HandleError(err)
|
||||||
|
@ -82,7 +82,7 @@ func (x *Extensions) toGoOpenAPI(ret *spec.Schema) {
|
|||||||
ret.VendorExtensible.AddExtension("x-kubernetes-list-map-keys", x.XListMapKeys)
|
ret.VendorExtensible.AddExtension("x-kubernetes-list-map-keys", x.XListMapKeys)
|
||||||
}
|
}
|
||||||
if x.XListType != nil {
|
if x.XListType != nil {
|
||||||
ret.VendorExtensible.AddExtension("x-kubernetes-list-type", x.XListType)
|
ret.VendorExtensible.AddExtension("x-kubernetes-list-type", *x.XListType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,12 +108,12 @@ type Extensions struct {
|
|||||||
// Atomic lists will be entirely replaced when updated. This extension
|
// Atomic lists will be entirely replaced when updated. This extension
|
||||||
// may be used on any type of list (struct, scalar, ...).
|
// may be used on any type of list (struct, scalar, ...).
|
||||||
// 2) `set`:
|
// 2) `set`:
|
||||||
// Sets are lists that must not have multiple times the same value. Each
|
// Sets are lists that must not have multiple items with the same value. Each
|
||||||
// value must be a scalar (or another atomic type).
|
// value must be a scalar (or another atomic type).
|
||||||
// 3) `map`:
|
// 3) `map`:
|
||||||
// These lists are like maps in that their elements have a non-index key
|
// These lists are like maps in that their elements have a non-index key
|
||||||
// used to identify them. Order is preserved upon merge. The map tag
|
// used to identify them. Order is preserved upon merge. The map tag
|
||||||
// must only be used on a list with struct elements.
|
// must only be used on a list with elements of type object.
|
||||||
XListType *string
|
XListType *string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,7 +250,7 @@ func ConvertJSONSchemaPropsWithPostProcess(in *apiextensions.JSONSchemaProps, ou
|
|||||||
out.VendorExtensible.AddExtension("x-kubernetes-list-map-keys", in.XListMapKeys)
|
out.VendorExtensible.AddExtension("x-kubernetes-list-map-keys", in.XListMapKeys)
|
||||||
}
|
}
|
||||||
if in.XListType != nil {
|
if in.XListType != nil {
|
||||||
out.VendorExtensible.AddExtension("x-kubernetes-list-type", in.XListType)
|
out.VendorExtensible.AddExtension("x-kubernetes-list-type", *in.XListType)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -35,6 +35,7 @@ import (
|
|||||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
utildiff "k8s.io/apimachinery/pkg/util/diff"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
k8sclientset "k8s.io/client-go/kubernetes"
|
k8sclientset "k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/rest"
|
"k8s.io/client-go/rest"
|
||||||
@ -507,7 +508,8 @@ func waitForDefinition(c k8sclientset.Interface, name string, schema []byte) err
|
|||||||
// drop properties and extension that we added
|
// drop properties and extension that we added
|
||||||
dropDefaults(&d)
|
dropDefaults(&d)
|
||||||
if !apiequality.Semantic.DeepEqual(expect, d) {
|
if !apiequality.Semantic.DeepEqual(expect, d) {
|
||||||
return false, fmt.Sprintf("spec.SwaggerProps.Definitions[\"%s\"] not match; expect: %v, actual: %v", name, expect, d)
|
diff := utildiff.ObjectGoPrintSideBySide(expect, d)
|
||||||
|
return false, fmt.Sprintf("spec.SwaggerProps.Definitions[\"%s\"] not match; expect: %v, actual: %v\n%v", name, expect, d, diff)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true, ""
|
return true, ""
|
||||||
@ -627,6 +629,7 @@ properties:
|
|||||||
bars:
|
bars:
|
||||||
description: List of Bars and their specs.
|
description: List of Bars and their specs.
|
||||||
type: array
|
type: array
|
||||||
|
x-kubernetes-list-type: atomic
|
||||||
items:
|
items:
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
@ -643,6 +646,7 @@ properties:
|
|||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
|
x-kubernetes-list-type: atomic
|
||||||
status:
|
status:
|
||||||
description: Status of Foo
|
description: Status of Foo
|
||||||
type: object
|
type: object
|
||||||
@ -650,6 +654,7 @@ properties:
|
|||||||
bars:
|
bars:
|
||||||
description: List of Bars and their statuses.
|
description: List of Bars and their statuses.
|
||||||
type: array
|
type: array
|
||||||
|
x-kubernetes-list-type: atomic
|
||||||
items:
|
items:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@ -681,6 +686,7 @@ properties:
|
|||||||
bars:
|
bars:
|
||||||
description: List of Bars and their statuses.
|
description: List of Bars and their statuses.
|
||||||
type: array
|
type: array
|
||||||
|
x-kubernetes-list-type: atomic
|
||||||
items:
|
items:
|
||||||
type: object`)
|
type: object`)
|
||||||
|
|
||||||
@ -702,6 +708,7 @@ properties:
|
|||||||
bars:
|
bars:
|
||||||
description: List of Bars and their statuses.
|
description: List of Bars and their statuses.
|
||||||
type: array
|
type: array
|
||||||
|
x-kubernetes-list-type: atomic
|
||||||
items:
|
items:
|
||||||
type: object`)
|
type: object`)
|
||||||
|
|
||||||
@ -723,5 +730,6 @@ properties:
|
|||||||
bars:
|
bars:
|
||||||
description: List of Bars and their statuses.
|
description: List of Bars and their statuses.
|
||||||
type: array
|
type: array
|
||||||
|
x-kubernetes-list-type: atomic
|
||||||
items:
|
items:
|
||||||
type: object`)
|
type: object`)
|
||||||
|
@ -19,12 +19,14 @@ package apiserver
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||||
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
||||||
"k8s.io/apiextensions-apiserver/test/integration/fixtures"
|
"k8s.io/apiextensions-apiserver/test/integration/fixtures"
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
genericfeatures "k8s.io/apiserver/pkg/features"
|
genericfeatures "k8s.io/apiserver/pkg/features"
|
||||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
@ -83,6 +85,7 @@ spec:
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create custom resource with apply: %v:\n%v", err, string(result))
|
t.Fatalf("failed to create custom resource with apply: %v:\n%v", err, string(result))
|
||||||
}
|
}
|
||||||
|
verifyReplicas(t, result, 1)
|
||||||
|
|
||||||
// Patch object to change the number of replicas
|
// Patch object to change the number of replicas
|
||||||
result, err = rest.Patch(types.MergePatchType).
|
result, err = rest.Patch(types.MergePatchType).
|
||||||
@ -93,6 +96,7 @@ spec:
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to update number of replicas with merge patch: %v:\n%v", err, string(result))
|
t.Fatalf("failed to update number of replicas with merge patch: %v:\n%v", err, string(result))
|
||||||
}
|
}
|
||||||
|
verifyReplicas(t, result, 5)
|
||||||
|
|
||||||
// Re-apply, we should get conflicts now, since the number of replicas was changed.
|
// Re-apply, we should get conflicts now, since the number of replicas was changed.
|
||||||
result, err = rest.Patch(types.ApplyPatchType).
|
result, err = rest.Patch(types.ApplyPatchType).
|
||||||
@ -108,8 +112,8 @@ spec:
|
|||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("Expecting to get conflicts as API error")
|
t.Fatalf("Expecting to get conflicts as API error")
|
||||||
}
|
}
|
||||||
if len(status.Status().Details.Causes) < 1 {
|
if len(status.Status().Details.Causes) != 1 {
|
||||||
t.Fatalf("Expecting to get at least one conflict when applying object after updating replicas, got: %v", status.Status().Details.Causes)
|
t.Fatalf("Expecting to get one conflict when applying object after updating replicas, got: %v", status.Status().Details.Causes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-apply with force, should work fine.
|
// Re-apply with force, should work fine.
|
||||||
@ -123,6 +127,7 @@ spec:
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to apply object with force after updating replicas: %v:\n%v", err, string(result))
|
t.Fatalf("failed to apply object with force after updating replicas: %v:\n%v", err, string(result))
|
||||||
}
|
}
|
||||||
|
verifyReplicas(t, result, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestApplyCRDStructuralSchema tests that when a CRD has a structural schema in its validation field,
|
// TestApplyCRDStructuralSchema tests that when a CRD has a structural schema in its validation field,
|
||||||
@ -239,27 +244,38 @@ spec:
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create custom resource with apply: %v:\n%v", err, string(result))
|
t.Fatalf("failed to create custom resource with apply: %v:\n%v", err, string(result))
|
||||||
}
|
}
|
||||||
|
verifyNumFinalizers(t, result, 1)
|
||||||
|
verifyFinalizersIncludes(t, result, "test-finalizer")
|
||||||
|
verifyReplicas(t, result, 1)
|
||||||
|
verifyNumPorts(t, result, 1)
|
||||||
|
|
||||||
// Patch object to add another finalizer to the finalizers list
|
// Patch object to add another finalizer to the finalizers list
|
||||||
result, err = rest.Patch(types.MergePatchType).
|
result, err = rest.Patch(types.MergePatchType).
|
||||||
AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
|
AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
|
||||||
Name(name).
|
Name(name).
|
||||||
Body([]byte(`{"metadata":{"finalizers":["another-one"]}}`)).
|
Body([]byte(`{"metadata":{"finalizers":["test-finalizer","another-one"]}}`)).
|
||||||
DoRaw()
|
DoRaw()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to add finalizer with merge patch: %v:\n%v", err, string(result))
|
t.Fatalf("failed to add finalizer with merge patch: %v:\n%v", err, string(result))
|
||||||
}
|
}
|
||||||
|
verifyNumFinalizers(t, result, 2)
|
||||||
|
verifyFinalizersIncludes(t, result, "test-finalizer")
|
||||||
|
verifyFinalizersIncludes(t, result, "another-one")
|
||||||
|
|
||||||
// Re-apply the same config, should work fine, since finalizers should have the list-type extension 'set'.
|
// Re-apply the same config, should work fine, since finalizers should have the list-type extension 'set'.
|
||||||
result, err = rest.Patch(types.ApplyPatchType).
|
result, err = rest.Patch(types.ApplyPatchType).
|
||||||
AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
|
AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
|
||||||
Name(name).
|
Name(name).
|
||||||
Param("fieldManager", "apply_test").
|
Param("fieldManager", "apply_test").
|
||||||
|
SetHeader("Accept", "application/json").
|
||||||
Body(yamlBody).
|
Body(yamlBody).
|
||||||
DoRaw()
|
DoRaw()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to apply same config after adding a finalizer: %v:\n%v", err, string(result))
|
t.Fatalf("failed to apply same config after adding a finalizer: %v:\n%v", err, string(result))
|
||||||
}
|
}
|
||||||
|
verifyNumFinalizers(t, result, 2)
|
||||||
|
verifyFinalizersIncludes(t, result, "test-finalizer")
|
||||||
|
verifyFinalizersIncludes(t, result, "another-one")
|
||||||
|
|
||||||
// Patch object to change the number of replicas
|
// Patch object to change the number of replicas
|
||||||
result, err = rest.Patch(types.MergePatchType).
|
result, err = rest.Patch(types.MergePatchType).
|
||||||
@ -270,6 +286,7 @@ spec:
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to update number of replicas with merge patch: %v:\n%v", err, string(result))
|
t.Fatalf("failed to update number of replicas with merge patch: %v:\n%v", err, string(result))
|
||||||
}
|
}
|
||||||
|
verifyReplicas(t, result, 5)
|
||||||
|
|
||||||
// Re-apply, we should get conflicts now, since the number of replicas was changed.
|
// Re-apply, we should get conflicts now, since the number of replicas was changed.
|
||||||
result, err = rest.Patch(types.ApplyPatchType).
|
result, err = rest.Patch(types.ApplyPatchType).
|
||||||
@ -285,8 +302,8 @@ spec:
|
|||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("Expecting to get conflicts as API error")
|
t.Fatalf("Expecting to get conflicts as API error")
|
||||||
}
|
}
|
||||||
if len(status.Status().Details.Causes) < 1 {
|
if len(status.Status().Details.Causes) != 1 {
|
||||||
t.Fatalf("Expecting to get at least one conflict when applying object after updating replicas, got: %v", status.Status().Details.Causes)
|
t.Fatalf("Expecting to get one conflict when applying object after updating replicas, got: %v", status.Status().Details.Causes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-apply with force, should work fine.
|
// Re-apply with force, should work fine.
|
||||||
@ -300,6 +317,7 @@ spec:
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to apply object with force after updating replicas: %v:\n%v", err, string(result))
|
t.Fatalf("failed to apply object with force after updating replicas: %v:\n%v", err, string(result))
|
||||||
}
|
}
|
||||||
|
verifyReplicas(t, result, 1)
|
||||||
|
|
||||||
// New applier tries to edit an existing list item, we should get conflicts.
|
// New applier tries to edit an existing list item, we should get conflicts.
|
||||||
result, err = rest.Patch(types.ApplyPatchType).
|
result, err = rest.Patch(types.ApplyPatchType).
|
||||||
@ -324,8 +342,8 @@ spec:
|
|||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("Expecting to get conflicts as API error")
|
t.Fatalf("Expecting to get conflicts as API error")
|
||||||
}
|
}
|
||||||
if len(status.Status().Details.Causes) < 1 {
|
if len(status.Status().Details.Causes) != 1 {
|
||||||
t.Fatalf("Expecting to get at least one conflict when a different applier updates existing list item, got: %v", status.Status().Details.Causes)
|
t.Fatalf("Expecting to get one conflict when a different applier updates existing list item, got: %v", status.Status().Details.Causes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// New applier tries to add a new list item, should work fine.
|
// New applier tries to add a new list item, should work fine.
|
||||||
@ -343,10 +361,12 @@ spec:
|
|||||||
- name: "y"
|
- name: "y"
|
||||||
containerPort: 8080
|
containerPort: 8080
|
||||||
protocol: TCP`, apiVersion, kind, name))).
|
protocol: TCP`, apiVersion, kind, name))).
|
||||||
|
SetHeader("Accept", "application/json").
|
||||||
DoRaw()
|
DoRaw()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to add a new list item to the object as a different applier: %v:\n%v", err, string(result))
|
t.Fatalf("failed to add a new list item to the object as a different applier: %v:\n%v", err, string(result))
|
||||||
}
|
}
|
||||||
|
verifyNumPorts(t, result, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestApplyCRDNonStructuralSchema tests that when a CRD has a non-structural schema in its validation field,
|
// TestApplyCRDNonStructuralSchema tests that when a CRD has a non-structural schema in its validation field,
|
||||||
@ -430,27 +450,37 @@ spec:
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create custom resource with apply: %v:\n%v", err, string(result))
|
t.Fatalf("failed to create custom resource with apply: %v:\n%v", err, string(result))
|
||||||
}
|
}
|
||||||
|
verifyNumFinalizers(t, result, 1)
|
||||||
|
verifyFinalizersIncludes(t, result, "test-finalizer")
|
||||||
|
verifyReplicas(t, result, 1.0)
|
||||||
|
|
||||||
// Patch object to add another finalizer to the finalizers list
|
// Patch object to add another finalizer to the finalizers list
|
||||||
result, err = rest.Patch(types.MergePatchType).
|
result, err = rest.Patch(types.MergePatchType).
|
||||||
AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
|
AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
|
||||||
Name(name).
|
Name(name).
|
||||||
Body([]byte(`{"metadata":{"finalizers":["another-one"]}}`)).
|
Body([]byte(`{"metadata":{"finalizers":["test-finalizer","another-one"]}}`)).
|
||||||
DoRaw()
|
DoRaw()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to add finalizer with merge patch: %v:\n%v", err, string(result))
|
t.Fatalf("failed to add finalizer with merge patch: %v:\n%v", err, string(result))
|
||||||
}
|
}
|
||||||
|
verifyNumFinalizers(t, result, 2)
|
||||||
|
verifyFinalizersIncludes(t, result, "test-finalizer")
|
||||||
|
verifyFinalizersIncludes(t, result, "another-one")
|
||||||
|
|
||||||
// Re-apply the same config, should work fine, since finalizers should have the list-type extension 'set'.
|
// Re-apply the same config, should work fine, since finalizers should have the list-type extension 'set'.
|
||||||
result, err = rest.Patch(types.ApplyPatchType).
|
result, err = rest.Patch(types.ApplyPatchType).
|
||||||
AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
|
AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
|
||||||
Name(name).
|
Name(name).
|
||||||
Param("fieldManager", "apply_test").
|
Param("fieldManager", "apply_test").
|
||||||
|
SetHeader("Accept", "application/json").
|
||||||
Body(yamlBody).
|
Body(yamlBody).
|
||||||
DoRaw()
|
DoRaw()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to apply same config after adding a finalizer: %v:\n%v", err, string(result))
|
t.Fatalf("failed to apply same config after adding a finalizer: %v:\n%v", err, string(result))
|
||||||
}
|
}
|
||||||
|
verifyNumFinalizers(t, result, 2)
|
||||||
|
verifyFinalizersIncludes(t, result, "test-finalizer")
|
||||||
|
verifyFinalizersIncludes(t, result, "another-one")
|
||||||
|
|
||||||
// Patch object to change the number of replicas
|
// Patch object to change the number of replicas
|
||||||
result, err = rest.Patch(types.MergePatchType).
|
result, err = rest.Patch(types.MergePatchType).
|
||||||
@ -461,6 +491,7 @@ spec:
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to update number of replicas with merge patch: %v:\n%v", err, string(result))
|
t.Fatalf("failed to update number of replicas with merge patch: %v:\n%v", err, string(result))
|
||||||
}
|
}
|
||||||
|
verifyReplicas(t, result, 5.0)
|
||||||
|
|
||||||
// Re-apply, we should get conflicts now, since the number of replicas was changed.
|
// Re-apply, we should get conflicts now, since the number of replicas was changed.
|
||||||
result, err = rest.Patch(types.ApplyPatchType).
|
result, err = rest.Patch(types.ApplyPatchType).
|
||||||
@ -476,8 +507,8 @@ spec:
|
|||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("Expecting to get conflicts as API error")
|
t.Fatalf("Expecting to get conflicts as API error")
|
||||||
}
|
}
|
||||||
if len(status.Status().Details.Causes) < 1 {
|
if len(status.Status().Details.Causes) != 1 {
|
||||||
t.Fatalf("Expecting to get at least one conflict when applying object after updating replicas, got: %v", status.Status().Details.Causes)
|
t.Fatalf("Expecting to get one conflict when applying object after updating replicas, got: %v", status.Status().Details.Causes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-apply with force, should work fine.
|
// Re-apply with force, should work fine.
|
||||||
@ -491,4 +522,88 @@ spec:
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to apply object with force after updating replicas: %v:\n%v", err, string(result))
|
t.Fatalf("failed to apply object with force after updating replicas: %v:\n%v", err, string(result))
|
||||||
}
|
}
|
||||||
|
verifyReplicas(t, result, 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifyNumFinalizers checks that len(.metadata.finalizers) == n
|
||||||
|
func verifyNumFinalizers(t *testing.T, b []byte, n int) {
|
||||||
|
obj := unstructured.Unstructured{}
|
||||||
|
err := obj.UnmarshalJSON(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to unmarshal response: %v", err)
|
||||||
|
}
|
||||||
|
if actual, expected := len(obj.GetFinalizers()), n; actual != expected {
|
||||||
|
t.Fatalf("expected %v finalizers but got %v:\n%v", expected, actual, string(b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifyFinalizersIncludes checks that .metadata.finalizers includes e
|
||||||
|
func verifyFinalizersIncludes(t *testing.T, b []byte, e string) {
|
||||||
|
obj := unstructured.Unstructured{}
|
||||||
|
err := obj.UnmarshalJSON(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to unmarshal response: %v", err)
|
||||||
|
}
|
||||||
|
for _, a := range obj.GetFinalizers() {
|
||||||
|
if a == e {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Fatalf("expected finalizers to include %q but got: %v", e, obj.GetFinalizers())
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifyReplicas checks that .spec.replicas == r
|
||||||
|
func verifyReplicas(t *testing.T, b []byte, r int) {
|
||||||
|
obj := unstructured.Unstructured{}
|
||||||
|
err := obj.UnmarshalJSON(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to find replicas number in response: %v:\n%v", err, string(b))
|
||||||
|
}
|
||||||
|
spec, ok := obj.Object["spec"]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("failed to find replicas number in response:\n%v", string(b))
|
||||||
|
}
|
||||||
|
specMap, ok := spec.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("failed to find replicas number in response:\n%v", string(b))
|
||||||
|
}
|
||||||
|
replicas, ok := specMap["replicas"]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("failed to find replicas number in response:\n%v", string(b))
|
||||||
|
}
|
||||||
|
replicasNumber, ok := replicas.(int64)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("failed to find replicas number in response: expected int64 but got: %v", reflect.TypeOf(replicas))
|
||||||
|
}
|
||||||
|
if actual, expected := replicasNumber, int64(r); actual != expected {
|
||||||
|
t.Fatalf("expected %v ports but got %v:\n%v", expected, actual, string(b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifyNumPorts checks that len(.spec.ports) == n
|
||||||
|
func verifyNumPorts(t *testing.T, b []byte, n int) {
|
||||||
|
obj := unstructured.Unstructured{}
|
||||||
|
err := obj.UnmarshalJSON(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to find ports list in response: %v:\n%v", err, string(b))
|
||||||
|
}
|
||||||
|
spec, ok := obj.Object["spec"]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("failed to find ports list in response:\n%v", string(b))
|
||||||
|
}
|
||||||
|
specMap, ok := spec.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("failed to find ports list in response:\n%v", string(b))
|
||||||
|
}
|
||||||
|
ports, ok := specMap["ports"]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("failed to find ports list in response:\n%v", string(b))
|
||||||
|
}
|
||||||
|
portsList, ok := ports.([]interface{})
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("failed to find ports list in response: expected array but got: %v", reflect.TypeOf(ports))
|
||||||
|
}
|
||||||
|
if actual, expected := len(portsList), n; actual != expected {
|
||||||
|
t.Fatalf("expected %v ports but got %v:\n%v", expected, actual, string(b))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user