Skip to content

Commit 6e3ef0b

Browse files
authored
Merge pull request kubernetes#93078 from vareti/show-error-in-status-if-preserve-unknown-field-is-true-for-nonstructural-schemas
Show error in status if preserve unknown fields is true for nonstructural schemas
2 parents db9f1e9 + 68c23df commit 6e3ef0b

File tree

4 files changed

+129
-22
lines changed

4 files changed

+129
-22
lines changed

staging/src/k8s.io/apiextensions-apiserver/pkg/controller/nonstructuralschema/BUILD

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
load("@io_bazel_rules_go//go:def.bzl", "go_library")
1+
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
22

33
go_library(
44
name = "go_default_library",
@@ -38,3 +38,13 @@ filegroup(
3838
tags = ["automanaged"],
3939
visibility = ["//visibility:public"],
4040
)
41+
42+
go_test(
43+
name = "go_default_test",
44+
srcs = ["nonstructuralschema_controller_test.go"],
45+
embed = [":go_default_library"],
46+
deps = [
47+
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1:go_default_library",
48+
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
49+
],
50+
)

staging/src/k8s.io/apiextensions-apiserver/pkg/controller/nonstructuralschema/nonstructuralschema_controller.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@ func calculateCondition(in *apiextensionsv1.CustomResourceDefinition) *apiextens
9090

9191
allErrs := field.ErrorList{}
9292

93+
if in.Spec.PreserveUnknownFields {
94+
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "preserveUnknownFields"),
95+
in.Spec.PreserveUnknownFields,
96+
fmt.Sprint("must be false")))
97+
}
98+
9399
for i, v := range in.Spec.Versions {
94100
if v.Schema == nil || v.Schema.OpenAPIV3Schema == nil {
95101
continue
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
Copyright 2020 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package nonstructuralschema
18+
19+
import (
20+
"fmt"
21+
"reflect"
22+
"testing"
23+
24+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
25+
"k8s.io/apimachinery/pkg/util/validation/field"
26+
)
27+
28+
func Test_calculateCondition(t *testing.T) {
29+
tests := []struct {
30+
name string
31+
args *apiextensionsv1.CustomResourceDefinition
32+
want *apiextensionsv1.CustomResourceDefinitionCondition
33+
}{
34+
{
35+
name: "preserve unknown fields is false",
36+
args: &apiextensionsv1.CustomResourceDefinition{
37+
Spec: apiextensionsv1.CustomResourceDefinitionSpec{
38+
PreserveUnknownFields: false,
39+
},
40+
},
41+
},
42+
{
43+
name: "preserve unknown fields is true",
44+
args: &apiextensionsv1.CustomResourceDefinition{
45+
Spec: apiextensionsv1.CustomResourceDefinitionSpec{
46+
PreserveUnknownFields: true,
47+
},
48+
},
49+
want: &apiextensionsv1.CustomResourceDefinitionCondition{
50+
Type: apiextensionsv1.NonStructuralSchema,
51+
Status: apiextensionsv1.ConditionTrue,
52+
Reason: "Violations",
53+
Message: field.Invalid(field.NewPath("spec", "preserveUnknownFields"),
54+
true,
55+
fmt.Sprint("must be false")).Error(),
56+
},
57+
},
58+
}
59+
for _, tt := range tests {
60+
t.Run(tt.name, func(t *testing.T) {
61+
if got := calculateCondition(tt.args); !reflect.DeepEqual(got, tt.want) {
62+
t.Errorf("calculateCondition() = %v, want %v", got, tt.want)
63+
}
64+
})
65+
}
66+
}

staging/src/k8s.io/apiextensions-apiserver/test/integration/validation_test.go

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2828
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2929
"k8s.io/apimachinery/pkg/runtime/schema"
30+
"k8s.io/apimachinery/pkg/types"
3031
"k8s.io/apimachinery/pkg/util/wait"
3132
"k8s.io/apimachinery/pkg/util/yaml"
3233

@@ -780,21 +781,25 @@ spec:
780781
if v := "spec.versions[0].schema.openAPIV3Schema.properties[a].type: Required value: must not be empty for specified object fields"; !strings.Contains(cond.Message, v) {
781782
t.Fatalf("expected violation %q, but got: %v", v, cond.Message)
782783
}
784+
if v := "spec.preserveUnknownFields: Invalid value: true: must be false"; !strings.Contains(cond.Message, v) {
785+
t.Fatalf("expected violation %q, but got: %v", v, cond.Message)
786+
}
783787

784788
// remove schema
785789
t.Log("Remove schema")
786790
for retry := 0; retry < 5; retry++ {
787-
crd, err = apiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Get(context.TODO(), name, metav1.GetOptions{})
788-
if err != nil {
789-
t.Fatalf("unexpected get error: %v", err)
790-
}
791-
crd.Spec.Validation = nil
792-
if _, err = apiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Update(context.TODO(), crd, metav1.UpdateOptions{}); apierrors.IsConflict(err) {
791+
// This patch fixes two fields to resolve
792+
// 1. property type validation error
793+
// 2. preserveUnknownFields validation error
794+
patch := []byte("[{\"op\":\"add\",\"path\":\"/spec/validation/openAPIV3Schema/properties/a/type\",\"value\":\"int\"}," +
795+
"{\"op\":\"replace\",\"path\":\"/spec/preserveUnknownFields\",\"value\":false}]")
796+
if _, err = apiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Patch(context.TODO(), name, types.JSONPatchType, patch, metav1.PatchOptions{}); apierrors.IsConflict(err) {
793797
continue
794798
}
795799
if err != nil {
796800
t.Fatalf("unexpected update error: %v", err)
797801
}
802+
break
798803
}
799804
if err != nil {
800805
t.Fatalf("unexpected update error: %v", err)
@@ -814,20 +819,22 @@ spec:
814819
t.Fatalf("unexpected error waiting for NonStructuralSchema condition: %v", cond)
815820
}
816821

817-
// readd schema
818-
t.Log("Readd schema")
822+
// re-add schema
823+
t.Log("Re-add schema")
819824
for retry := 0; retry < 5; retry++ {
820825
crd, err = apiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Get(context.TODO(), name, metav1.GetOptions{})
821826
if err != nil {
822827
t.Fatalf("unexpected get error: %v", err)
823828
}
829+
crd.Spec.PreserveUnknownFields = nil
824830
crd.Spec.Validation = &apiextensionsv1beta1.CustomResourceValidation{OpenAPIV3Schema: origSchema}
825831
if _, err = apiExtensionClient.ApiextensionsV1beta1().CustomResourceDefinitions().Update(context.TODO(), crd, metav1.UpdateOptions{}); apierrors.IsConflict(err) {
826832
continue
827833
}
828834
if err != nil {
829835
t.Fatalf("unexpected update error: %v", err)
830836
}
837+
break
831838
}
832839
if err != nil {
833840
t.Fatalf("unexpected update error: %v", err)
@@ -849,6 +856,9 @@ spec:
849856
if v := "spec.versions[0].schema.openAPIV3Schema.properties[a].type: Required value: must not be empty for specified object fields"; !strings.Contains(cond.Message, v) {
850857
t.Fatalf("expected violation %q, but got: %v", v, cond.Message)
851858
}
859+
if v := "spec.preserveUnknownFields: Invalid value: true: must be false"; !strings.Contains(cond.Message, v) {
860+
t.Fatalf("expected violation %q, but got: %v", v, cond.Message)
861+
}
852862
}
853863

854864
func TestNonStructuralSchemaCondition(t *testing.T) {
@@ -862,6 +872,7 @@ func TestNonStructuralSchemaCondition(t *testing.T) {
862872
apiVersion: apiextensions.k8s.io/v1beta1
863873
kind: CustomResourceDefinition
864874
spec:
875+
preserveUnknownFields: PRESERVE_UNKNOWN_FIELDS
865876
version: v1beta1
866877
names:
867878
plural: foos
@@ -882,14 +893,27 @@ spec:
882893

883894
type Test struct {
884895
desc string
896+
preserveUnknownFields string
885897
globalSchema, v1Schema, v1beta1Schema string
886898
expectedCreateErrors []string
887899
unexpectedCreateErrors []string
888900
expectedViolations []string
889901
unexpectedViolations []string
890902
}
891903
tests := []Test{
892-
{"empty", "", "", "", nil, nil, nil, nil},
904+
{
905+
desc: "empty",
906+
expectedViolations: []string{
907+
"spec.preserveUnknownFields: Invalid value: true: must be false",
908+
},
909+
},
910+
{
911+
desc: "preserve unknown fields is false",
912+
preserveUnknownFields: "false",
913+
globalSchema: `
914+
type: object
915+
`,
916+
},
893917
{
894918
desc: "int-or-string and preserve-unknown-fields true",
895919
globalSchema: `
@@ -922,7 +946,8 @@ x-kubernetes-embedded-resource: true
922946
},
923947
},
924948
{
925-
desc: "embedded-resource without preserve-unknown-fields, but properties",
949+
desc: "embedded-resource without preserve-unknown-fields, but properties",
950+
preserveUnknownFields: "false",
926951
globalSchema: `
927952
type: object
928953
x-kubernetes-embedded-resource: true
@@ -934,16 +959,15 @@ properties:
934959
metadata:
935960
type: object
936961
`,
937-
expectedViolations: []string{},
938962
},
939963
{
940-
desc: "embedded-resource with preserve-unknown-fields",
964+
desc: "embedded-resource with preserve-unknown-fields",
965+
preserveUnknownFields: "false",
941966
globalSchema: `
942967
type: object
943968
x-kubernetes-embedded-resource: true
944969
x-kubernetes-preserve-unknown-fields: true
945970
`,
946-
expectedViolations: []string{},
947971
},
948972
{
949973
desc: "embedded-resource with wrong type",
@@ -1330,7 +1354,8 @@ oneOf:
13301354
},
13311355
},
13321356
{
1333-
desc: "structural complete",
1357+
desc: "structural complete",
1358+
preserveUnknownFields: "false",
13341359
globalSchema: `
13351360
type: object
13361361
properties:
@@ -1391,7 +1416,6 @@ oneOf:
13911416
- properties:
13921417
g: {}
13931418
`,
1394-
expectedViolations: nil,
13951419
},
13961420
{
13971421
desc: "invalid v1beta1 schema",
@@ -1472,7 +1496,8 @@ properties:
14721496
},
14731497
},
14741498
{
1475-
desc: "metadata with name property",
1499+
desc: "metadata with name property",
1500+
preserveUnknownFields: "false",
14761501
globalSchema: `
14771502
type: object
14781503
properties:
@@ -1483,10 +1508,10 @@ properties:
14831508
type: string
14841509
pattern: "^[a-z]+$"
14851510
`,
1486-
expectedViolations: []string{},
14871511
},
14881512
{
1489-
desc: "metadata with generateName property",
1513+
desc: "metadata with generateName property",
1514+
preserveUnknownFields: "false",
14901515
globalSchema: `
14911516
type: object
14921517
properties:
@@ -1497,10 +1522,10 @@ properties:
14971522
type: string
14981523
pattern: "^[a-z]+$"
14991524
`,
1500-
expectedViolations: []string{},
15011525
},
15021526
{
1503-
desc: "metadata with name and generateName property",
1527+
desc: "metadata with name and generateName property",
1528+
preserveUnknownFields: "false",
15041529
globalSchema: `
15051530
type: object
15061531
properties:
@@ -1514,7 +1539,6 @@ properties:
15141539
type: string
15151540
pattern: "^[a-z]+$"
15161541
`,
1517-
expectedViolations: []string{},
15181542
},
15191543
{
15201544
desc: "metadata under junctors",
@@ -1597,6 +1621,7 @@ properties:
15971621
"GLOBAL_SCHEMA", toValidationJSON(tst.globalSchema),
15981622
"V1BETA1_SCHEMA", toValidationJSON(tst.v1beta1Schema),
15991623
"V1_SCHEMA", toValidationJSON(tst.v1Schema),
1624+
"PRESERVE_UNKNOWN_FIELDS", tst.preserveUnknownFields,
16001625
).Replace(tmpl)
16011626

16021627
// decode CRD manifest

0 commit comments

Comments
 (0)