istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/config/file/util/kubeyaml/kubeyaml.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package kubeyaml 16 17 import ( 18 "bufio" 19 "bytes" 20 "io" 21 "strings" 22 "unicode" 23 ) 24 25 const ( 26 yamlSeparator = "---\n" 27 separator = "---" 28 ) 29 30 // Join the given yaml parts into a single multipart document. 31 func Join(parts ...[]byte) []byte { 32 var b bytes.Buffer 33 34 var lastIsNewLine bool 35 for _, p := range parts { 36 if len(p) == 0 { 37 continue 38 } 39 if b.Len() != 0 { 40 if !lastIsNewLine { 41 _, _ = b.WriteString("\n") 42 } 43 b.WriteString(yamlSeparator) 44 } 45 _, _ = b.Write(p) 46 s := string(p) 47 lastIsNewLine = s[len(s)-1] == '\n' 48 } 49 50 return b.Bytes() 51 } 52 53 // JoinString joins the given yaml parts into a single multipart document. 54 func JoinString(parts ...string) string { 55 var st strings.Builder 56 57 var lastIsNewLine bool 58 for _, p := range parts { 59 if len(p) == 0 { 60 continue 61 } 62 if st.Len() != 0 { 63 if !lastIsNewLine { 64 _, _ = st.WriteString("\n") 65 } 66 st.WriteString(yamlSeparator) 67 } 68 _, _ = st.WriteString(p) 69 lastIsNewLine = p[len(p)-1] == '\n' 70 } 71 72 return st.String() 73 } 74 75 type Reader interface { 76 Read() ([]byte, error) 77 } 78 79 // YAMLReader adapts from Kubernetes YAMLReader(apimachinery.k8s.io/pkg/util/yaml/decoder.go). 80 // It records the start line number of the chunk it reads each time. 81 type YAMLReader struct { 82 reader Reader 83 currLine int 84 } 85 86 func NewYAMLReader(r *bufio.Reader) *YAMLReader { 87 return &YAMLReader{ 88 reader: &LineReader{reader: r}, 89 currLine: 0, 90 } 91 } 92 93 // Read returns a full YAML document and its first line number. 94 func (r *YAMLReader) Read() ([]byte, int, error) { 95 var buffer bytes.Buffer 96 startLine := r.currLine + 1 97 foundStart := false 98 for { 99 r.currLine++ 100 line, err := r.reader.Read() 101 if err != nil && err != io.EOF { 102 return nil, startLine, err 103 } 104 105 // detect beginning of the chunk 106 if !bytes.Equal(line, []byte("\n")) && !bytes.Equal(line, []byte(yamlSeparator)) && !foundStart { 107 startLine = r.currLine 108 foundStart = true 109 } 110 111 sep := len([]byte(separator)) 112 if i := bytes.Index(line, []byte(separator)); i == 0 { 113 // We have a potential document terminator 114 i += sep 115 after := line[i:] 116 if len(strings.TrimRightFunc(string(after), unicode.IsSpace)) == 0 { 117 if buffer.Len() != 0 { 118 return buffer.Bytes(), startLine, nil 119 } 120 if err == io.EOF { 121 return nil, startLine, err 122 } 123 } 124 } 125 if err == io.EOF { 126 if buffer.Len() != 0 { 127 // If we're at EOF, we have a final, non-terminated line. Return it. 128 return buffer.Bytes(), startLine, nil 129 } 130 return nil, startLine, err 131 } 132 buffer.Write(line) 133 } 134 } 135 136 type LineReader struct { 137 reader *bufio.Reader 138 } 139 140 // Read returns a single line (with '\n' ended) from the underlying reader. 141 // An error is returned iff there is an error with the underlying reader. 142 func (r *LineReader) Read() ([]byte, error) { 143 var ( 144 isPrefix = true 145 err error 146 line []byte 147 buffer bytes.Buffer 148 ) 149 150 for isPrefix && err == nil { 151 line, isPrefix, err = r.reader.ReadLine() 152 buffer.Write(line) 153 } 154 buffer.WriteByte('\n') 155 return buffer.Bytes(), err 156 }