k8s.io/apimachinery@v0.29.2/pkg/util/strategicpatch/meta.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 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 // LookupPatchMetadataForStruct gets subschema and the patch metadata (e.g. patch strategy and merge key) for map. 60 LookupPatchMetadataForStruct(key string) (LookupPatchMeta, PatchMeta, error) 61 // LookupPatchMetadataForSlice get subschema and the patch metadata for slice. 62 LookupPatchMetadataForSlice(key string) (LookupPatchMeta, PatchMeta, error) 63 // Get the type name of the field 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 // If t is an array or a slice, get the element type. 102 // If element is still an array or a slice, return an error. 103 // Otherwise, return element type. 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 // If t is an pointer, get the underlying element. 110 // If the underlying element is neither an array nor a slice, the pointer is pointing to a slice, 111 // e.g. https://github.com/kubernetes/kubernetes/blob/bc22e206c79282487ea0bf5696d5ccec7e839a76/staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/patch_test.go#L2782-L2822 112 // If the underlying element is either an array or a slice, return its element type. 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 // Get the underlying type for pointers 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 // 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 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 }