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 }