Skip to content

Commit

Permalink
Use OpenAPI V3 for client side SMP
Browse files Browse the repository at this point in the history
  • Loading branch information
Jefftree committed Oct 31, 2023
1 parent 74fefd8 commit 4f3b0b1
Show file tree
Hide file tree
Showing 9 changed files with 16,252 additions and 2 deletions.
89 changes: 89 additions & 0 deletions staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/meta.go
Expand Up @@ -20,12 +20,17 @@ import (
"errors"
"fmt"
"reflect"
"strings"

"k8s.io/apimachinery/pkg/util/mergepatch"
forkedjson "k8s.io/apimachinery/third_party/forked/golang/json"
openapi "k8s.io/kube-openapi/pkg/util/proto"
"k8s.io/kube-openapi/pkg/validation/spec"
)

const patchMergeKey = "x-kubernetes-patch-merge-key"
const patchStrategy = "x-kubernetes-patch-strategy"

type PatchMeta struct {
patchStrategies []string
patchMergeKey string
Expand Down Expand Up @@ -148,6 +153,90 @@ func GetTagStructTypeOrDie(dataStruct interface{}) reflect.Type {
return t
}

type PatchMetaFromOpenAPIV3 struct {
// SchemaList is required to resolve OpenAPI V3 references
SchemaList map[string]*spec.Schema
Schema *spec.Schema
}

func (s PatchMetaFromOpenAPIV3) traverse(key string) (PatchMetaFromOpenAPIV3, error) {
if s.Schema == nil {
return PatchMetaFromOpenAPIV3{}, nil
}
if len(s.Schema.Properties) == 0 {
return PatchMetaFromOpenAPIV3{}, fmt.Errorf("unable to find api field \"%s\"", key)
}
subschema, ok := s.Schema.Properties[key]
if !ok {
return PatchMetaFromOpenAPIV3{}, fmt.Errorf("unable to find api field \"%s\"", key)
}
return PatchMetaFromOpenAPIV3{SchemaList: s.SchemaList, Schema: &subschema}, nil
}

func resolve(l *PatchMetaFromOpenAPIV3) error {
if len(l.Schema.AllOf) > 0 {
l.Schema = &l.Schema.AllOf[0]
}
if refString := l.Schema.Ref.String(); refString != "" {
str := strings.TrimPrefix(refString, "#/components/schemas/")
sch, ok := l.SchemaList[str]
if ok {
l.Schema = sch
} else {
return fmt.Errorf("Unable to resolve %s in OpenAPI V3", refString)
}
}
return nil
}

func (s PatchMetaFromOpenAPIV3) LookupPatchMetadataForStruct(key string) (LookupPatchMeta, PatchMeta, error) {
l, err := s.traverse(key)
if err != nil {
return l, PatchMeta{}, err
}
p := PatchMeta{}
f, ok := l.Schema.Extensions[patchMergeKey]
if ok {
p.SetPatchMergeKey(f.(string))
}
g, ok := l.Schema.Extensions[patchStrategy]
if ok {
p.SetPatchStrategies(strings.Split(g.(string), ","))
}

err = resolve(&l)
return l, p, err
}

func (s PatchMetaFromOpenAPIV3) LookupPatchMetadataForSlice(key string) (LookupPatchMeta, PatchMeta, error) {
l, err := s.traverse(key)
if err != nil {
return l, PatchMeta{}, err
}
p := PatchMeta{}
f, ok := l.Schema.Extensions[patchMergeKey]
if ok {
p.SetPatchMergeKey(f.(string))
}
g, ok := l.Schema.Extensions[patchStrategy]
if ok {
p.SetPatchStrategies(strings.Split(g.(string), ","))
}
if l.Schema.Items != nil {
l.Schema = l.Schema.Items.Schema
}
resolve(&l)
return l, p, err
}

func (s PatchMetaFromOpenAPIV3) Name() string {
schema := s.Schema
if len(schema.Type) > 0 {
return strings.Join(schema.Type, "")
}
return "Struct"
}

type PatchMetaFromOpenAPI struct {
Schema openapi.Schema
}
Expand Down
Expand Up @@ -36,6 +36,9 @@ import (
var (
fakeMergeItemSchema = sptest.Fake{Path: filepath.Join("testdata", "swagger-merge-item.json")}
fakePrecisionItemSchema = sptest.Fake{Path: filepath.Join("testdata", "swagger-precision-item.json")}

fakeMergeItemV3Schema = sptest.OpenAPIV3Getter{Path: filepath.Join("testdata", "swagger-merge-item-v3.json")}
fakePrecisionItemV3Schema = sptest.OpenAPIV3Getter{Path: filepath.Join("testdata", "swagger-precision-item-v3.json")}
)

type SortMergeListTestCases struct {
Expand Down Expand Up @@ -284,9 +287,14 @@ func TestSortMergeLists(t *testing.T) {
mergeItemOpenapiSchema := PatchMetaFromOpenAPI{
Schema: sptest.GetSchemaOrDie(&fakeMergeItemSchema, "mergeItem"),
}
mergeItemOpenapiV3Schema := PatchMetaFromOpenAPIV3{
SchemaList: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas,
Schema: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas["mergeItem"],
}
schemas := []LookupPatchMeta{
mergeItemStructSchema,
mergeItemOpenapiSchema,
mergeItemOpenapiV3Schema,
}

tc := SortMergeListTestCases{}
Expand Down Expand Up @@ -766,9 +774,14 @@ func TestCustomStrategicMergePatch(t *testing.T) {
mergeItemOpenapiSchema := PatchMetaFromOpenAPI{
Schema: sptest.GetSchemaOrDie(&fakeMergeItemSchema, "mergeItem"),
}
mergeItemOpenapiV3Schema := PatchMetaFromOpenAPIV3{
SchemaList: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas,
Schema: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas["mergeItem"],
}
schemas := []LookupPatchMeta{
mergeItemStructSchema,
mergeItemOpenapiSchema,
mergeItemOpenapiV3Schema,
}

tc := StrategicMergePatchTestCases{}
Expand Down Expand Up @@ -6169,9 +6182,14 @@ func TestStrategicMergePatch(t *testing.T) {
mergeItemOpenapiSchema := PatchMetaFromOpenAPI{
Schema: sptest.GetSchemaOrDie(&fakeMergeItemSchema, "mergeItem"),
}
mergeItemOpenapiV3Schema := PatchMetaFromOpenAPIV3{
SchemaList: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas,
Schema: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas["mergeItem"],
}
schemas := []LookupPatchMeta{
mergeItemStructSchema,
mergeItemOpenapiSchema,
mergeItemOpenapiV3Schema,
}

tc := StrategicMergePatchTestCases{}
Expand Down Expand Up @@ -6564,9 +6582,14 @@ func TestNumberConversion(t *testing.T) {
precisionItemOpenapiSchema := PatchMetaFromOpenAPI{
Schema: sptest.GetSchemaOrDie(&fakePrecisionItemSchema, "precisionItem"),
}
precisionItemOpenapiV3Schema := PatchMetaFromOpenAPIV3{
SchemaList: fakePrecisionItemV3Schema.SchemaOrDie().Components.Schemas,
Schema: fakePrecisionItemV3Schema.SchemaOrDie().Components.Schemas["precisionItem"],
}
precisionItemSchemas := []LookupPatchMeta{
precisionItemStructSchema,
precisionItemOpenapiSchema,
precisionItemOpenapiV3Schema,
}

for _, schema := range precisionItemSchemas {
Expand Down Expand Up @@ -6774,9 +6797,14 @@ func TestReplaceWithRawExtension(t *testing.T) {
mergeItemOpenapiSchema := PatchMetaFromOpenAPI{
Schema: sptest.GetSchemaOrDie(&fakeMergeItemSchema, "mergeItem"),
}
mergeItemOpenapiV3Schema := PatchMetaFromOpenAPIV3{
SchemaList: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas,
Schema: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas["mergeItem"],
}
schemas := []LookupPatchMeta{
mergeItemStructSchema,
mergeItemOpenapiSchema,
mergeItemOpenapiV3Schema,
}

for _, schema := range schemas {
Expand Down Expand Up @@ -6946,9 +6974,14 @@ func TestUnknownField(t *testing.T) {
mergeItemOpenapiSchema := PatchMetaFromOpenAPI{
Schema: sptest.GetSchemaOrDie(&fakeMergeItemSchema, "mergeItem"),
}
mergeItemOpenapiV3Schema := PatchMetaFromOpenAPIV3{
SchemaList: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas,
Schema: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas["mergeItem"],
}
schemas := []LookupPatchMeta{
mergeItemStructSchema,
mergeItemOpenapiSchema,
mergeItemOpenapiV3Schema,
}

for _, k := range sets.StringKeySet(testcases).List() {
Expand Down
@@ -0,0 +1,180 @@
{
"openapi": "3.0",
"info": {
"title": "StrategicMergePatchTestingMergeItem",
"version": "v3.0"
},
"paths": {},
"components": {
"schemas": {
"mergeItem": {
"description": "MergeItem is type definition for testing strategic merge.",
"required": [],
"properties": {
"name": {
"description": "Name field.",
"type": "string"
},
"value": {
"description": "Value field.",
"type": "string"
},
"other": {
"description": "Other field.",
"type": "string"
},
"mergingList": {
"description": "MergingList field.",
"type": "array",
"items": {
"default": {},
"allOf": [
{"$ref": "#/components/schemas/mergeItem"}
]
},
"x-kubernetes-patch-merge-key": "name",
"x-kubernetes-patch-strategy": "merge"
},
"nonMergingList": {
"description": "NonMergingList field.",
"type": "array",
"items": {
"$ref": "#/components/schemas/mergeItem"
}
},
"mergingIntList": {
"description": "MergingIntList field.",
"type": "array",
"items": {
"type": "integer",
"format": "int32"
},
"x-kubernetes-patch-strategy": "merge"
},
"nonMergingIntList": {
"description": "NonMergingIntList field.",
"type": "array",
"items": {
"type": "integer",
"format": "int32"
}
},
"mergeItemPtr": {
"description": "MergeItemPtr field.",
"allOf": [
{"$ref": "#/components/schemas/mergeItem"}
],
"x-kubernetes-patch-merge-key": "name",
"x-kubernetes-patch-strategy": "merge"
},
"simpleMap": {
"description": "SimpleMap field.",
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"replacingItem": {
"description": "ReplacingItem field.",
"allOf": [
{"$ref": "#/components/schemas/io.k8s.apimachinery.pkg.runtime.RawExtension"}
],
"x-kubernetes-patch-strategy": "replace"
},
"retainKeysMap": {
"description": "RetainKeysMap field.",
"allOf": [
{"$ref": "#/components/schemas/retainKeysMergeItem"}
],
"x-kubernetes-patch-strategy": "retainKeys"
},
"retainKeysMergingList": {
"description": "RetainKeysMergingList field.",
"type": "array",
"items": {
"$ref": "#/components/schemas/mergeItem"
},
"x-kubernetes-patch-merge-key": "name",
"x-kubernetes-patch-strategy": "merge,retainKeys"
}
},
"x-kubernetes-group-version-kind": [
{
"group": "fake-group",
"kind": "mergeItem",
"version": "some-version"
}
]
},
"retainKeysMergeItem": {
"description": "RetainKeysMergeItem is type definition for testing strategic merge.",
"required": [],
"properties": {
"name": {
"description": "Name field.",
"type": "string"
},
"value": {
"description": "Value field.",
"type": "string"
},
"other": {
"description": "Other field.",
"type": "string"
},
"simpleMap": {
"description": "SimpleMap field.",
"items": {
"type": "string"
}
},
"mergingList": {
"description": "MergingList field.",
"type": "array",
"items": {
"$ref": "#/components/schemas/mergeItem"
},
"x-kubernetes-patch-merge-key": "name",
"x-kubernetes-patch-strategy": "merge"
},
"nonMergingList": {
"description": "NonMergingList field.",
"type": "array",
"items": {
"$ref": "#/components/schemas/mergeItem"
}
},
"mergingIntList": {
"description": "MergingIntList field.",
"type": "array",
"items": {
"type": "integer",
"format": "int32"
},
"x-kubernetes-patch-strategy": "merge"
}
},
"x-kubernetes-group-version-kind": [
{
"group": "fake-group",
"kind": "retainKeysMergeItem",
"version": "some-version"
}
]
},
"io.k8s.apimachinery.pkg.runtime.RawExtension": {
"description": "RawExtension is used to hold extensions in external versions.",
"required": [
"Raw"
],
"properties": {
"Raw": {
"description": "Raw is the underlying serialization of this object.",
"type": "string",
"format": "byte"
}
}
}
}
}
}

0 comments on commit 4f3b0b1

Please sign in to comment.