...

Source file src/k8s.io/apimachinery/pkg/util/managedfields/internal/typeconverter.go

Documentation: k8s.io/apimachinery/pkg/util/managedfields/internal

     1  /*
     2  Copyright 2022 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 internal
    18  
    19  import (
    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/kube-openapi/pkg/schemaconv"
    26  	"k8s.io/kube-openapi/pkg/validation/spec"
    27  	smdschema "sigs.k8s.io/structured-merge-diff/v4/schema"
    28  	"sigs.k8s.io/structured-merge-diff/v4/typed"
    29  	"sigs.k8s.io/structured-merge-diff/v4/value"
    30  )
    31  
    32  // TypeConverter allows you to convert from runtime.Object to
    33  // typed.TypedValue and the other way around.
    34  type TypeConverter interface {
    35  	ObjectToTyped(runtime.Object, ...typed.ValidationOptions) (*typed.TypedValue, error)
    36  	TypedToObject(*typed.TypedValue) (runtime.Object, error)
    37  }
    38  
    39  type typeConverter struct {
    40  	parser map[schema.GroupVersionKind]*typed.ParseableType
    41  }
    42  
    43  var _ TypeConverter = &typeConverter{}
    44  
    45  func NewTypeConverter(openapiSpec map[string]*spec.Schema, preserveUnknownFields bool) (TypeConverter, error) {
    46  	typeSchema, err := schemaconv.ToSchemaFromOpenAPI(openapiSpec, preserveUnknownFields)
    47  	if err != nil {
    48  		return nil, fmt.Errorf("failed to convert models to schema: %v", err)
    49  	}
    50  
    51  	typeParser := typed.Parser{Schema: smdschema.Schema{Types: typeSchema.Types}}
    52  	tr := indexModels(&typeParser, openapiSpec)
    53  
    54  	return &typeConverter{parser: tr}, nil
    55  }
    56  
    57  func (c *typeConverter) ObjectToTyped(obj runtime.Object, opts ...typed.ValidationOptions) (*typed.TypedValue, error) {
    58  	gvk := obj.GetObjectKind().GroupVersionKind()
    59  	t := c.parser[gvk]
    60  	if t == nil {
    61  		return nil, NewNoCorrespondingTypeError(gvk)
    62  	}
    63  	switch o := obj.(type) {
    64  	case *unstructured.Unstructured:
    65  		return t.FromUnstructured(o.UnstructuredContent(), opts...)
    66  	default:
    67  		return t.FromStructured(obj, opts...)
    68  	}
    69  }
    70  
    71  func (c *typeConverter) TypedToObject(value *typed.TypedValue) (runtime.Object, error) {
    72  	return valueToObject(value.AsValue())
    73  }
    74  
    75  type deducedTypeConverter struct{}
    76  
    77  // DeducedTypeConverter is a TypeConverter for CRDs that don't have a
    78  // schema. It does implement the same interface though (and create the
    79  // same types of objects), so that everything can still work the same.
    80  // CRDs are merged with all their fields being "atomic" (lists
    81  // included).
    82  func NewDeducedTypeConverter() TypeConverter {
    83  	return deducedTypeConverter{}
    84  }
    85  
    86  // ObjectToTyped converts an object into a TypedValue with a "deduced type".
    87  func (deducedTypeConverter) ObjectToTyped(obj runtime.Object, opts ...typed.ValidationOptions) (*typed.TypedValue, error) {
    88  	switch o := obj.(type) {
    89  	case *unstructured.Unstructured:
    90  		return typed.DeducedParseableType.FromUnstructured(o.UnstructuredContent(), opts...)
    91  	default:
    92  		return typed.DeducedParseableType.FromStructured(obj, opts...)
    93  	}
    94  }
    95  
    96  // TypedToObject transforms the typed value into a runtime.Object. That
    97  // is not specific to deduced type.
    98  func (deducedTypeConverter) TypedToObject(value *typed.TypedValue) (runtime.Object, error) {
    99  	return valueToObject(value.AsValue())
   100  }
   101  
   102  func 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  
   112  func indexModels(
   113  	typeParser *typed.Parser,
   114  	openAPISchemas map[string]*spec.Schema,
   115  ) map[schema.GroupVersionKind]*typed.ParseableType {
   116  	tr := map[schema.GroupVersionKind]*typed.ParseableType{}
   117  	for modelName, model := range openAPISchemas {
   118  		gvkList := parseGroupVersionKind(model.Extensions)
   119  		if len(gvkList) == 0 {
   120  			continue
   121  		}
   122  
   123  		parsedType := typeParser.Type(modelName)
   124  		for _, gvk := range gvkList {
   125  			if len(gvk.Kind) > 0 {
   126  				tr[schema.GroupVersionKind(gvk)] = &parsedType
   127  			}
   128  		}
   129  	}
   130  	return tr
   131  }
   132  
   133  // Get and parse GroupVersionKind from the extension. Returns empty if it doesn't have one.
   134  func parseGroupVersionKind(extensions map[string]interface{}) []schema.GroupVersionKind {
   135  	gvkListResult := []schema.GroupVersionKind{}
   136  
   137  	// Get the extensions
   138  	gvkExtension, ok := extensions["x-kubernetes-group-version-kind"]
   139  	if !ok {
   140  		return []schema.GroupVersionKind{}
   141  	}
   142  
   143  	// gvk extension must be a list of at least 1 element.
   144  	gvkList, ok := gvkExtension.([]interface{})
   145  	if !ok {
   146  		return []schema.GroupVersionKind{}
   147  	}
   148  
   149  	for _, gvk := range gvkList {
   150  		var group, version, kind string
   151  
   152  		// gvk extension list must be a map with group, version, and
   153  		// kind fields
   154  		if gvkMap, ok := gvk.(map[interface{}]interface{}); ok {
   155  			group, ok = gvkMap["group"].(string)
   156  			if !ok {
   157  				continue
   158  			}
   159  			version, ok = gvkMap["version"].(string)
   160  			if !ok {
   161  				continue
   162  			}
   163  			kind, ok = gvkMap["kind"].(string)
   164  			if !ok {
   165  				continue
   166  			}
   167  
   168  		} else if gvkMap, ok := gvk.(map[string]interface{}); ok {
   169  			group, ok = gvkMap["group"].(string)
   170  			if !ok {
   171  				continue
   172  			}
   173  			version, ok = gvkMap["version"].(string)
   174  			if !ok {
   175  				continue
   176  			}
   177  			kind, ok = gvkMap["kind"].(string)
   178  			if !ok {
   179  				continue
   180  			}
   181  		} else {
   182  			continue
   183  		}
   184  
   185  		gvkListResult = append(gvkListResult, schema.GroupVersionKind{
   186  			Group:   group,
   187  			Version: version,
   188  			Kind:    kind,
   189  		})
   190  	}
   191  
   192  	return gvkListResult
   193  }
   194  

View as plain text