github.com/spotmaxtech/k8s-apimachinery-v0260@v0.0.1/pkg/api/apitesting/roundtrip/construct.go (about)

     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 "github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/api/meta"
    27  	metav1 "github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/apis/meta/v1"
    28  	"github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/runtime"
    29  	"github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/runtime/schema"
    30  	"github.com/spotmaxtech/k8s-apimachinery-v0260/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  		fill(dataString, dataInt, t.Elem(), v.Index(0), fillFuncs, filledTypes)
   114  
   115  	case reflect.Map:
   116  		// construct the key, which must be a string type, possibly converted to a type alias of string
   117  		key := reflect.ValueOf(dataString + "Key").Convert(t.Key())
   118  		// construct a zero-value item
   119  		item := reflect.New(t.Elem())
   120  		// recurse to populate the item, preserving the data context
   121  		fill(dataString, dataInt, t.Elem(), item.Elem(), fillFuncs, filledTypes)
   122  		// store in the map
   123  		v.Set(reflect.MakeMap(t))
   124  		v.SetMapIndex(key, item.Elem())
   125  
   126  	case reflect.Struct:
   127  		for i := 0; i < t.NumField(); i++ {
   128  			field := t.Field(i)
   129  
   130  			if !field.IsExported() {
   131  				continue
   132  			}
   133  
   134  			// use the json field name, which must be stable
   135  			dataString := strings.Split(field.Tag.Get("json"), ",")[0]
   136  			if len(dataString) == 0 {
   137  				// fall back to the struct field name if there is no json field name
   138  				dataString = "<no json tag> " + field.Name
   139  			}
   140  
   141  			// use the protobuf tag, which must be stable
   142  			dataInt := 0
   143  			if protobufTagParts := strings.Split(field.Tag.Get("protobuf"), ","); len(protobufTagParts) > 1 {
   144  				if tag, err := strconv.Atoi(protobufTagParts[1]); err != nil {
   145  					panic(err)
   146  				} else {
   147  					dataInt = tag
   148  				}
   149  			}
   150  			if dataInt == 0 {
   151  				// fall back to the length of dataString as a backup
   152  				dataInt = -len(dataString)
   153  			}
   154  
   155  			fieldType := field.Type
   156  			fieldValue := v.Field(i)
   157  
   158  			fill(dataString, dataInt, reflect.PointerTo(fieldType), fieldValue.Addr(), fillFuncs, filledTypes)
   159  		}
   160  
   161  	case reflect.Pointer:
   162  		fill(dataString, dataInt, t.Elem(), v.Elem(), fillFuncs, filledTypes)
   163  
   164  	case reflect.String:
   165  		// use Convert to set into string alias types correctly
   166  		v.Set(reflect.ValueOf(dataString + "Value").Convert(t))
   167  
   168  	case reflect.Bool:
   169  		// set to true to ensure we serialize omitempty fields
   170  		v.Set(reflect.ValueOf(true).Convert(t))
   171  
   172  	case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
   173  		// use Convert to set into int alias types and different int widths correctly
   174  		v.Set(reflect.ValueOf(dataInt).Convert(t))
   175  
   176  	default:
   177  		panic(fmt.Errorf("unhandled type %v in field %s", t, dataString))
   178  	}
   179  }