github.com/grahambrereton-form3/tilt@v0.10.18/internal/k8s/serialize.go (about)

     1  package k8s
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"io"
     7  	"strings"
     8  
     9  	v1 "k8s.io/api/core/v1"
    10  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    11  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    12  	"k8s.io/apimachinery/pkg/runtime"
    13  	yamlDecoder "k8s.io/apimachinery/pkg/util/yaml"
    14  	"k8s.io/client-go/kubernetes/scheme"
    15  	yamlEncoder "sigs.k8s.io/yaml"
    16  )
    17  
    18  func ParseYAMLFromString(yaml string) ([]K8sEntity, error) {
    19  	buf := bytes.NewBuffer([]byte(yaml))
    20  	return ParseYAML(buf)
    21  }
    22  
    23  func parseYAMLFromStringWithDeletedResources(yamlWithDeletedResources string) ([]K8sEntity, error) {
    24  	lines := strings.Split(yamlWithDeletedResources, "\n")
    25  	for len(lines) > 0 {
    26  		line := lines[0]
    27  		if !strings.HasSuffix(line, "deleted") {
    28  			break
    29  		}
    30  		lines = lines[1:]
    31  	}
    32  	return ParseYAMLFromString(strings.Join(lines, "\n"))
    33  }
    34  
    35  func decodeMetaList(list *metav1.List) ([]K8sEntity, error) {
    36  	result := make([]K8sEntity, 0, len(list.Items))
    37  	for _, item := range list.Items {
    38  		decoded, err := decodeRawExtension(item)
    39  		if err != nil {
    40  			return nil, err
    41  		}
    42  		result = append(result, decoded...)
    43  	}
    44  	return result, nil
    45  }
    46  
    47  func decodeList(list *v1.List) ([]K8sEntity, error) {
    48  	return decodeMetaList((*metav1.List)(list))
    49  }
    50  
    51  func decodeToRuntimeObj(ext runtime.RawExtension) (runtime.Object, error) {
    52  	ext.Raw = bytes.TrimSpace(ext.Raw)
    53  
    54  	// NOTE(nick): I LOL'd at the null check, but it's what kubectl does.
    55  	if len(ext.Raw) == 0 || bytes.Equal(ext.Raw, []byte("null")) {
    56  		return nil, nil
    57  	}
    58  
    59  	obj, _, err :=
    60  		scheme.Codecs.UniversalDeserializer().Decode(ext.Raw, nil, nil)
    61  	if err == nil {
    62  		return obj, nil
    63  	}
    64  
    65  	if !runtime.IsNotRegisteredError(err) {
    66  		return nil, err
    67  	}
    68  
    69  	// If this is a NotRegisteredError, fallback to unstructured code
    70  	obj, _, err =
    71  		unstructured.UnstructuredJSONScheme.Decode(ext.Raw, nil, nil)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  	return obj, nil
    76  }
    77  
    78  func decodeRawExtension(ext runtime.RawExtension) ([]K8sEntity, error) {
    79  	obj, err := decodeToRuntimeObj(ext)
    80  	if err != nil {
    81  		return nil, err
    82  	} else if obj == nil {
    83  		return nil, nil
    84  	}
    85  
    86  	// Check to see if this is a list, and we can decode the list elements.
    87  	list, isList := obj.(*v1.List)
    88  	if isList {
    89  		return decodeList(list)
    90  	}
    91  
    92  	metaList, isMetaList := obj.(*metav1.List)
    93  	if isMetaList {
    94  		return decodeMetaList(metaList)
    95  	}
    96  
    97  	return []K8sEntity{NewK8sEntity(obj)}, nil
    98  }
    99  
   100  // Parse the YAML into entities.
   101  // Loosely based on
   102  // https://github.com/kubernetes/cli-runtime/blob/d6a36215b15f83b94578f2ffce5d00447972e8ae/pkg/genericclioptions/resource/visitor.go#L583
   103  func ParseYAML(k8sYaml io.Reader) ([]K8sEntity, error) {
   104  	reader := bufio.NewReader(k8sYaml)
   105  	decoder := yamlDecoder.NewYAMLOrJSONDecoder(reader, 4096)
   106  
   107  	result := make([]K8sEntity, 0)
   108  	for {
   109  		ext := runtime.RawExtension{}
   110  		if err := decoder.Decode(&ext); err != nil {
   111  			if err == io.EOF {
   112  				break
   113  			}
   114  			return nil, err
   115  		}
   116  
   117  		entities, err := decodeRawExtension(ext)
   118  		if err != nil {
   119  			return nil, err
   120  		}
   121  		result = append(result, entities...)
   122  	}
   123  
   124  	return result, nil
   125  }
   126  
   127  // Serializes the provided K8s object as YAML to the given writer.
   128  //
   129  // By convention, all K8s objects contain ObjectMetadata, Spec, and Status.
   130  // This only serializes the metadata and spec, skipping the status.
   131  func serializeSpec(obj runtime.Object, w io.Writer) error {
   132  	json, err := specJSONIterator.Marshal(obj)
   133  	if err != nil {
   134  		return err
   135  	}
   136  	data, err := yamlEncoder.JSONToYAML(json)
   137  	if err != nil {
   138  		return err
   139  	}
   140  	_, err = w.Write(data)
   141  	return err
   142  }
   143  
   144  // Serializes the provided K8s objects as YAML.
   145  //
   146  // By convention, all K8s objects contain ObjectMetadata, Spec, and Status.
   147  // This only serializes the metadata and spec, skipping the status.
   148  func SerializeSpecYAML(decoded []K8sEntity) (string, error) {
   149  	buf := bytes.NewBuffer(nil)
   150  	for i, obj := range decoded {
   151  		if i != 0 {
   152  			buf.Write([]byte("\n---\n"))
   153  		}
   154  		err := serializeSpec(obj.Obj, buf)
   155  		if err != nil {
   156  			return "", err
   157  		}
   158  	}
   159  	return buf.String(), nil
   160  }