Skip to content

Commit 1d343c8

Browse files
authored
Merge pull request kubernetes#85722 from sttts/sttts-apiextensions-nullable-required
apiextensions: filter required nullable to workaround kubectl validation
2 parents d9c1203 + ae72e19 commit 1d343c8

File tree

2 files changed

+115
-0
lines changed

2 files changed

+115
-0
lines changed

staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/v2/conversion.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,15 @@ func ToStructuralOpenAPIV2(in *structuralschema.Structural) *structuralschema.St
7373
changed = true
7474
}
7575

76+
for f, fs := range s.Properties {
77+
if fs.Nullable {
78+
s.ValueValidation.Required, changed = filterOut(s.ValueValidation.Required, f)
79+
}
80+
}
81+
if s.AdditionalProperties != nil && s.AdditionalProperties.Structural != nil && s.AdditionalProperties.Structural.Nullable {
82+
s.ValueValidation.Required, changed = nil, true
83+
}
84+
7685
return changed
7786
},
7887
// we drop all junctors above, and hence, never reach nested value validations
@@ -82,3 +91,24 @@ func ToStructuralOpenAPIV2(in *structuralschema.Structural) *structuralschema.St
8291

8392
return out
8493
}
94+
95+
func filterOut(ss []string, x string) ([]string, bool) {
96+
var filtered []string
97+
for i, s := range ss {
98+
if s == x {
99+
if filtered == nil {
100+
filtered = make([]string, i, len(ss))
101+
copy(filtered, ss[:i])
102+
}
103+
} else if filtered != nil {
104+
filtered = append(filtered, s)
105+
}
106+
}
107+
if filtered != nil {
108+
if len(filtered) == 0 {
109+
return nil, true
110+
}
111+
return filtered, true
112+
}
113+
return ss, false
114+
}

staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/v2/conversion_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,63 @@ func Test_ConvertJSONSchemaPropsToOpenAPIv2SchemaByType(t *testing.T) {
172172
},
173173
expected: new(spec.Schema),
174174
},
175+
{
176+
name: "nullable required",
177+
in: &apiextensions.JSONSchemaProps{
178+
Type: "object",
179+
Properties: map[string]apiextensions.JSONSchemaProps{
180+
"a": {
181+
Nullable: true,
182+
Type: "string",
183+
},
184+
"b": {
185+
Nullable: true,
186+
Type: "string",
187+
},
188+
"c": {
189+
Type: "string",
190+
},
191+
},
192+
Required: []string{"a", "c"},
193+
},
194+
expected: &spec.Schema{
195+
SchemaProps: spec.SchemaProps{
196+
Type: []string{"object"},
197+
Properties: map[string]spec.Schema{
198+
"a": {},
199+
"b": {},
200+
"c": {
201+
SchemaProps: spec.SchemaProps{
202+
Type: []string{"string"},
203+
},
204+
},
205+
},
206+
Required: []string{"c"},
207+
},
208+
},
209+
},
210+
{
211+
name: "nullable required additionalProperties",
212+
in: &apiextensions.JSONSchemaProps{
213+
Type: "object",
214+
AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{
215+
Schema: &apiextensions.JSONSchemaProps{
216+
Nullable: true,
217+
Type: "string",
218+
},
219+
},
220+
Required: []string{"a", "c"},
221+
},
222+
expected: &spec.Schema{
223+
SchemaProps: spec.SchemaProps{
224+
Type: []string{"object"},
225+
AdditionalProperties: &spec.SchemaOrBool{
226+
Allows: true,
227+
Schema: &spec.Schema{},
228+
},
229+
},
230+
},
231+
},
175232
{
176233
name: "title",
177234
in: &apiextensions.JSONSchemaProps{
@@ -829,6 +886,34 @@ func fuzzFuncs(f *fuzz.Fuzzer, refFunc func(ref *spec.Ref, c fuzz.Continue, visi
829886
)
830887
}
831888

889+
func TestFilterOut(t *testing.T) {
890+
type Test struct {
891+
name string
892+
input []string
893+
x string
894+
expected []string
895+
expectedChanged bool
896+
}
897+
for _, tt := range []Test{
898+
{"nil", nil, "foo", nil, false},
899+
{"empty", []string{}, "foo", []string{}, false},
900+
{"foo", []string{"foo"}, "foo", nil, true},
901+
{"aaa", []string{"a", "a", "a"}, "a", nil, true},
902+
{"abc", []string{"a", "b", "c"}, "c", []string{"a", "b"}, true},
903+
{"abbbcc", []string{"a", "b", "b", "b", "c", "c"}, "b", []string{"a", "c", "c"}, true},
904+
} {
905+
t.Run(tt.name, func(t *testing.T) {
906+
got, gotChanged := filterOut(tt.input, tt.x)
907+
if !reflect.DeepEqual(tt.expected, got) {
908+
t.Errorf("expected slice %v, got %v", tt.expected, got)
909+
}
910+
if tt.expectedChanged != gotChanged {
911+
t.Errorf("expected changed %v, got %v", tt.expected, got)
912+
}
913+
})
914+
}
915+
}
916+
832917
func max(i, j int) int {
833918
if i > j {
834919
return i

0 commit comments

Comments
 (0)