...

Source file src/k8s.io/apimachinery/pkg/util/strategicpatch/meta.go

Documentation: k8s.io/apimachinery/pkg/util/strategicpatch

     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  }
   284  

View as plain text