pilot/pkg/config/file/util/kubeyaml/kubeyaml.go (115 lines of code) (raw):

// Copyright Istio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package kubeyaml import ( "bufio" "bytes" "io" "strings" "unicode" ) const ( yamlSeparator = "---\n" separator = "---" ) // Join the given yaml parts into a single multipart document. func Join(parts ...[]byte) []byte { var b bytes.Buffer var lastIsNewLine bool for _, p := range parts { if len(p) == 0 { continue } if b.Len() != 0 { if !lastIsNewLine { _, _ = b.WriteString("\n") } b.WriteString(yamlSeparator) } _, _ = b.Write(p) s := string(p) lastIsNewLine = s[len(s)-1] == '\n' } return b.Bytes() } // JoinString joins the given yaml parts into a single multipart document. func JoinString(parts ...string) string { var st strings.Builder var lastIsNewLine bool for _, p := range parts { if len(p) == 0 { continue } if st.Len() != 0 { if !lastIsNewLine { _, _ = st.WriteString("\n") } st.WriteString(yamlSeparator) } _, _ = st.WriteString(p) lastIsNewLine = p[len(p)-1] == '\n' } return st.String() } type Reader interface { Read() ([]byte, error) } // YAMLReader adapts from Kubernetes YAMLReader(apimachinery.k8s.io/pkg/util/yaml/decoder.go). // It records the start line number of the chunk it reads each time. type YAMLReader struct { reader Reader currLine int } func NewYAMLReader(r *bufio.Reader) *YAMLReader { return &YAMLReader{ reader: &LineReader{reader: r}, currLine: 0, } } // Read returns a full YAML document and its first line number. func (r *YAMLReader) Read() ([]byte, int, error) { var buffer bytes.Buffer startLine := r.currLine + 1 foundStart := false for { r.currLine++ line, err := r.reader.Read() if err != nil && err != io.EOF { return nil, startLine, err } // detect beginning of the chunk if !bytes.Equal(line, []byte("\n")) && !bytes.Equal(line, []byte(yamlSeparator)) && !foundStart { startLine = r.currLine foundStart = true } sep := len([]byte(separator)) if i := bytes.Index(line, []byte(separator)); i == 0 { // We have a potential document terminator i += sep after := line[i:] if len(strings.TrimRightFunc(string(after), unicode.IsSpace)) == 0 { if buffer.Len() != 0 { return buffer.Bytes(), startLine, nil } if err == io.EOF { return nil, startLine, err } } } if err == io.EOF { if buffer.Len() != 0 { // If we're at EOF, we have a final, non-terminated line. Return it. return buffer.Bytes(), startLine, nil } return nil, startLine, err } buffer.Write(line) } } type LineReader struct { reader *bufio.Reader } // Read returns a single line (with '\n' ended) from the underlying reader. // An error is returned iff there is an error with the underlying reader. func (r *LineReader) Read() ([]byte, error) { var ( isPrefix = true err error line []byte buffer bytes.Buffer ) for isPrefix && err == nil { line, isPrefix, err = r.reader.ReadLine() buffer.Write(line) } buffer.WriteByte('\n') return buffer.Bytes(), err }