Skip to content

Commit

Permalink
Proper yaml doc splitting
Browse files Browse the repository at this point in the history
Signed-off-by: Kimmo Lehto <klehto@mirantis.com>
  • Loading branch information
kke committed Jan 14, 2025
1 parent cb3c944 commit f8fe9bc
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 15 deletions.
60 changes: 45 additions & 15 deletions pkg/manifest/reader.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package manifest

import (
"bufio"
"bytes"
"fmt"
"io"
Expand All @@ -10,7 +11,6 @@ import (
"strings"
"time"

log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
)

Expand Down Expand Up @@ -64,6 +64,27 @@ func (rd *ResourceDefinition) Unmarshal(obj any) error {
return nil
}

func yamlDocumentSplit(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}

// Look for the document separator
sepIndex := bytes.Index(data, []byte("\n---"))
if sepIndex >= 0 {
// Return everything up to the separator
return sepIndex + len("\n---"), data[:sepIndex], nil
}

// If at EOF, return the remaining data
if atEOF {
return len(data), data, nil
}

// Request more data
return 0, nil, nil
}

// Reader reads Kubernetes resource definitions from input streams.
type Reader struct {
IgnoreErrors bool
Expand All @@ -79,32 +100,41 @@ func name(r io.Reader) string {

// Parse parses Kubernetes resource definitions from the provided input stream. They are then available via the Resources() or GetResources(apiVersion, kind) methods.
func (r *Reader) Parse(input io.Reader) error {
var buf bytes.Buffer
tee := io.TeeReader(input, &buf)
decoder := yaml.NewDecoder(tee)
for {
buf.Reset()
log.Debugf("reading next resource definition from %s", name(input))
scanner := bufio.NewScanner(input)
scanner.Split(yamlDocumentSplit)

for scanner.Scan() {
rawChunk := scanner.Bytes()

// Skip empty chunks
if len(rawChunk) == 0 {
continue
}

rd := &ResourceDefinition{}
if err := decoder.Decode(rd); err != nil {
if err == io.EOF {
break
}
if err := yaml.UnmarshalStrict(rawChunk, rd); err != nil {
if r.IgnoreErrors {
continue
}
return fmt.Errorf("encountered an error while parsing %s: %w", name(input), err)
return fmt.Errorf("failed to decode resource %s: %w", name(input), err)
}

if rd.APIVersion == "" || rd.Kind == "" {
if r.IgnoreErrors {
continue
}
return fmt.Errorf("missing apiVersion or kind in %s", name(input))
return fmt.Errorf("missing apiVersion or kind in resource %s", name(input))
}
rd.Raw = buf.Bytes()
rd.Origin = name(input)

// Store the raw chunk
rd.Raw = append([]byte{}, rawChunk...)
r.manifests = append(r.manifests, rd)
}

if err := scanner.Err(); err != nil {
return fmt.Errorf("error reading input: %w", err)
}

return nil
}

Expand Down
3 changes: 3 additions & 0 deletions pkg/manifest/reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ metadata:
service := r.Resources()[1]
assert.Equal(t, "v1", service.APIVersion, "Unexpected apiVersion for Service")
assert.Equal(t, "Service", service.Kind, "Unexpected kind for Service")
require.Len(t, service.Raw, len(input2))
}

func TestReader_FilterResources(t *testing.T) {
Expand Down Expand Up @@ -107,6 +108,8 @@ metadata:
assert.Len(t, v2Pods, 1, "Expected 1 v2 Pod to be returned")
assert.Equal(t, "pod1", v1Pods[0].Metadata.Name, "Unexpected name for v1 Pod")
assert.Equal(t, "pod2", v2Pods[0].Metadata.Name, "Unexpected name for v2 Pod")
assert.NotEmpty(t, v1Pods[0].Raw, "Expected raw data to be populated")
assert.NotEmpty(t, v2Pods[0].Raw, "Expected raw data to be populated")
}

func TestReader_GetResources(t *testing.T) {
Expand Down

0 comments on commit f8fe9bc

Please sign in to comment.