blob: f84d65aacb3d8ee69c13ffd24b1360b76c678455 [file] [log] [blame]
Matthias Andreas Benkard832a54e2019-01-29 09:27:38 +01001/*
2Copyright 2017 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package strategicpatch
18
19import (
20 "errors"
21 "strings"
22
23 "k8s.io/apimachinery/pkg/util/mergepatch"
24 openapi "k8s.io/kube-openapi/pkg/util/proto"
25)
26
27const (
28 patchStrategyOpenapiextensionKey = "x-kubernetes-patch-strategy"
29 patchMergeKeyOpenapiextensionKey = "x-kubernetes-patch-merge-key"
30)
31
32type LookupPatchItem interface {
33 openapi.SchemaVisitor
34
35 Error() error
36 Path() *openapi.Path
37}
38
39type kindItem struct {
40 key string
41 path *openapi.Path
42 err error
43 patchmeta PatchMeta
44 subschema openapi.Schema
45 hasVisitKind bool
46}
47
48func NewKindItem(key string, path *openapi.Path) *kindItem {
49 return &kindItem{
50 key: key,
51 path: path,
52 }
53}
54
55var _ LookupPatchItem = &kindItem{}
56
57func (item *kindItem) Error() error {
58 return item.err
59}
60
61func (item *kindItem) Path() *openapi.Path {
62 return item.path
63}
64
65func (item *kindItem) VisitPrimitive(schema *openapi.Primitive) {
66 item.err = errors.New("expected kind, but got primitive")
67}
68
69func (item *kindItem) VisitArray(schema *openapi.Array) {
70 item.err = errors.New("expected kind, but got slice")
71}
72
73func (item *kindItem) VisitMap(schema *openapi.Map) {
74 item.err = errors.New("expected kind, but got map")
75}
76
77func (item *kindItem) VisitReference(schema openapi.Reference) {
78 if !item.hasVisitKind {
79 schema.SubSchema().Accept(item)
80 }
81}
82
83func (item *kindItem) VisitKind(schema *openapi.Kind) {
84 subschema, ok := schema.Fields[item.key]
85 if !ok {
86 item.err = FieldNotFoundError{Path: schema.GetPath().String(), Field: item.key}
87 return
88 }
89
90 mergeKey, patchStrategies, err := parsePatchMetadata(subschema.GetExtensions())
91 if err != nil {
92 item.err = err
93 return
94 }
95 item.patchmeta = PatchMeta{
96 patchStrategies: patchStrategies,
97 patchMergeKey: mergeKey,
98 }
99 item.subschema = subschema
100}
101
102type sliceItem struct {
103 key string
104 path *openapi.Path
105 err error
106 patchmeta PatchMeta
107 subschema openapi.Schema
108 hasVisitKind bool
109}
110
111func NewSliceItem(key string, path *openapi.Path) *sliceItem {
112 return &sliceItem{
113 key: key,
114 path: path,
115 }
116}
117
118var _ LookupPatchItem = &sliceItem{}
119
120func (item *sliceItem) Error() error {
121 return item.err
122}
123
124func (item *sliceItem) Path() *openapi.Path {
125 return item.path
126}
127
128func (item *sliceItem) VisitPrimitive(schema *openapi.Primitive) {
129 item.err = errors.New("expected slice, but got primitive")
130}
131
132func (item *sliceItem) VisitArray(schema *openapi.Array) {
133 if !item.hasVisitKind {
134 item.err = errors.New("expected visit kind first, then visit array")
135 }
136 subschema := schema.SubType
137 item.subschema = subschema
138}
139
140func (item *sliceItem) VisitMap(schema *openapi.Map) {
141 item.err = errors.New("expected slice, but got map")
142}
143
144func (item *sliceItem) VisitReference(schema openapi.Reference) {
145 if !item.hasVisitKind {
146 schema.SubSchema().Accept(item)
147 } else {
148 item.subschema = schema.SubSchema()
149 }
150}
151
152func (item *sliceItem) VisitKind(schema *openapi.Kind) {
153 subschema, ok := schema.Fields[item.key]
154 if !ok {
155 item.err = FieldNotFoundError{Path: schema.GetPath().String(), Field: item.key}
156 return
157 }
158
159 mergeKey, patchStrategies, err := parsePatchMetadata(subschema.GetExtensions())
160 if err != nil {
161 item.err = err
162 return
163 }
164 item.patchmeta = PatchMeta{
165 patchStrategies: patchStrategies,
166 patchMergeKey: mergeKey,
167 }
168 item.hasVisitKind = true
169 subschema.Accept(item)
170}
171
172func parsePatchMetadata(extensions map[string]interface{}) (string, []string, error) {
173 ps, foundPS := extensions[patchStrategyOpenapiextensionKey]
174 var patchStrategies []string
175 var mergeKey, patchStrategy string
176 var ok bool
177 if foundPS {
178 patchStrategy, ok = ps.(string)
179 if ok {
180 patchStrategies = strings.Split(patchStrategy, ",")
181 } else {
182 return "", nil, mergepatch.ErrBadArgType(patchStrategy, ps)
183 }
184 }
185 mk, foundMK := extensions[patchMergeKeyOpenapiextensionKey]
186 if foundMK {
187 mergeKey, ok = mk.(string)
188 if !ok {
189 return "", nil, mergepatch.ErrBadArgType(mergeKey, mk)
190 }
191 }
192 return mergeKey, patchStrategies, nil
193}