@@ -650,6 +650,103 @@ func TestCustomResourceValidatorsWithBlockingErrors(t *testing.T) {
650
650
})
651
651
}
652
652
653
+ // TestCustomResourceValidatorsWithSchemaConversion tests CRD replacement with schema conversion issue should not panic.
654
+ func TestCustomResourceValidatorsWithSchemaConversion (t * testing.T ) {
655
+ server , err := apiservertesting .StartTestServer (t , apiservertesting .NewDefaultTestServerOptions (), nil , framework .SharedEtcd ())
656
+ if err != nil {
657
+ t .Fatal (err )
658
+ }
659
+ defer server .TearDownFn ()
660
+ config := server .ClientConfig
661
+
662
+ apiExtensionClient , err := clientset .NewForConfig (config )
663
+ if err != nil {
664
+ t .Fatal (err )
665
+ }
666
+ dynamicClient , err := dynamic .NewForConfig (config )
667
+ if err != nil {
668
+ t .Fatal (err )
669
+ }
670
+
671
+ // Create CRD with normal items+array schema
672
+ structuralWithValidators := crdWithSchema (t , "Structural" , structuralSchemaWithItemsUnderArray )
673
+ crd , err := fixtures .CreateNewV1CustomResourceDefinition (structuralWithValidators , apiExtensionClient , dynamicClient )
674
+ if err != nil {
675
+ t .Fatal (err )
676
+ }
677
+ gvr := schema.GroupVersionResource {
678
+ Group : crd .Spec .Group ,
679
+ Version : crd .Spec .Versions [0 ].Name ,
680
+ Resource : crd .Spec .Names .Plural ,
681
+ }
682
+ crClient := dynamicClient .Resource (gvr )
683
+
684
+ // Create a valid CR instance
685
+ name1 := names .SimpleNameGenerator .GenerateName ("cr-1" )
686
+ _ , err = crClient .Create (context .TODO (), & unstructured.Unstructured {Object : map [string ]interface {}{
687
+ "apiVersion" : gvr .Group + "/" + gvr .Version ,
688
+ "kind" : crd .Spec .Names .Kind ,
689
+ "metadata" : map [string ]interface {}{
690
+ "name" : name1 ,
691
+ },
692
+ "spec" : map [string ]interface {}{
693
+ "backend" : []interface {}{
694
+ map [string ]interface {}{
695
+ "replicas" : 8 ,
696
+ },
697
+ },
698
+ },
699
+ }}, metav1.CreateOptions {})
700
+ if err != nil {
701
+ t .Errorf ("Failed to create custom resource: %v" , err )
702
+ }
703
+ crd , err = apiExtensionClient .ApiextensionsV1 ().CustomResourceDefinitions ().Get (context .TODO (), crd .Name , metav1.GetOptions {})
704
+ if err != nil {
705
+ t .Fatal (err )
706
+ }
707
+ structuralSchemaWithItemsUnderObject := crdWithSchema (t , "Structural" , structuralSchemaWithItemsUnderObject )
708
+ structuralSchemaWithItemsUnderObject .SetResourceVersion (crd .GetResourceVersion ())
709
+ // Update CRD with invalid schema items under object
710
+ crd , err = apiExtensionClient .ApiextensionsV1 ().CustomResourceDefinitions ().Update (context .TODO (), structuralSchemaWithItemsUnderObject , metav1.UpdateOptions {})
711
+ if err != nil {
712
+ t .Fatal (err )
713
+ }
714
+ // Make an unrelated update to the previous persisted CR instance to make sure CRD handler doesn't panic
715
+ oldCR , err := crClient .Get (context .TODO (), name1 , metav1.GetOptions {})
716
+ if err != nil {
717
+ t .Fatal (err )
718
+ }
719
+ oldCR .Object ["metadata" ].(map [string ]interface {})["labels" ] = map [string ]interface {}{"key" : "value" }
720
+ _ , err = crClient .Update (context .TODO (), oldCR , metav1.UpdateOptions {})
721
+ if err == nil || ! strings .Contains (err .Error (), "rule compiler initialization error: Failed to convert to declType for CEL validation rules" ) {
722
+ t .Fatalf ("expect error to contain \r ule compiler initialization error: Failed to convert to declType for CEL validation rules\" but get: %v" , err )
723
+ }
724
+ // Create another CR instance with an array and be rejected
725
+ name2 := names .SimpleNameGenerator .GenerateName ("cr-2" )
726
+ _ , err = crClient .Create (context .TODO (), & unstructured.Unstructured {Object : map [string ]interface {}{
727
+ "apiVersion" : gvr .Group + "/" + gvr .Version ,
728
+ "kind" : crd .Spec .Names .Kind ,
729
+ "metadata" : map [string ]interface {}{
730
+ "name" : name2 ,
731
+ },
732
+ "spec" : map [string ]interface {}{
733
+ "backend" : []interface {}{
734
+ map [string ]interface {}{
735
+ "replicas" : 7 ,
736
+ },
737
+ },
738
+ },
739
+ }}, metav1.CreateOptions {})
740
+ if err == nil || ! strings .Contains (err .Error (), "Invalid value: \" array\" : spec.backend in body must be of type object: \" array\" " ) {
741
+ t .Fatalf ("expect error to contain \" Invalid value: \" array\" : spec.backend in body must be of type object: \" array\" \" but get: %v" , err )
742
+ }
743
+ // Delete the CRD
744
+ err = fixtures .DeleteV1CustomResourceDefinition (structuralWithValidators , apiExtensionClient )
745
+ if err != nil {
746
+ t .Fatal (err )
747
+ }
748
+ }
749
+
653
750
func nonStructuralCrdWithValidations () * apiextensionsv1beta1.CustomResourceDefinition {
654
751
return & apiextensionsv1beta1.CustomResourceDefinition {
655
752
ObjectMeta : metav1.ObjectMeta {
@@ -880,6 +977,76 @@ var structuralSchemaWithBlockingErr = []byte(`
880
977
}
881
978
}` )
882
979
980
+ var structuralSchemaWithItemsUnderArray = []byte (`
981
+ {
982
+ "openAPIV3Schema": {
983
+ "description": "CRD with CEL validators",
984
+ "type": "object",
985
+ "properties": {
986
+ "spec": {
987
+ "type": "object",
988
+ "properties": {
989
+ "backend": {
990
+ "type": "array",
991
+ "maxItems": 100,
992
+ "items": {
993
+ "type": "object",
994
+ "properties": {
995
+ "replicas": {
996
+ "type": "integer"
997
+ }
998
+ },
999
+ "required": [
1000
+ "replicas"
1001
+ ],
1002
+ "x-kubernetes-validations": [
1003
+ {
1004
+ "rule": "0 <= self.replicas && self.replicas <= 10"
1005
+ }
1006
+ ]
1007
+ }
1008
+ }
1009
+ }
1010
+ }
1011
+ }
1012
+ }
1013
+ }` )
1014
+
1015
+ var structuralSchemaWithItemsUnderObject = []byte (`
1016
+ {
1017
+ "openAPIV3Schema": {
1018
+ "description": "CRD with CEL validators",
1019
+ "type": "object",
1020
+ "properties": {
1021
+ "spec": {
1022
+ "type": "object",
1023
+ "properties": {
1024
+ "backend": {
1025
+ "type": "object",
1026
+ "maxItems": 100,
1027
+ "items": {
1028
+ "type": "object",
1029
+ "properties": {
1030
+ "replicas": {
1031
+ "type": "integer"
1032
+ }
1033
+ },
1034
+ "required": [
1035
+ "replicas"
1036
+ ],
1037
+ "x-kubernetes-validations": [
1038
+ {
1039
+ "rule": "0 <= self.replicas && self.replicas <= 10"
1040
+ }
1041
+ ]
1042
+ }
1043
+ }
1044
+ }
1045
+ }
1046
+ }
1047
+ }
1048
+ }` )
1049
+
883
1050
var structuralSchemaWithValidMetadataValidators = []byte (`
884
1051
{
885
1052
"openAPIV3Schema": {
0 commit comments