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  }