...

Source file src/kubevirt.io/api/apitesting/roundtrip/construct.go

Documentation: kubevirt.io/api/apitesting/roundtrip

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

View as plain text