github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/kubernetes/manifest/manifests.go (about) 1 /* 2 Copyright 2019 The Skaffold 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 manifest 18 19 import ( 20 "bufio" 21 "bytes" 22 "io" 23 "regexp" 24 "strings" 25 26 k8syaml "k8s.io/apimachinery/pkg/util/yaml" 27 ) 28 29 // ManifestList is a list of yaml manifests. 30 // 31 //nolint:golint 32 type ManifestList [][]byte 33 34 // Load uses the Kubernetes `apimachinery` to split YAML content into a set of YAML documents. 35 func Load(in io.Reader) (ManifestList, error) { 36 r := k8syaml.NewYAMLReader(bufio.NewReader(in)) 37 var docs [][]byte 38 for { 39 doc, err := r.Read() 40 switch { 41 case err == io.EOF: 42 return ManifestList(docs), nil 43 case err != nil: 44 return nil, err 45 default: 46 docs = append(docs, doc) 47 } 48 } 49 } 50 51 func (l *ManifestList) String() string { 52 var str string 53 for i, manifest := range *l { 54 if i != 0 { 55 str += "\n---\n" 56 } 57 str += string(bytes.TrimSpace(manifest)) 58 } 59 return str 60 } 61 62 // Append appends the yaml manifests defined in the given buffer. 63 // `buf` can contain concatenated manifests without `---` separators 64 // because `kubectl create --dry-run -oyaml` produces such output. 65 func (l *ManifestList) Append(buf []byte) { 66 // If there's at most one `apiVersion` field, then append the `buf` as is. 67 if len(regexp.MustCompile("(?m)^apiVersion:").FindAll(buf, -1)) <= 1 { 68 *l = append(*l, buf) 69 return 70 } 71 72 // If there are `---` separators, then append each individual manifest as is. 73 parts := bytes.Split(buf, []byte("\n---\n")) 74 if len(parts) > 1 { 75 *l = append(*l, parts...) 76 return 77 } 78 79 // There are no `---` separators, let's identify each individual manifest 80 // based on the top level keys lexicographical order. 81 yaml := string(buf) 82 83 var part string 84 var previousKey = "" 85 86 for _, line := range strings.Split(yaml, "\n") { 87 // Not a top level key. 88 if strings.HasPrefix(line, "-") || strings.HasPrefix(line, " ") || !strings.Contains(line, ":") { 89 part += "\n" + line 90 continue 91 } 92 93 // Top level key. 94 key := line[0:strings.Index(line, ":")] 95 if strings.Compare(key, previousKey) > 0 { 96 if part != "" { 97 part += "\n" 98 } 99 part += line 100 } else { 101 *l = append(*l, []byte(part)) 102 part = line 103 } 104 105 previousKey = key 106 } 107 108 *l = append(*l, []byte(part)) 109 } 110 111 // Diff computes the list of manifests that have changed. 112 func (l *ManifestList) Diff(latest ManifestList) ManifestList { 113 if l == nil { 114 return latest 115 } 116 117 oldManifests := map[string]bool{} 118 for _, oldManifest := range *l { 119 oldManifests[string(oldManifest)] = true 120 } 121 122 var updated ManifestList 123 124 for _, manifest := range latest { 125 if !oldManifests[string(manifest)] { 126 updated = append(updated, manifest) 127 } 128 } 129 130 return updated 131 } 132 133 // Reader returns a reader on the raw yaml descriptors. 134 func (l *ManifestList) Reader() io.Reader { 135 return strings.NewReader(l.String()) 136 }