...

Source file src/k8s.io/apimachinery/pkg/api/apitesting/roundtrip/construct.go

Documentation: k8s.io/apimachinery/pkg/api/apitesting/roundtrip

     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 roundtrip
    18  
    19  import (
    20  	"fmt"
    21  	"reflect"
    22  	"strconv"
    23  	"strings"
    24  	"time"
    25  
    26  	apimeta "k8s.io/apimachinery/pkg/api/meta"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	"k8s.io/apimachinery/pkg/runtime/schema"
    30  	"k8s.io/apimachinery/pkg/util/intstr"
    31  )
    32  
    33  func defaultFillFuncs() map[reflect.Type]FillFunc {
    34  	funcs := map[reflect.Type]FillFunc{}
    35  	funcs[reflect.TypeOf(&runtime.RawExtension{})] = func(s string, i int, obj interface{}) {
    36  		// generate a raw object in normalized form
    37  		// TODO: test non-normalized round-tripping... YAMLToJSON normalizes and makes exact comparisons fail
    38  		obj.(*runtime.RawExtension).Raw = []byte(`{"apiVersion":"example.com/v1","kind":"CustomType","spec":{"replicas":1},"status":{"available":1}}`)
    39  	}
    40  	funcs[reflect.TypeOf(&metav1.TypeMeta{})] = func(s string, i int, obj interface{}) {
    41  		// APIVersion and Kind are not serialized in all formats (notably protobuf), so clear by default for cross-format checking.
    42  		obj.(*metav1.TypeMeta).APIVersion = ""
    43  		obj.(*metav1.TypeMeta).Kind = ""
    44  	}
    45  	funcs[reflect.TypeOf(&metav1.FieldsV1{})] = func(s string, i int, obj interface{}) {
    46  		obj.(*metav1.FieldsV1).Raw = []byte(`{}`)
    47  	}
    48  	funcs[reflect.TypeOf(&metav1.Time{})] = func(s string, i int, obj interface{}) {
    49  		// use the integer as an offset from the year
    50  		obj.(*metav1.Time).Time = time.Date(2000+i, 1, 1, 1, 1, 1, 0, time.UTC)
    51  	}
    52  	funcs[reflect.TypeOf(&metav1.MicroTime{})] = func(s string, i int, obj interface{}) {
    53  		// use the integer as an offset from the year, and as a microsecond
    54  		obj.(*metav1.MicroTime).Time = time.Date(2000+i, 1, 1, 1, 1, 1, i*int(time.Microsecond), time.UTC)
    55  	}
    56  	funcs[reflect.TypeOf(&intstr.IntOrString{})] = func(s string, i int, obj interface{}) {
    57  		// use the string as a string value
    58  		obj.(*intstr.IntOrString).Type = intstr.String
    59  		obj.(*intstr.IntOrString).StrVal = s + "Value"
    60  	}
    61  	return funcs
    62  }
    63  
    64  // CompatibilityTestObject returns a deterministically filled object for the specified GVK
    65  func CompatibilityTestObject(scheme *runtime.Scheme, gvk schema.GroupVersionKind, fillFuncs map[reflect.Type]FillFunc) (runtime.Object, error) {
    66  	// Construct the object
    67  	obj, err := scheme.New(gvk)
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  
    72  	fill("", 0, reflect.TypeOf(obj), reflect.ValueOf(obj), fillFuncs, map[reflect.Type]bool{})
    73  
    74  	// Set the kind and apiVersion
    75  	if typeAcc, err := apimeta.TypeAccessor(obj); err != nil {
    76  		return nil, err
    77  	} else {
    78  		typeAcc.SetKind(gvk.Kind)
    79  		typeAcc.SetAPIVersion(gvk.GroupVersion().String())
    80  	}
    81  
    82  	return obj, nil
    83  }
    84  
    85  func fill(dataString string, dataInt int, t reflect.Type, v reflect.Value, fillFuncs map[reflect.Type]FillFunc, filledTypes map[reflect.Type]bool) {
    86  	if filledTypes[t] {
    87  		// we already filled this type, avoid recursing infinitely
    88  		return
    89  	}
    90  	filledTypes[t] = true
    91  	defer delete(filledTypes, t)
    92  
    93  	// if nil, populate pointers with a zero-value instance of the underlying type
    94  	if t.Kind() == reflect.Pointer && v.IsNil() {
    95  		if v.CanSet() {
    96  			v.Set(reflect.New(t.Elem()))
    97  		} else if v.IsNil() {
    98  			panic(fmt.Errorf("unsettable nil pointer of type %v in field %s", t, dataString))
    99  		}
   100  	}
   101  
   102  	if f, ok := fillFuncs[t]; ok {
   103  		// use the custom fill function for this type
   104  		f(dataString, dataInt, v.Interface())
   105  		return
   106  	}
   107  
   108  	switch t.Kind() {
   109  	case reflect.Slice:
   110  		// populate with a single-item slice
   111  		v.Set(reflect.MakeSlice(t, 1, 1))
   112  		// recurse to populate the item, preserving the data context
   113  		if t.Elem().Kind() == reflect.Pointer {
   114  			fill(dataString, dataInt, t.Elem(), v.Index(0), fillFuncs, filledTypes)
   115  		} else {
   116  			fill(dataString, dataInt, reflect.PointerTo(t.Elem()), v.Index(0).Addr(), fillFuncs, filledTypes)
   117  		}
   118  
   119  	case reflect.Map:
   120  		// construct the key, which must be a string type, possibly converted to a type alias of string
   121  		key := reflect.ValueOf(dataString + "Key").Convert(t.Key())
   122  		// construct a zero-value item
   123  		item := reflect.New(t.Elem())
   124  		// recurse to populate the item, preserving the data context
   125  		fill(dataString, dataInt, t.Elem(), item.Elem(), fillFuncs, filledTypes)
   126  		// store in the map
   127  		v.Set(reflect.MakeMap(t))
   128  		v.SetMapIndex(key, item.Elem())
   129  
   130  	case reflect.Struct:
   131  		for i := 0; i < t.NumField(); i++ {
   132  			field := t.Field(i)
   133  
   134  			if !field.IsExported() {
   135  				continue
   136  			}
   137  
   138  			// use the json field name, which must be stable
   139  			dataString := strings.Split(field.Tag.Get("json"), ",")[0]
   140  			if dataString == "-" {
   141  				// unserialized field, no need to fill it
   142  				continue
   143  			}
   144  			if len(dataString) == 0 {
   145  				// fall back to the struct field name if there is no json field name
   146  				dataString = "<no json tag> " + field.Name
   147  			}
   148  
   149  			// use the protobuf tag, which must be stable
   150  			dataInt := 0
   151  			if protobufTagParts := strings.Split(field.Tag.Get("protobuf"), ","); len(protobufTagParts) > 1 {
   152  				if tag, err := strconv.Atoi(protobufTagParts[1]); err != nil {
   153  					panic(err)
   154  				} else {
   155  					dataInt = tag
   156  				}
   157  			}
   158  			if dataInt == 0 {
   159  				// fall back to the length of dataString as a backup
   160  				dataInt = -len(dataString)
   161  			}
   162  
   163  			fieldType := field.Type
   164  			fieldValue := v.Field(i)
   165  
   166  			fill(dataString, dataInt, reflect.PointerTo(fieldType), fieldValue.Addr(), fillFuncs, filledTypes)
   167  		}
   168  
   169  	case reflect.Pointer:
   170  		fill(dataString, dataInt, t.Elem(), v.Elem(), fillFuncs, filledTypes)
   171  
   172  	case reflect.String:
   173  		// use Convert to set into string alias types correctly
   174  		v.Set(reflect.ValueOf(dataString + "Value").Convert(t))
   175  
   176  	case reflect.Bool:
   177  		// set to true to ensure we serialize omitempty fields
   178  		v.Set(reflect.ValueOf(true).Convert(t))
   179  
   180  	case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
   181  		// use Convert to set into int alias types and different int widths correctly
   182  		v.Set(reflect.ValueOf(dataInt).Convert(t))
   183  
   184  	case reflect.Float32, reflect.Float64:
   185  		// use Convert to set into float types
   186  		v.Set(reflect.ValueOf(float32(dataInt) + 0.5).Convert(t))
   187  
   188  	default:
   189  		panic(fmt.Errorf("unhandled type %v in field %s", t, dataString))
   190  	}
   191  }
   192  

View as plain text