mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-07-17 20:00:07 +00:00
Merge pull request #136977 from pedjak/allow-extradocs-openapi-schema-response
Preserve externalDocs and example in CRD structural schema conversion
This commit is contained in:
@@ -35,10 +35,6 @@ import (
|
||||
// - dependencies
|
||||
// - additionalItems
|
||||
// - definitions.
|
||||
//
|
||||
// The follow fields are not preserved:
|
||||
// - externalDocs
|
||||
// - example.
|
||||
func NewStructural(s *apiextensions.JSONSchemaProps) (*Structural, error) {
|
||||
if s == nil {
|
||||
return nil, nil
|
||||
@@ -126,6 +122,15 @@ func newGenerics(s *apiextensions.JSONSchemaProps) (*Generic, error) {
|
||||
if s.Default != nil {
|
||||
g.Default = JSON{interface{}(*s.Default)}
|
||||
}
|
||||
if s.ExternalDocs != nil {
|
||||
g.ExternalDocs = &ExternalDocumentation{
|
||||
Description: s.ExternalDocs.Description,
|
||||
URL: s.ExternalDocs.URL,
|
||||
}
|
||||
}
|
||||
if s.Example != nil {
|
||||
g.Example = JSON{interface{}(*s.Example)}
|
||||
}
|
||||
|
||||
return g, nil
|
||||
}
|
||||
|
||||
@@ -70,10 +70,6 @@ func TestStructuralRoundtripOrError(t *testing.T) {
|
||||
origSchema := &apiextensions.JSONSchemaProps{}
|
||||
x := reflect.ValueOf(origSchema).Elem()
|
||||
n := rand.Intn(x.NumField())
|
||||
if name := x.Type().Field(n).Name; name == "Example" || name == "ExternalDocs" {
|
||||
// we drop these intentionally
|
||||
continue
|
||||
}
|
||||
f.Fill(x.Field(n).Addr().Interface())
|
||||
|
||||
// it roundtrips or NewStructural errors out. We should never drop anything
|
||||
|
||||
@@ -62,6 +62,13 @@ func (g *Generic) toKubeOpenAPI(ret *spec.Schema) {
|
||||
ret.Description = g.Description
|
||||
ret.Title = g.Title
|
||||
ret.Default = g.Default.Object
|
||||
if g.ExternalDocs != nil {
|
||||
ret.ExternalDocs = &spec.ExternalDocumentation{
|
||||
Description: g.ExternalDocs.Description,
|
||||
URL: g.ExternalDocs.URL,
|
||||
}
|
||||
}
|
||||
ret.Example = g.Example.Object
|
||||
}
|
||||
|
||||
func (x *Extensions) toKubeOpenAPI(ret *spec.Schema) {
|
||||
|
||||
@@ -30,6 +30,23 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
)
|
||||
|
||||
// isJSONEmptyForOmitempty returns true if the JSON value would be dropped by the
|
||||
// kube-openapi v2 JSON encoder's omitempty tag. Unlike omitzero (used for Default),
|
||||
// the Example field uses omitempty which considers empty strings, empty slices,
|
||||
// and empty maps as empty.
|
||||
func isJSONEmptyForOmitempty(j JSON) bool {
|
||||
switch v := j.Object.(type) {
|
||||
case string:
|
||||
return v == ""
|
||||
case []interface{}:
|
||||
return len(v) == 0
|
||||
case map[string]interface{}:
|
||||
return len(v) == 0
|
||||
default:
|
||||
return j.Object == nil
|
||||
}
|
||||
}
|
||||
|
||||
func TestStructuralKubeOpenAPIRoundtrip(t *testing.T) {
|
||||
f := randfill.New()
|
||||
seed := time.Now().UnixNano()
|
||||
@@ -64,13 +81,21 @@ func TestStructuralKubeOpenAPIRoundtrip(t *testing.T) {
|
||||
f.Fill(orig)
|
||||
|
||||
// normalize Structural.ValueValidation to zero values if it was nil before
|
||||
// and normalize Example values that are considered empty by omitempty
|
||||
// (the kube-openapi Example field uses omitempty, not omitzero, so
|
||||
// empty strings/slices/maps get dropped during JSON roundtrip)
|
||||
normalizer := Visitor{
|
||||
Structural: func(s *Structural) bool {
|
||||
changed := false
|
||||
if s.ValueValidation == nil {
|
||||
s.ValueValidation = &ValueValidation{}
|
||||
return true
|
||||
changed = true
|
||||
}
|
||||
return false
|
||||
if isJSONEmptyForOmitempty(s.Generic.Example) {
|
||||
s.Generic.Example = JSON{}
|
||||
changed = true
|
||||
}
|
||||
return changed
|
||||
},
|
||||
}
|
||||
normalizer.Visit(orig)
|
||||
|
||||
@@ -46,6 +46,14 @@ type StructuralOrBool struct {
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// ExternalDocumentation allows referencing an external resource for extended documentation.
|
||||
type ExternalDocumentation struct {
|
||||
Description string
|
||||
URL string
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// Generic contains the generic schema fields not allowed in value validation.
|
||||
type Generic struct {
|
||||
Description string
|
||||
@@ -53,10 +61,12 @@ type Generic struct {
|
||||
// It can be object, array, number, integer, boolean, string.
|
||||
// It is optional only if x-kubernetes-preserve-unknown-fields
|
||||
// or x-kubernetes-int-or-string is true.
|
||||
Type string
|
||||
Title string
|
||||
Default JSON
|
||||
Nullable bool
|
||||
Type string
|
||||
Title string
|
||||
Default JSON
|
||||
Nullable bool
|
||||
ExternalDocs *ExternalDocumentation
|
||||
Example JSON
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
@@ -402,6 +402,9 @@ func TestValidateNestedValueValidationComplete(t *testing.T) {
|
||||
// check that we didn't forget to check any forbidden generic field
|
||||
tt := reflect.TypeOf(Generic{})
|
||||
for i := 0; i < tt.NumField(); i++ {
|
||||
if name := tt.Field(i).Name; name == "ExternalDocs" || name == "Example" {
|
||||
continue // allowed in nested value validations
|
||||
}
|
||||
vv := &NestedValueValidation{}
|
||||
x := reflect.ValueOf(&vv.ForbiddenGenerics).Elem()
|
||||
fuzzer.Fill(x.Field(i).Addr().Interface())
|
||||
|
||||
@@ -56,10 +56,32 @@ func (in *Extensions) DeepCopy() *Extensions {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ExternalDocumentation) DeepCopyInto(out *ExternalDocumentation) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalDocumentation.
|
||||
func (in *ExternalDocumentation) DeepCopy() *ExternalDocumentation {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ExternalDocumentation)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Generic) DeepCopyInto(out *Generic) {
|
||||
*out = *in
|
||||
out.Default = in.Default.DeepCopy()
|
||||
if in.ExternalDocs != nil {
|
||||
in, out := &in.ExternalDocs, &out.ExternalDocs
|
||||
*out = new(ExternalDocumentation)
|
||||
**out = **in
|
||||
}
|
||||
out.Example = in.Example.DeepCopy()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -632,7 +632,6 @@ func Test_ConvertJSONSchemaPropsToOpenAPIv2SchemaByType(t *testing.T) {
|
||||
},
|
||||
expected: new(spec.Schema).
|
||||
WithExternalDocs(testStr, testStr2),
|
||||
expectDiff: true,
|
||||
},
|
||||
{
|
||||
name: "example",
|
||||
@@ -641,7 +640,6 @@ func Test_ConvertJSONSchemaPropsToOpenAPIv2SchemaByType(t *testing.T) {
|
||||
},
|
||||
expected: new(spec.Schema).
|
||||
WithExample(testStr),
|
||||
expectDiff: true,
|
||||
},
|
||||
{
|
||||
name: "preserve-unknown-fields in arrays",
|
||||
|
||||
Reference in New Issue
Block a user