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 }