1
16
17 package strategicpatch
18
19 import (
20 "errors"
21 "fmt"
22 "reflect"
23 "strings"
24
25 "k8s.io/apimachinery/pkg/util/mergepatch"
26 forkedjson "k8s.io/apimachinery/third_party/forked/golang/json"
27 openapi "k8s.io/kube-openapi/pkg/util/proto"
28 "k8s.io/kube-openapi/pkg/validation/spec"
29 )
30
31 const patchMergeKey = "x-kubernetes-patch-merge-key"
32 const patchStrategy = "x-kubernetes-patch-strategy"
33
34 type PatchMeta struct {
35 patchStrategies []string
36 patchMergeKey string
37 }
38
39 func (pm *PatchMeta) GetPatchStrategies() []string {
40 if pm.patchStrategies == nil {
41 return []string{}
42 }
43 return pm.patchStrategies
44 }
45
46 func (pm *PatchMeta) SetPatchStrategies(ps []string) {
47 pm.patchStrategies = ps
48 }
49
50 func (pm *PatchMeta) GetPatchMergeKey() string {
51 return pm.patchMergeKey
52 }
53
54 func (pm *PatchMeta) SetPatchMergeKey(pmk string) {
55 pm.patchMergeKey = pmk
56 }
57
58 type LookupPatchMeta interface {
59
60 LookupPatchMetadataForStruct(key string) (LookupPatchMeta, PatchMeta, error)
61
62 LookupPatchMetadataForSlice(key string) (LookupPatchMeta, PatchMeta, error)
63
64 Name() string
65 }
66
67 type PatchMetaFromStruct struct {
68 T reflect.Type
69 }
70
71 func NewPatchMetaFromStruct(dataStruct interface{}) (PatchMetaFromStruct, error) {
72 t, err := getTagStructType(dataStruct)
73 return PatchMetaFromStruct{T: t}, err
74 }
75
76 var _ LookupPatchMeta = PatchMetaFromStruct{}
77
78 func (s PatchMetaFromStruct) LookupPatchMetadataForStruct(key string) (LookupPatchMeta, PatchMeta, error) {
79 fieldType, fieldPatchStrategies, fieldPatchMergeKey, err := forkedjson.LookupPatchMetadataForStruct(s.T, key)
80 if err != nil {
81 return nil, PatchMeta{}, err
82 }
83
84 return PatchMetaFromStruct{T: fieldType},
85 PatchMeta{
86 patchStrategies: fieldPatchStrategies,
87 patchMergeKey: fieldPatchMergeKey,
88 }, nil
89 }
90
91 func (s PatchMetaFromStruct) LookupPatchMetadataForSlice(key string) (LookupPatchMeta, PatchMeta, error) {
92 subschema, patchMeta, err := s.LookupPatchMetadataForStruct(key)
93 if err != nil {
94 return nil, PatchMeta{}, err
95 }
96 elemPatchMetaFromStruct := subschema.(PatchMetaFromStruct)
97 t := elemPatchMetaFromStruct.T
98
99 var elemType reflect.Type
100 switch t.Kind() {
101
102
103
104 case reflect.Array, reflect.Slice:
105 elemType = t.Elem()
106 if elemType.Kind() == reflect.Array || elemType.Kind() == reflect.Slice {
107 return nil, PatchMeta{}, errors.New("unexpected slice of slice")
108 }
109
110
111
112
113 case reflect.Pointer:
114 t = t.Elem()
115 if t.Kind() == reflect.Array || t.Kind() == reflect.Slice {
116 t = t.Elem()
117 }
118 elemType = t
119 default:
120 return nil, PatchMeta{}, fmt.Errorf("expected slice or array type, but got: %s", s.T.Kind().String())
121 }
122
123 return PatchMetaFromStruct{T: elemType}, patchMeta, nil
124 }
125
126 func (s PatchMetaFromStruct) Name() string {
127 return s.T.Kind().String()
128 }
129
130 func getTagStructType(dataStruct interface{}) (reflect.Type, error) {
131 if dataStruct == nil {
132 return nil, mergepatch.ErrBadArgKind(struct{}{}, nil)
133 }
134
135 t := reflect.TypeOf(dataStruct)
136
137 if t.Kind() == reflect.Pointer {
138 t = t.Elem()
139 }
140
141 if t.Kind() != reflect.Struct {
142 return nil, mergepatch.ErrBadArgKind(struct{}{}, dataStruct)
143 }
144
145 return t, nil
146 }
147
148 func GetTagStructTypeOrDie(dataStruct interface{}) reflect.Type {
149 t, err := getTagStructType(dataStruct)
150 if err != nil {
151 panic(err)
152 }
153 return t
154 }
155
156 type PatchMetaFromOpenAPIV3 struct {
157
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
240 type PatchMetaFromOpenAPI struct {
241 Schema openapi.Schema
242 }
243
244 func NewPatchMetaFromOpenAPI(s openapi.Schema) PatchMetaFromOpenAPI {
245 return PatchMetaFromOpenAPI{Schema: s}
246 }
247
248 var _ LookupPatchMeta = PatchMetaFromOpenAPI{}
249
250 func (s PatchMetaFromOpenAPI) LookupPatchMetadataForStruct(key string) (LookupPatchMeta, PatchMeta, error) {
251 if s.Schema == nil {
252 return nil, PatchMeta{}, nil
253 }
254 kindItem := NewKindItem(key, s.Schema.GetPath())
255 s.Schema.Accept(kindItem)
256
257 err := kindItem.Error()
258 if err != nil {
259 return nil, PatchMeta{}, err
260 }
261 return PatchMetaFromOpenAPI{Schema: kindItem.subschema},
262 kindItem.patchmeta, nil
263 }
264
265 func (s PatchMetaFromOpenAPI) LookupPatchMetadataForSlice(key string) (LookupPatchMeta, PatchMeta, error) {
266 if s.Schema == nil {
267 return nil, PatchMeta{}, nil
268 }
269 sliceItem := NewSliceItem(key, s.Schema.GetPath())
270 s.Schema.Accept(sliceItem)
271
272 err := sliceItem.Error()
273 if err != nil {
274 return nil, PatchMeta{}, err
275 }
276 return PatchMetaFromOpenAPI{Schema: sliceItem.subschema},
277 sliceItem.patchmeta, nil
278 }
279
280 func (s PatchMetaFromOpenAPI) Name() string {
281 schema := s.Schema
282 return schema.GetName()
283 }
284
View as plain text