github.com/kubeshop/testkube@v1.17.23/internal/common/crd.go (about) 1 package common 2 3 import ( 4 "encoding/json" 5 "reflect" 6 "regexp" 7 8 "gopkg.in/yaml.v2" 9 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 "k8s.io/apimachinery/pkg/runtime" 11 "k8s.io/apimachinery/pkg/runtime/schema" 12 "k8s.io/client-go/kubernetes/scheme" 13 ) 14 15 type SerializeOptions struct { 16 OmitCreationTimestamp bool 17 CleanMeta bool 18 Kind string 19 GroupVersion *schema.GroupVersion 20 } 21 22 type ObjectWithTypeMeta interface { 23 SetGroupVersionKind(schema.GroupVersionKind) 24 } 25 26 func AppendTypeMeta(kind string, version schema.GroupVersion, crs ...ObjectWithTypeMeta) { 27 for _, cr := range crs { 28 cr.SetGroupVersionKind(schema.GroupVersionKind{ 29 Group: version.Group, 30 Version: version.Version, 31 Kind: kind, 32 }) 33 } 34 } 35 36 func CleanObjectMeta(crs ...metav1.Object) { 37 for _, cr := range crs { 38 cr.SetGeneration(0) 39 cr.SetResourceVersion("") 40 cr.SetSelfLink("") 41 cr.SetUID("") 42 cr.SetFinalizers(nil) 43 cr.SetOwnerReferences(nil) 44 cr.SetManagedFields(nil) 45 46 annotations := cr.GetAnnotations() 47 delete(annotations, "kubectl.kubernetes.io/last-applied-configuration") 48 cr.SetAnnotations(annotations) 49 } 50 } 51 52 var creationTsNullRegex = regexp.MustCompile(`\n\s+creationTimestamp: null`) 53 var creationTsRegex = regexp.MustCompile(`\n\s+creationTimestamp:[^\n]*`) 54 55 func SerializeCRD(cr interface{}, opts SerializeOptions) ([]byte, error) { 56 if opts.CleanMeta || (opts.Kind != "" && opts.GroupVersion != nil) { 57 // For simplicity, support both direct struct (as in *List.Items), as well as the pointer itself 58 if reflect.ValueOf(cr).Kind() == reflect.Struct { 59 v := reflect.ValueOf(cr) 60 p := reflect.New(v.Type()) 61 p.Elem().Set(v) 62 cr = p.Interface() 63 } 64 65 // Deep copy object, as it will have modifications 66 switch cr.(type) { 67 case runtime.Object: 68 cr = cr.(runtime.Object).DeepCopyObject() 69 } 70 71 // Clean messy metadata 72 if opts.CleanMeta { 73 if v, ok := cr.(metav1.Object); ok { 74 CleanObjectMeta(v) 75 cr = v 76 } 77 } 78 79 // Append metadata when expected 80 if opts.Kind != "" && opts.GroupVersion != nil { 81 if v, ok := cr.(ObjectWithTypeMeta); ok { 82 AppendTypeMeta(opts.Kind, *opts.GroupVersion, v) 83 cr = v 84 } 85 } 86 } 87 88 out, err := json.Marshal(cr) 89 if err != nil { 90 return nil, err 91 } 92 m := yaml.MapSlice{} 93 _ = yaml.Unmarshal(out, &m) 94 b, _ := yaml.Marshal(m) 95 if opts.OmitCreationTimestamp { 96 b = creationTsRegex.ReplaceAll(b, nil) 97 } else { 98 b = creationTsNullRegex.ReplaceAll(b, nil) 99 } 100 return b, err 101 } 102 103 var crdSeparator = []byte("---\n") 104 105 // SerializeCRDs builds a serialized version of CRD, 106 // persisting the order of properties from the struct. 107 func SerializeCRDs[T interface{}](crs []T, opts SerializeOptions) ([]byte, error) { 108 result := []byte(nil) 109 for _, cr := range crs { 110 b, err := SerializeCRD(cr, opts) 111 if err != nil { 112 return nil, err 113 } 114 if len(result) > 0 { 115 result = append(append(result, crdSeparator...), b...) 116 } else { 117 result = b 118 } 119 } 120 return result, nil 121 } 122 123 func DeserializeCRD(cr runtime.Object, content []byte) error { 124 _, _, err := scheme.Codecs.UniversalDeserializer().Decode(content, nil, cr) 125 return err 126 }