Skip to content

Commit 14ccbf5

Browse files
authored
Fix crash when parsing large configuration files (#969)
Signed-off-by: Kimmo Lehto <[email protected]>
1 parent 2517163 commit 14ccbf5

File tree

2 files changed

+43
-31
lines changed

2 files changed

+43
-31
lines changed

pkg/manifest/reader.go

Lines changed: 12 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package manifest
33
import (
44
"bufio"
55
"bytes"
6+
"errors"
67
"fmt"
78
"io"
89
"os"
@@ -12,6 +13,7 @@ import (
1213
"time"
1314

1415
"gopkg.in/yaml.v2"
16+
yamlutil "k8s.io/apimachinery/pkg/util/yaml"
1517
)
1618

1719
// ResourceDefinition represents a single Kubernetes resource definition.
@@ -64,27 +66,6 @@ func (rd *ResourceDefinition) Unmarshal(obj any) error {
6466
return nil
6567
}
6668

67-
func yamlDocumentSplit(data []byte, atEOF bool) (advance int, token []byte, err error) {
68-
if atEOF && len(data) == 0 {
69-
return 0, nil, nil
70-
}
71-
72-
// Look for the document separator
73-
sepIndex := bytes.Index(data, []byte("\n---"))
74-
if sepIndex >= 0 {
75-
// Return everything up to the separator
76-
return sepIndex + len("\n---"), data[:sepIndex], nil
77-
}
78-
79-
// If at EOF, return the remaining data
80-
if atEOF {
81-
return len(data), data, nil
82-
}
83-
84-
// Request more data
85-
return 0, nil, nil
86-
}
87-
8869
// Reader reads Kubernetes resource definitions from input streams.
8970
type Reader struct {
9071
IgnoreErrors bool
@@ -100,14 +81,18 @@ func name(r io.Reader) string {
10081

10182
// Parse parses Kubernetes resource definitions from the provided input stream. They are then available via the Resources() or GetResources(apiVersion, kind) methods.
10283
func (r *Reader) Parse(input io.Reader) error {
103-
scanner := bufio.NewScanner(input)
104-
scanner.Split(yamlDocumentSplit)
84+
yamlReader := yamlutil.NewYAMLReader(bufio.NewReader(input))
10585

106-
for scanner.Scan() {
107-
rawChunk := scanner.Bytes()
86+
for {
87+
rawChunk, err := yamlReader.Read()
88+
if err != nil {
89+
if errors.Is(err, io.EOF) {
90+
break
91+
}
92+
return fmt.Errorf("error reading input: %w", err)
93+
}
10894

109-
// Skip empty chunks
110-
if len(rawChunk) == 0 {
95+
if len(bytes.TrimSpace(rawChunk)) == 0 {
11196
continue
11297
}
11398

@@ -131,10 +116,6 @@ func (r *Reader) Parse(input io.Reader) error {
131116
r.manifests = append(r.manifests, rd)
132117
}
133118

134-
if err := scanner.Err(); err != nil {
135-
return fmt.Errorf("error reading input: %w", err)
136-
}
137-
138119
return nil
139120
}
140121

pkg/manifest/reader_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package manifest_test
22

33
import (
4+
"fmt"
45
"strings"
56
"testing"
67

@@ -149,3 +150,33 @@ metadata:
149150
require.NoError(t, err, "GetResources should not return an error for Services")
150151
assert.Len(t, services, 1, "Expected 1 Service to be returned")
151152
}
153+
154+
func TestReader_ParseHandlesLargeManifest(t *testing.T) {
155+
largeData := strings.Repeat("x", 70*1024)
156+
manifestStr := fmt.Sprintf(`apiVersion: v1
157+
kind: ConfigMap
158+
metadata:
159+
name: big-config
160+
data:
161+
payload: "%s"
162+
`, largeData)
163+
164+
r := &manifest.Reader{}
165+
require.NoError(t, r.Parse(strings.NewReader(manifestStr)))
166+
require.Equal(t, 1, r.Len(), "Expected a single manifest to be parsed")
167+
168+
resource := r.Resources()[0]
169+
require.Equal(t, "ConfigMap", resource.Kind)
170+
require.Equal(t, "v1", resource.APIVersion)
171+
require.Equal(t, strings.TrimSpace(manifestStr), strings.TrimSpace(string(resource.Raw)))
172+
173+
var parsed struct {
174+
APIVersion string `yaml:"apiVersion"`
175+
Kind string `yaml:"kind"`
176+
Metadata map[string]string `yaml:"metadata"`
177+
Data map[string]string `yaml:"data"`
178+
}
179+
require.NoError(t, resource.Unmarshal(&parsed))
180+
require.Contains(t, parsed.Data, "payload")
181+
require.Len(t, parsed.Data["payload"], len(largeData))
182+
}

0 commit comments

Comments
 (0)