PR feedback

This commit is contained in:
Kevin Delgado 2022-02-25 23:07:41 +00:00
parent 68ac44c2b1
commit bbf1208480

View File

@ -21,7 +21,6 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"net" "net"
@ -36,13 +35,10 @@ import (
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
apiextensionshelpers "k8s.io/apiextensions-apiserver/pkg/apihelpers" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
apiextensionsinternal "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apiextensions-apiserver/pkg/apiserver/conversion" "k8s.io/apiextensions-apiserver/pkg/apiserver/conversion"
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
structuraldefaulting "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting"
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake" "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake"
informers "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions" informers "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions"
listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1" listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1"
@ -55,7 +51,6 @@ import (
serializerjson "k8s.io/apimachinery/pkg/runtime/serializer/json" serializerjson "k8s.io/apimachinery/pkg/runtime/serializer/json"
"k8s.io/apimachinery/pkg/runtime/serializer/protobuf" "k8s.io/apimachinery/pkg/runtime/serializer/protobuf"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/endpoints/discovery" "k8s.io/apiserver/pkg/endpoints/discovery"
@ -649,7 +644,7 @@ func testHandlerConversion(t *testing.T, enableWatchCache bool) {
} }
func TestDecoder(t *testing.T) { func TestDecoder(t *testing.T) {
multiVersion := ` multiVersionJSON := `
{ {
"apiVersion": "stable.example.com/v1beta1", "apiVersion": "stable.example.com/v1beta1",
"kind": "MultiVersion", "kind": "MultiVersion",
@ -670,19 +665,40 @@ num: 1
num: 2 num: 2
unknown: foo` unknown: foo`
decoded := &unstructured.Unstructured{Object: map[string]interface{}{}} expectedObjUnknownNotPreserved := &unstructured.Unstructured{}
decoded.SetGroupVersionKind(schema.GroupVersionKind{ err := expectedObjUnknownNotPreserved.UnmarshalJSON([]byte(`
Group: "stable.example.com", {
Version: "v1beta1", "apiVersion": "stable.example.com/v1beta1",
Kind: "MultiVersion", "kind": "MultiVersion",
}) "metadata": {
decoded.SetName("my-mv") "creationTimestamp": null,
decoded.SetGeneration(1) "generation": 1,
unstructured.SetNestedField(decoded.Object, int64(2), "num") "name": "my-mv"
unstructured.SetNestedField(decoded.Object, nil, "metadata", "creationTimestamp") },
"num": 2
}
`))
if err != nil {
t.Fatal(err)
}
decodedPreserveUnknown := decoded.DeepCopy() expectedObjUnknownPreserved := &unstructured.Unstructured{}
unstructured.SetNestedField(decodedPreserveUnknown.Object, "foo", "unknown") err = expectedObjUnknownPreserved.UnmarshalJSON([]byte(`
{
"apiVersion": "stable.example.com/v1beta1",
"kind": "MultiVersion",
"metadata": {
"creationTimestamp": null,
"generation": 1,
"name": "my-mv"
},
"num": 2,
"unknown": "foo"
}
`))
if err != nil {
t.Fatal(err)
}
testcases := []struct { testcases := []struct {
name string name string
@ -690,42 +706,37 @@ unknown: foo`
yaml bool yaml bool
strictDecoding bool strictDecoding bool
preserveUnknownFields bool preserveUnknownFields bool
crd *v1.CustomResourceDefinition
expectedObj *unstructured.Unstructured expectedObj *unstructured.Unstructured
expectedErr error expectedErr error
}{ }{
{ {
name: "strict-decoding", name: "strict-decoding",
body: multiVersion, body: multiVersionJSON,
strictDecoding: true, strictDecoding: true,
crd: multiVersionFixture.DeepCopy(), expectedObj: expectedObjUnknownNotPreserved,
expectedObj: decoded,
expectedErr: errors.New(`strict decoding error: duplicate field "num", unknown field "unknown"`), expectedErr: errors.New(`strict decoding error: duplicate field "num", unknown field "unknown"`),
}, },
{ {
name: "non-strict-decoding", name: "non-strict-decoding",
body: multiVersion, body: multiVersionJSON,
strictDecoding: false, strictDecoding: false,
crd: multiVersionFixture.DeepCopy(), expectedObj: expectedObjUnknownNotPreserved,
expectedObj: decoded,
expectedErr: nil, expectedErr: nil,
}, },
{ {
name: "strict-decoding-preserve-unknown", name: "strict-decoding-preserve-unknown",
body: multiVersion, body: multiVersionJSON,
strictDecoding: true, strictDecoding: true,
preserveUnknownFields: true, preserveUnknownFields: true,
crd: multiVersionFixture.DeepCopy(), expectedObj: expectedObjUnknownPreserved,
expectedObj: decodedPreserveUnknown,
expectedErr: errors.New(`strict decoding error: duplicate field "num"`), expectedErr: errors.New(`strict decoding error: duplicate field "num"`),
}, },
{ {
name: "non-strict-decoding-preserve-unknown", name: "non-strict-decoding-preserve-unknown",
body: multiVersion, body: multiVersionJSON,
strictDecoding: false, strictDecoding: false,
preserveUnknownFields: true, preserveUnknownFields: true,
crd: multiVersionFixture.DeepCopy(), expectedObj: expectedObjUnknownPreserved,
expectedObj: decodedPreserveUnknown,
expectedErr: nil, expectedErr: nil,
}, },
{ {
@ -733,8 +744,7 @@ unknown: foo`
body: multiVersionYAML, body: multiVersionYAML,
yaml: true, yaml: true,
strictDecoding: true, strictDecoding: true,
crd: multiVersionFixture.DeepCopy(), expectedObj: expectedObjUnknownNotPreserved,
expectedObj: decoded,
expectedErr: errors.New(`strict decoding error: yaml: unmarshal errors: expectedErr: errors.New(`strict decoding error: yaml: unmarshal errors:
line 7: key "num" already set in map, unknown field "unknown"`), line 7: key "num" already set in map, unknown field "unknown"`),
}, },
@ -743,8 +753,7 @@ unknown: foo`
body: multiVersionYAML, body: multiVersionYAML,
yaml: true, yaml: true,
strictDecoding: false, strictDecoding: false,
crd: multiVersionFixture.DeepCopy(), expectedObj: expectedObjUnknownNotPreserved,
expectedObj: decoded,
expectedErr: nil, expectedErr: nil,
}, },
{ {
@ -753,8 +762,7 @@ unknown: foo`
yaml: true, yaml: true,
strictDecoding: true, strictDecoding: true,
preserveUnknownFields: true, preserveUnknownFields: true,
crd: multiVersionFixture.DeepCopy(), expectedObj: expectedObjUnknownPreserved,
expectedObj: decodedPreserveUnknown,
expectedErr: errors.New(`strict decoding error: yaml: unmarshal errors: expectedErr: errors.New(`strict decoding error: yaml: unmarshal errors:
line 7: key "num" already set in map`), line 7: key "num" already set in map`),
}, },
@ -764,21 +772,22 @@ unknown: foo`
yaml: true, yaml: true,
strictDecoding: false, strictDecoding: false,
preserveUnknownFields: true, preserveUnknownFields: true,
crd: multiVersionFixture.DeepCopy(), expectedObj: expectedObjUnknownPreserved,
expectedObj: decodedPreserveUnknown,
expectedErr: nil, expectedErr: nil,
}, },
} }
for _, tc := range testcases { for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
v := "v1beta1" v := "v1beta1"
var err error
structuralSchemas := map[string]*structuralschema.Structural{} structuralSchemas := map[string]*structuralschema.Structural{}
structuralSchemas[v], err = generateStructuralSchema(tc.crd, v) structuralSchema, err := structuralschema.NewStructural(&apiextensions.JSONSchemaProps{
Type: "object",
Properties: map[string]apiextensions.JSONSchemaProps{"num": {Type: "integer", Description: "v1beta1 num field"}},
})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
structuralSchemas[v] = structuralSchema
delegate := serializerjson.NewSerializerWithOptions(serializerjson.DefaultMetaFactory, unstructuredCreator{}, nil, serializerjson.SerializerOptions{tc.yaml, false, tc.strictDecoding}) delegate := serializerjson.NewSerializerWithOptions(serializerjson.DefaultMetaFactory, unstructuredCreator{}, nil, serializerjson.SerializerOptions{tc.yaml, false, tc.strictDecoding})
decoder := &schemaCoercingDecoder{ decoder := &schemaCoercingDecoder{
delegate: delegate, delegate: delegate,
@ -825,40 +834,6 @@ unknown: foo`
} }
func generateStructuralSchema(crd *v1.CustomResourceDefinition, version string) (*structuralschema.Structural, error) {
val, err := apiextensionshelpers.GetSchemaForVersion(crd, version)
if err != nil {
utilruntime.HandleError(err)
return nil, fmt.Errorf("the server could not properly serve the CR schema")
}
if val == nil {
return nil, nil
}
internalValidation := &apiextensionsinternal.CustomResourceValidation{}
if err := apiextensionsv1.Convert_v1_CustomResourceValidation_To_apiextensions_CustomResourceValidation(val, internalValidation, nil); err != nil {
return nil, fmt.Errorf("failed converting CRD validation to internal version: %v", err)
}
s, err := structuralschema.NewStructural(internalValidation.OpenAPIV3Schema)
if crd.Spec.PreserveUnknownFields == false && err != nil {
// This should never happen. If it does, it is a programming error.
utilruntime.HandleError(fmt.Errorf("failed to convert schema to structural: %v", err))
return nil, fmt.Errorf("the server could not properly serve the CR schema") // validation should avoid this
}
if crd.Spec.PreserveUnknownFields == false {
// we don't own s completely, e.g. defaults are not deep-copied. So better make a copy here.
s = s.DeepCopy()
if err := structuraldefaulting.PruneDefaults(s); err != nil {
// This should never happen. If it does, it is a programming error.
utilruntime.HandleError(fmt.Errorf("failed to prune defaults: %v", err))
return nil, fmt.Errorf("the server could not properly serve the CR schema") // validation should avoid this
}
}
return s, nil
}
type dummyAdmissionImpl struct{} type dummyAdmissionImpl struct{}
func (dummyAdmissionImpl) Handles(operation admission.Operation) bool { return false } func (dummyAdmissionImpl) Handles(operation admission.Operation) bool { return false }