1/* 2Copyright 2018 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 fieldmanager 18 19import ( 20 "fmt" 21 22 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 23 "k8s.io/apimachinery/pkg/runtime" 24 "k8s.io/apimachinery/pkg/runtime/schema" 25 "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal" 26 "k8s.io/kube-openapi/pkg/util/proto" 27 "sigs.k8s.io/structured-merge-diff/v4/typed" 28 "sigs.k8s.io/structured-merge-diff/v4/value" 29) 30 31// TypeConverter allows you to convert from runtime.Object to 32// typed.TypedValue and the other way around. 33type TypeConverter interface { 34 ObjectToTyped(runtime.Object) (*typed.TypedValue, error) 35 TypedToObject(*typed.TypedValue) (runtime.Object, error) 36} 37 38// DeducedTypeConverter is a TypeConverter for CRDs that don't have a 39// schema. It does implement the same interface though (and create the 40// same types of objects), so that everything can still work the same. 41// CRDs are merged with all their fields being "atomic" (lists 42// included). 43// 44// Note that this is not going to be sufficient for converting to/from 45// CRDs that have a schema defined (we don't support that schema yet). 46// TODO(jennybuckley): Use the schema provided by a CRD if it exists. 47type DeducedTypeConverter struct{} 48 49var _ TypeConverter = DeducedTypeConverter{} 50 51// ObjectToTyped converts an object into a TypedValue with a "deduced type". 52func (DeducedTypeConverter) ObjectToTyped(obj runtime.Object) (*typed.TypedValue, error) { 53 switch o := obj.(type) { 54 case *unstructured.Unstructured: 55 return typed.DeducedParseableType.FromUnstructured(o.UnstructuredContent()) 56 default: 57 return typed.DeducedParseableType.FromStructured(obj) 58 } 59} 60 61// TypedToObject transforms the typed value into a runtime.Object. That 62// is not specific to deduced type. 63func (DeducedTypeConverter) TypedToObject(value *typed.TypedValue) (runtime.Object, error) { 64 return valueToObject(value.AsValue()) 65} 66 67type typeConverter struct { 68 parser *internal.GvkParser 69} 70 71var _ TypeConverter = &typeConverter{} 72 73// NewTypeConverter builds a TypeConverter from a proto.Models. This 74// will automatically find the proper version of the object, and the 75// corresponding schema information. 76func NewTypeConverter(models proto.Models, preserveUnknownFields bool) (TypeConverter, error) { 77 parser, err := internal.NewGVKParser(models, preserveUnknownFields) 78 if err != nil { 79 return nil, err 80 } 81 return &typeConverter{parser: parser}, nil 82} 83 84func (c *typeConverter) ObjectToTyped(obj runtime.Object) (*typed.TypedValue, error) { 85 gvk := obj.GetObjectKind().GroupVersionKind() 86 t := c.parser.Type(gvk) 87 if t == nil { 88 return nil, newNoCorrespondingTypeError(gvk) 89 } 90 switch o := obj.(type) { 91 case *unstructured.Unstructured: 92 return t.FromUnstructured(o.UnstructuredContent()) 93 default: 94 return t.FromStructured(obj) 95 } 96} 97 98func (c *typeConverter) TypedToObject(value *typed.TypedValue) (runtime.Object, error) { 99 return valueToObject(value.AsValue()) 100} 101 102func valueToObject(val value.Value) (runtime.Object, error) { 103 vu := val.Unstructured() 104 switch o := vu.(type) { 105 case map[string]interface{}: 106 return &unstructured.Unstructured{Object: o}, nil 107 default: 108 return nil, fmt.Errorf("failed to convert value to unstructured for type %T", vu) 109 } 110} 111 112type noCorrespondingTypeErr struct { 113 gvk schema.GroupVersionKind 114} 115 116func newNoCorrespondingTypeError(gvk schema.GroupVersionKind) error { 117 return &noCorrespondingTypeErr{gvk: gvk} 118} 119 120func (k *noCorrespondingTypeErr) Error() string { 121 return fmt.Sprintf("no corresponding type for %v", k.gvk) 122} 123 124func isNoCorrespondingTypeError(err error) bool { 125 if err == nil { 126 return false 127 } 128 _, ok := err.(*noCorrespondingTypeErr) 129 return ok 130} 131