bugfix: disable ratcheting for typemeta fields

The metadata fields may undergo pre-processing that often does not reflect the input. Example is `apiVersion` if conversion takes place.
This commit is contained in:
Alexander Zielenski 2023-11-01 10:06:56 -07:00
parent 7353f52bc8
commit 20ec766adf
3 changed files with 119 additions and 1 deletions

View File

@ -210,8 +210,16 @@ func (r ratchetingOptions) shouldRatchetError() bool {
func (r ratchetingOptions) key(field string) ratchetingOptions {
if r.currentCorrelation == nil {
return r
} else if r.nearestParentCorrelation == nil && (field == "apiVersion" || field == "kind") {
// We cannot ratchet changes to the APIVersion and kind fields field since
// they aren't visible. (both old and new are converted to the same type)
//
return ratchetingOptions{}
}
// nearestParentCorrelation is always non-nil except for the root node.
// The below line ensures that the next nearestParentCorrelation is set
// to a non-nil r.currentCorrelation
return ratchetingOptions{currentCorrelation: r.currentCorrelation.Key(field), nearestParentCorrelation: r.currentCorrelation}
}

View File

@ -3622,6 +3622,106 @@ func TestRatcheting(t *testing.T) {
`rule compile error: compilation failed: ERROR: <input>:1:1: undeclared reference to 'asdausidyhASDNJm'`,
},
},
{
name: "typemeta fields are not ratcheted",
schema: mustSchema(`
type: object
properties:
apiVersion:
type: string
x-kubernetes-validations:
- rule: self == "v1"
kind:
type: string
x-kubernetes-validations:
- rule: self == "Pod"
`),
oldObj: mustUnstructured(`
apiVersion: v2
kind: Baz
`),
newObj: mustUnstructured(`
apiVersion: v2
kind: Baz
`),
errors: []string{
`root.apiVersion: Invalid value: "string": failed rule: self == "v1"`,
`root.kind: Invalid value: "string": failed rule: self == "Pod"`,
},
},
{
name: "nested typemeta fields may still be ratcheted",
schema: mustSchema(`
type: object
properties:
list:
type: array
x-kubernetes-list-type: map
x-kubernetes-list-map-keys: ["name"]
maxItems: 2
items:
type: object
properties:
name:
type: string
apiVersion:
type: string
x-kubernetes-validations:
- rule: self == "v1"
kind:
type: string
x-kubernetes-validations:
- rule: self == "Pod"
subField:
type: object
properties:
apiVersion:
type: string
x-kubernetes-validations:
- rule: self == "v1"
kind:
type: string
x-kubernetes-validations:
- rule: self == "Pod"
otherField:
type: string
`),
oldObj: mustUnstructured(`
subField:
apiVersion: v2
kind: Baz
list:
- name: entry1
apiVersion: v2
kind: Baz
- name: entry2
apiVersion: v3
kind: Bar
`),
newObj: mustUnstructured(`
subField:
apiVersion: v2
kind: Baz
otherField: newValue
list:
- name: entry1
apiVersion: v2
kind: Baz
otherField: newValue2
- name: entry2
apiVersion: v3
kind: Bar
otherField: newValue3
`),
warnings: []string{
`root.subField.apiVersion: Invalid value: "string": failed rule: self == "v1"`,
`root.subField.kind: Invalid value: "string": failed rule: self == "Pod"`,
`root.list[0].apiVersion: Invalid value: "string": failed rule: self == "v1"`,
`root.list[0].kind: Invalid value: "string": failed rule: self == "Pod"`,
`root.list[1].apiVersion: Invalid value: "string": failed rule: self == "v1"`,
`root.list[1].kind: Invalid value: "string": failed rule: self == "Pod"`,
},
},
}
for _, c := range cases {

View File

@ -165,7 +165,13 @@ func (r *ratchetingValueValidator) Validate(new interface{}) *validate.Result {
// If the old value cannot be correlated, then default validation is used.
func (r *ratchetingValueValidator) SubPropertyValidator(field string, schema *spec.Schema, rootSchema interface{}, root string, formats strfmt.Registry, options ...validate.Option) validate.ValueValidator {
childNode := r.correlation.Key(field)
if childNode == nil {
if childNode == nil || (r.path == "" && isTypeMetaField(field)) {
// Defer to default validation if we cannot correlate the old value
// or if we are validating the root object and the field is a metadata
// field.
//
// We cannot ratchet changes to the APIVersion field since they aren't visible.
// (both old and new are converted to the same type)
return validate.NewSchemaValidator(schema, rootSchema, root, formats, options...)
}
@ -210,3 +216,7 @@ func (r ratchetingValueValidator) SetPath(path string) {
func (r ratchetingValueValidator) Applies(source interface{}, valueKind reflect.Kind) bool {
return true
}
func isTypeMetaField(path string) bool {
return path == "kind" || path == "apiVersion"
}