Skip to content

Commit 458fd90

Browse files
Merge pull request #120707 from Jefftree/csa-openapiv3
Use OpenAPI V3 for client side SMP Kubernetes-commit: 07d2da75bd6b5a0bd9b4618889ba1a42ad192b03
2 parents 850727f + 5a8e3a4 commit 458fd90

File tree

5 files changed

+416
-0
lines changed

5 files changed

+416
-0
lines changed

pkg/util/strategicpatch/meta.go

+89
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,17 @@ import (
2020
"errors"
2121
"fmt"
2222
"reflect"
23+
"strings"
2324

2425
"k8s.io/apimachinery/pkg/util/mergepatch"
2526
forkedjson "k8s.io/apimachinery/third_party/forked/golang/json"
2627
openapi "k8s.io/kube-openapi/pkg/util/proto"
28+
"k8s.io/kube-openapi/pkg/validation/spec"
2729
)
2830

31+
const patchMergeKey = "x-kubernetes-patch-merge-key"
32+
const patchStrategy = "x-kubernetes-patch-strategy"
33+
2934
type PatchMeta struct {
3035
patchStrategies []string
3136
patchMergeKey string
@@ -148,6 +153,90 @@ func GetTagStructTypeOrDie(dataStruct interface{}) reflect.Type {
148153
return t
149154
}
150155

156+
type PatchMetaFromOpenAPIV3 struct {
157+
// SchemaList is required to resolve OpenAPI V3 references
158+
SchemaList map[string]*spec.Schema
159+
Schema *spec.Schema
160+
}
161+
162+
func (s PatchMetaFromOpenAPIV3) traverse(key string) (PatchMetaFromOpenAPIV3, error) {
163+
if s.Schema == nil {
164+
return PatchMetaFromOpenAPIV3{}, nil
165+
}
166+
if len(s.Schema.Properties) == 0 {
167+
return PatchMetaFromOpenAPIV3{}, fmt.Errorf("unable to find api field \"%s\"", key)
168+
}
169+
subschema, ok := s.Schema.Properties[key]
170+
if !ok {
171+
return PatchMetaFromOpenAPIV3{}, fmt.Errorf("unable to find api field \"%s\"", key)
172+
}
173+
return PatchMetaFromOpenAPIV3{SchemaList: s.SchemaList, Schema: &subschema}, nil
174+
}
175+
176+
func resolve(l *PatchMetaFromOpenAPIV3) error {
177+
if len(l.Schema.AllOf) > 0 {
178+
l.Schema = &l.Schema.AllOf[0]
179+
}
180+
if refString := l.Schema.Ref.String(); refString != "" {
181+
str := strings.TrimPrefix(refString, "#/components/schemas/")
182+
sch, ok := l.SchemaList[str]
183+
if ok {
184+
l.Schema = sch
185+
} else {
186+
return fmt.Errorf("unable to resolve %s in OpenAPI V3", refString)
187+
}
188+
}
189+
return nil
190+
}
191+
192+
func (s PatchMetaFromOpenAPIV3) LookupPatchMetadataForStruct(key string) (LookupPatchMeta, PatchMeta, error) {
193+
l, err := s.traverse(key)
194+
if err != nil {
195+
return l, PatchMeta{}, err
196+
}
197+
p := PatchMeta{}
198+
f, ok := l.Schema.Extensions[patchMergeKey]
199+
if ok {
200+
p.SetPatchMergeKey(f.(string))
201+
}
202+
g, ok := l.Schema.Extensions[patchStrategy]
203+
if ok {
204+
p.SetPatchStrategies(strings.Split(g.(string), ","))
205+
}
206+
207+
err = resolve(&l)
208+
return l, p, err
209+
}
210+
211+
func (s PatchMetaFromOpenAPIV3) LookupPatchMetadataForSlice(key string) (LookupPatchMeta, PatchMeta, error) {
212+
l, err := s.traverse(key)
213+
if err != nil {
214+
return l, PatchMeta{}, err
215+
}
216+
p := PatchMeta{}
217+
f, ok := l.Schema.Extensions[patchMergeKey]
218+
if ok {
219+
p.SetPatchMergeKey(f.(string))
220+
}
221+
g, ok := l.Schema.Extensions[patchStrategy]
222+
if ok {
223+
p.SetPatchStrategies(strings.Split(g.(string), ","))
224+
}
225+
if l.Schema.Items != nil {
226+
l.Schema = l.Schema.Items.Schema
227+
}
228+
err = resolve(&l)
229+
return l, p, err
230+
}
231+
232+
func (s PatchMetaFromOpenAPIV3) Name() string {
233+
schema := s.Schema
234+
if len(schema.Type) > 0 {
235+
return strings.Join(schema.Type, "")
236+
}
237+
return "Struct"
238+
}
239+
151240
type PatchMetaFromOpenAPI struct {
152241
Schema openapi.Schema
153242
}

pkg/util/strategicpatch/patch_test.go

+33
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ import (
3636
var (
3737
fakeMergeItemSchema = sptest.Fake{Path: filepath.Join("testdata", "swagger-merge-item.json")}
3838
fakePrecisionItemSchema = sptest.Fake{Path: filepath.Join("testdata", "swagger-precision-item.json")}
39+
40+
fakeMergeItemV3Schema = sptest.OpenAPIV3Getter{Path: filepath.Join("testdata", "swagger-merge-item-v3.json")}
41+
fakePrecisionItemV3Schema = sptest.OpenAPIV3Getter{Path: filepath.Join("testdata", "swagger-precision-item-v3.json")}
3942
)
4043

4144
type SortMergeListTestCases struct {
@@ -284,9 +287,14 @@ func TestSortMergeLists(t *testing.T) {
284287
mergeItemOpenapiSchema := PatchMetaFromOpenAPI{
285288
Schema: sptest.GetSchemaOrDie(&fakeMergeItemSchema, "mergeItem"),
286289
}
290+
mergeItemOpenapiV3Schema := PatchMetaFromOpenAPIV3{
291+
SchemaList: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas,
292+
Schema: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas["mergeItem"],
293+
}
287294
schemas := []LookupPatchMeta{
288295
mergeItemStructSchema,
289296
mergeItemOpenapiSchema,
297+
mergeItemOpenapiV3Schema,
290298
}
291299

292300
tc := SortMergeListTestCases{}
@@ -766,9 +774,14 @@ func TestCustomStrategicMergePatch(t *testing.T) {
766774
mergeItemOpenapiSchema := PatchMetaFromOpenAPI{
767775
Schema: sptest.GetSchemaOrDie(&fakeMergeItemSchema, "mergeItem"),
768776
}
777+
mergeItemOpenapiV3Schema := PatchMetaFromOpenAPIV3{
778+
SchemaList: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas,
779+
Schema: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas["mergeItem"],
780+
}
769781
schemas := []LookupPatchMeta{
770782
mergeItemStructSchema,
771783
mergeItemOpenapiSchema,
784+
mergeItemOpenapiV3Schema,
772785
}
773786

774787
tc := StrategicMergePatchTestCases{}
@@ -6169,9 +6182,14 @@ func TestStrategicMergePatch(t *testing.T) {
61696182
mergeItemOpenapiSchema := PatchMetaFromOpenAPI{
61706183
Schema: sptest.GetSchemaOrDie(&fakeMergeItemSchema, "mergeItem"),
61716184
}
6185+
mergeItemOpenapiV3Schema := PatchMetaFromOpenAPIV3{
6186+
SchemaList: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas,
6187+
Schema: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas["mergeItem"],
6188+
}
61726189
schemas := []LookupPatchMeta{
61736190
mergeItemStructSchema,
61746191
mergeItemOpenapiSchema,
6192+
mergeItemOpenapiV3Schema,
61756193
}
61766194

61776195
tc := StrategicMergePatchTestCases{}
@@ -6564,9 +6582,14 @@ func TestNumberConversion(t *testing.T) {
65646582
precisionItemOpenapiSchema := PatchMetaFromOpenAPI{
65656583
Schema: sptest.GetSchemaOrDie(&fakePrecisionItemSchema, "precisionItem"),
65666584
}
6585+
precisionItemOpenapiV3Schema := PatchMetaFromOpenAPIV3{
6586+
SchemaList: fakePrecisionItemV3Schema.SchemaOrDie().Components.Schemas,
6587+
Schema: fakePrecisionItemV3Schema.SchemaOrDie().Components.Schemas["precisionItem"],
6588+
}
65676589
precisionItemSchemas := []LookupPatchMeta{
65686590
precisionItemStructSchema,
65696591
precisionItemOpenapiSchema,
6592+
precisionItemOpenapiV3Schema,
65706593
}
65716594

65726595
for _, schema := range precisionItemSchemas {
@@ -6774,9 +6797,14 @@ func TestReplaceWithRawExtension(t *testing.T) {
67746797
mergeItemOpenapiSchema := PatchMetaFromOpenAPI{
67756798
Schema: sptest.GetSchemaOrDie(&fakeMergeItemSchema, "mergeItem"),
67766799
}
6800+
mergeItemOpenapiV3Schema := PatchMetaFromOpenAPIV3{
6801+
SchemaList: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas,
6802+
Schema: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas["mergeItem"],
6803+
}
67776804
schemas := []LookupPatchMeta{
67786805
mergeItemStructSchema,
67796806
mergeItemOpenapiSchema,
6807+
mergeItemOpenapiV3Schema,
67806808
}
67816809

67826810
for _, schema := range schemas {
@@ -6946,9 +6974,14 @@ func TestUnknownField(t *testing.T) {
69466974
mergeItemOpenapiSchema := PatchMetaFromOpenAPI{
69476975
Schema: sptest.GetSchemaOrDie(&fakeMergeItemSchema, "mergeItem"),
69486976
}
6977+
mergeItemOpenapiV3Schema := PatchMetaFromOpenAPIV3{
6978+
SchemaList: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas,
6979+
Schema: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas["mergeItem"],
6980+
}
69496981
schemas := []LookupPatchMeta{
69506982
mergeItemStructSchema,
69516983
mergeItemOpenapiSchema,
6984+
mergeItemOpenapiV3Schema,
69526985
}
69536986

69546987
for _, k := range sets.StringKeySet(testcases).List() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
{
2+
"openapi": "3.0",
3+
"info": {
4+
"title": "StrategicMergePatchTestingMergeItem",
5+
"version": "v3.0"
6+
},
7+
"paths": {},
8+
"components": {
9+
"schemas": {
10+
"mergeItem": {
11+
"description": "MergeItem is type definition for testing strategic merge.",
12+
"required": [],
13+
"properties": {
14+
"name": {
15+
"description": "Name field.",
16+
"type": "string"
17+
},
18+
"value": {
19+
"description": "Value field.",
20+
"type": "string"
21+
},
22+
"other": {
23+
"description": "Other field.",
24+
"type": "string"
25+
},
26+
"mergingList": {
27+
"description": "MergingList field.",
28+
"type": "array",
29+
"items": {
30+
"default": {},
31+
"allOf": [
32+
{"$ref": "#/components/schemas/mergeItem"}
33+
]
34+
},
35+
"x-kubernetes-patch-merge-key": "name",
36+
"x-kubernetes-patch-strategy": "merge"
37+
},
38+
"nonMergingList": {
39+
"description": "NonMergingList field.",
40+
"type": "array",
41+
"items": {
42+
"$ref": "#/components/schemas/mergeItem"
43+
}
44+
},
45+
"mergingIntList": {
46+
"description": "MergingIntList field.",
47+
"type": "array",
48+
"items": {
49+
"type": "integer",
50+
"format": "int32"
51+
},
52+
"x-kubernetes-patch-strategy": "merge"
53+
},
54+
"nonMergingIntList": {
55+
"description": "NonMergingIntList field.",
56+
"type": "array",
57+
"items": {
58+
"type": "integer",
59+
"format": "int32"
60+
}
61+
},
62+
"mergeItemPtr": {
63+
"description": "MergeItemPtr field.",
64+
"allOf": [
65+
{"$ref": "#/components/schemas/mergeItem"}
66+
],
67+
"x-kubernetes-patch-merge-key": "name",
68+
"x-kubernetes-patch-strategy": "merge"
69+
},
70+
"simpleMap": {
71+
"description": "SimpleMap field.",
72+
"type": "object",
73+
"additionalProperties": {
74+
"type": "string"
75+
}
76+
},
77+
"replacingItem": {
78+
"description": "ReplacingItem field.",
79+
"allOf": [
80+
{"$ref": "#/components/schemas/io.k8s.apimachinery.pkg.runtime.RawExtension"}
81+
],
82+
"x-kubernetes-patch-strategy": "replace"
83+
},
84+
"retainKeysMap": {
85+
"description": "RetainKeysMap field.",
86+
"allOf": [
87+
{"$ref": "#/components/schemas/retainKeysMergeItem"}
88+
],
89+
"x-kubernetes-patch-strategy": "retainKeys"
90+
},
91+
"retainKeysMergingList": {
92+
"description": "RetainKeysMergingList field.",
93+
"type": "array",
94+
"items": {
95+
"$ref": "#/components/schemas/mergeItem"
96+
},
97+
"x-kubernetes-patch-merge-key": "name",
98+
"x-kubernetes-patch-strategy": "merge,retainKeys"
99+
}
100+
},
101+
"x-kubernetes-group-version-kind": [
102+
{
103+
"group": "fake-group",
104+
"kind": "mergeItem",
105+
"version": "some-version"
106+
}
107+
]
108+
},
109+
"retainKeysMergeItem": {
110+
"description": "RetainKeysMergeItem is type definition for testing strategic merge.",
111+
"required": [],
112+
"properties": {
113+
"name": {
114+
"description": "Name field.",
115+
"type": "string"
116+
},
117+
"value": {
118+
"description": "Value field.",
119+
"type": "string"
120+
},
121+
"other": {
122+
"description": "Other field.",
123+
"type": "string"
124+
},
125+
"simpleMap": {
126+
"description": "SimpleMap field.",
127+
"items": {
128+
"type": "string"
129+
}
130+
},
131+
"mergingList": {
132+
"description": "MergingList field.",
133+
"type": "array",
134+
"items": {
135+
"$ref": "#/components/schemas/mergeItem"
136+
},
137+
"x-kubernetes-patch-merge-key": "name",
138+
"x-kubernetes-patch-strategy": "merge"
139+
},
140+
"nonMergingList": {
141+
"description": "NonMergingList field.",
142+
"type": "array",
143+
"items": {
144+
"$ref": "#/components/schemas/mergeItem"
145+
}
146+
},
147+
"mergingIntList": {
148+
"description": "MergingIntList field.",
149+
"type": "array",
150+
"items": {
151+
"type": "integer",
152+
"format": "int32"
153+
},
154+
"x-kubernetes-patch-strategy": "merge"
155+
}
156+
},
157+
"x-kubernetes-group-version-kind": [
158+
{
159+
"group": "fake-group",
160+
"kind": "retainKeysMergeItem",
161+
"version": "some-version"
162+
}
163+
]
164+
},
165+
"io.k8s.apimachinery.pkg.runtime.RawExtension": {
166+
"description": "RawExtension is used to hold extensions in external versions.",
167+
"required": [
168+
"Raw"
169+
],
170+
"properties": {
171+
"Raw": {
172+
"description": "Raw is the underlying serialization of this object.",
173+
"type": "string",
174+
"format": "byte"
175+
}
176+
}
177+
}
178+
}
179+
}
180+
}

0 commit comments

Comments
 (0)