Skip to content

Commit 64258fc

Browse files
Merge pull request #136977 from pedjak/allow-extradocs-openapi-schema-response
Preserve externalDocs and example in CRD structural schema conversion Kubernetes-commit: 24ca19d980277000b8a2a5e66b0e431e2ae67aa0
2 parents bc22ed3 + b0c29ed commit 64258fc

8 files changed

Lines changed: 82 additions & 16 deletions

File tree

pkg/apiserver/schema/convert.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,6 @@ import (
3535
// - dependencies
3636
// - additionalItems
3737
// - definitions.
38-
//
39-
// The follow fields are not preserved:
40-
// - externalDocs
41-
// - example.
4238
func NewStructural(s *apiextensions.JSONSchemaProps) (*Structural, error) {
4339
if s == nil {
4440
return nil, nil
@@ -126,6 +122,15 @@ func newGenerics(s *apiextensions.JSONSchemaProps) (*Generic, error) {
126122
if s.Default != nil {
127123
g.Default = JSON{interface{}(*s.Default)}
128124
}
125+
if s.ExternalDocs != nil {
126+
g.ExternalDocs = &ExternalDocumentation{
127+
Description: s.ExternalDocs.Description,
128+
URL: s.ExternalDocs.URL,
129+
}
130+
}
131+
if s.Example != nil {
132+
g.Example = JSON{interface{}(*s.Example)}
133+
}
129134

130135
return g, nil
131136
}

pkg/apiserver/schema/convert_test.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,6 @@ func TestStructuralRoundtripOrError(t *testing.T) {
7070
origSchema := &apiextensions.JSONSchemaProps{}
7171
x := reflect.ValueOf(origSchema).Elem()
7272
n := rand.Intn(x.NumField())
73-
if name := x.Type().Field(n).Name; name == "Example" || name == "ExternalDocs" {
74-
// we drop these intentionally
75-
continue
76-
}
7773
f.Fill(x.Field(n).Addr().Interface())
7874

7975
// it roundtrips or NewStructural errors out. We should never drop anything

pkg/apiserver/schema/kubeopenapi.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,13 @@ func (g *Generic) toKubeOpenAPI(ret *spec.Schema) {
6262
ret.Description = g.Description
6363
ret.Title = g.Title
6464
ret.Default = g.Default.Object
65+
if g.ExternalDocs != nil {
66+
ret.ExternalDocs = &spec.ExternalDocumentation{
67+
Description: g.ExternalDocs.Description,
68+
URL: g.ExternalDocs.URL,
69+
}
70+
}
71+
ret.Example = g.Example.Object
6572
}
6673

6774
func (x *Extensions) toKubeOpenAPI(ret *spec.Schema) {

pkg/apiserver/schema/kubeopenapi_test.go

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,23 @@ import (
3030
"k8s.io/apimachinery/pkg/util/json"
3131
)
3232

33+
// isJSONEmptyForOmitempty returns true if the JSON value would be dropped by the
34+
// kube-openapi v2 JSON encoder's omitempty tag. Unlike omitzero (used for Default),
35+
// the Example field uses omitempty which considers empty strings, empty slices,
36+
// and empty maps as empty.
37+
func isJSONEmptyForOmitempty(j JSON) bool {
38+
switch v := j.Object.(type) {
39+
case string:
40+
return v == ""
41+
case []interface{}:
42+
return len(v) == 0
43+
case map[string]interface{}:
44+
return len(v) == 0
45+
default:
46+
return j.Object == nil
47+
}
48+
}
49+
3350
func TestStructuralKubeOpenAPIRoundtrip(t *testing.T) {
3451
f := randfill.New()
3552
seed := time.Now().UnixNano()
@@ -64,13 +81,21 @@ func TestStructuralKubeOpenAPIRoundtrip(t *testing.T) {
6481
f.Fill(orig)
6582

6683
// normalize Structural.ValueValidation to zero values if it was nil before
84+
// and normalize Example values that are considered empty by omitempty
85+
// (the kube-openapi Example field uses omitempty, not omitzero, so
86+
// empty strings/slices/maps get dropped during JSON roundtrip)
6787
normalizer := Visitor{
6888
Structural: func(s *Structural) bool {
89+
changed := false
6990
if s.ValueValidation == nil {
7091
s.ValueValidation = &ValueValidation{}
71-
return true
92+
changed = true
93+
}
94+
if isJSONEmptyForOmitempty(s.Generic.Example) {
95+
s.Generic.Example = JSON{}
96+
changed = true
7297
}
73-
return false
98+
return changed
7499
},
75100
}
76101
normalizer.Visit(orig)

pkg/apiserver/schema/structural.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,27 @@ type StructuralOrBool struct {
4646

4747
// +k8s:deepcopy-gen=true
4848

49+
// ExternalDocumentation allows referencing an external resource for extended documentation.
50+
type ExternalDocumentation struct {
51+
Description string
52+
URL string
53+
}
54+
55+
// +k8s:deepcopy-gen=true
56+
4957
// Generic contains the generic schema fields not allowed in value validation.
5058
type Generic struct {
5159
Description string
5260
// type specifies the type of a value.
5361
// It can be object, array, number, integer, boolean, string.
5462
// It is optional only if x-kubernetes-preserve-unknown-fields
5563
// or x-kubernetes-int-or-string is true.
56-
Type string
57-
Title string
58-
Default JSON
59-
Nullable bool
64+
Type string
65+
Title string
66+
Default JSON
67+
Nullable bool
68+
ExternalDocs *ExternalDocumentation
69+
Example JSON
6070
}
6171

6272
// +k8s:deepcopy-gen=true

pkg/apiserver/schema/validation_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,9 @@ func TestValidateNestedValueValidationComplete(t *testing.T) {
402402
// check that we didn't forget to check any forbidden generic field
403403
tt := reflect.TypeOf(Generic{})
404404
for i := 0; i < tt.NumField(); i++ {
405+
if name := tt.Field(i).Name; name == "ExternalDocs" || name == "Example" {
406+
continue // allowed in nested value validations
407+
}
405408
vv := &NestedValueValidation{}
406409
x := reflect.ValueOf(&vv.ForbiddenGenerics).Elem()
407410
fuzzer.Fill(x.Field(i).Addr().Interface())

pkg/apiserver/schema/zz_generated.deepcopy.go

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/controller/openapi/v2/conversion_test.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,6 @@ func Test_ConvertJSONSchemaPropsToOpenAPIv2SchemaByType(t *testing.T) {
632632
},
633633
expected: new(spec.Schema).
634634
WithExternalDocs(testStr, testStr2),
635-
expectDiff: true,
636635
},
637636
{
638637
name: "example",
@@ -641,7 +640,6 @@ func Test_ConvertJSONSchemaPropsToOpenAPIv2SchemaByType(t *testing.T) {
641640
},
642641
expected: new(spec.Schema).
643642
WithExample(testStr),
644-
expectDiff: true,
645643
},
646644
{
647645
name: "preserve-unknown-fields in arrays",

0 commit comments

Comments
 (0)