mirror of https://github.com/helm/helm
reload CRDs after installing CRDs from 'templates/' folder Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com>pull/11022/head
parent
8199db309a
commit
5e0a4dc031
@ -0,0 +1,164 @@
|
||||
/*
|
||||
Copyright The Helm 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 kube
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
type filteredReader struct {
|
||||
reader io.ReadCloser
|
||||
|
||||
// a buffer allocation used by the reader
|
||||
rawBuffer []byte
|
||||
// maxBytes is the max size of the rawBuffer
|
||||
maxBytes int
|
||||
|
||||
nread int
|
||||
nready int
|
||||
ndone int
|
||||
|
||||
recovering bool
|
||||
eof bool
|
||||
|
||||
// shouldInclude decides whether to skip an input resource or not
|
||||
shouldInclude func(error, *metav1.PartialObjectMetadata) bool
|
||||
}
|
||||
|
||||
var ErrObjectTooLarge = fmt.Errorf("object to decode was longer than maximum allowed size")
|
||||
|
||||
const yamlSeparator = "\n---"
|
||||
|
||||
func filterManifest(
|
||||
reader io.ReadCloser,
|
||||
shouldInclude func(error, *metav1.PartialObjectMetadata) bool,
|
||||
initialBufferSize int,
|
||||
maxBufferSize int,
|
||||
) io.ReadCloser {
|
||||
return &filteredReader{
|
||||
reader: reader,
|
||||
|
||||
rawBuffer: make([]byte, initialBufferSize),
|
||||
maxBytes: maxBufferSize,
|
||||
|
||||
nread: 0,
|
||||
nready: 0,
|
||||
ndone: 0,
|
||||
|
||||
recovering: false,
|
||||
eof: false,
|
||||
|
||||
shouldInclude: shouldInclude,
|
||||
}
|
||||
}
|
||||
|
||||
func FilterManifest(reader io.ReadCloser, shouldInclude func(error, *metav1.PartialObjectMetadata) bool) io.ReadCloser {
|
||||
return filterManifest(reader, shouldInclude, 1024, 16*1024*1024)
|
||||
}
|
||||
|
||||
// Decode reads the next object from the stream and decodes it.
|
||||
func (d *filteredReader) Read(buf []byte) (written int, err error) {
|
||||
// write as much of the remaining readBuffer as possible
|
||||
if d.nready > d.ndone {
|
||||
n := copy(buf, d.rawBuffer[d.ndone:d.nready])
|
||||
d.ndone += n
|
||||
written += n
|
||||
}
|
||||
|
||||
// we can't read anything and the buffer is empty
|
||||
if d.eof && d.nread == 0 {
|
||||
return written, io.EOF
|
||||
}
|
||||
|
||||
// we can return if the buffer is filled already
|
||||
if len(buf) == written {
|
||||
return written, nil
|
||||
}
|
||||
|
||||
if d.ndone != d.nready {
|
||||
panic("unexpected")
|
||||
}
|
||||
|
||||
d.nread = copy(d.rawBuffer, d.rawBuffer[d.nready:d.nread])
|
||||
d.ndone, d.nready = 0, 0
|
||||
|
||||
sepLen := len([]byte(yamlSeparator))
|
||||
for {
|
||||
// go back in buffer to check for '\n---' sequence
|
||||
d.nready -= sepLen
|
||||
if d.nready < 0 {
|
||||
d.nready = 0
|
||||
}
|
||||
|
||||
// check if new bytes contain '\n---'
|
||||
if i := bytes.Index(d.rawBuffer[d.nready:d.nread], []byte(yamlSeparator)); i >= 0 {
|
||||
d.nready = d.nready + i + sepLen
|
||||
break
|
||||
} else {
|
||||
d.nready = d.nread
|
||||
}
|
||||
|
||||
if d.nread >= len(d.rawBuffer) {
|
||||
if cap(d.rawBuffer) > d.maxBytes {
|
||||
d.recovering = true // TODO
|
||||
d.nready, d.nread = 0, 0
|
||||
return written, ErrObjectTooLarge
|
||||
}
|
||||
d.rawBuffer = append(d.rawBuffer, 0)
|
||||
d.rawBuffer = d.rawBuffer[:cap(d.rawBuffer)]
|
||||
}
|
||||
|
||||
if d.eof {
|
||||
d.nready = d.nread
|
||||
break
|
||||
}
|
||||
|
||||
n, err := d.reader.Read(d.rawBuffer[d.nread:])
|
||||
d.nread += n
|
||||
if err == io.EOF {
|
||||
d.eof = true
|
||||
} else if err != nil {
|
||||
return written, err
|
||||
}
|
||||
}
|
||||
|
||||
var metadata metav1.PartialObjectMetadata
|
||||
|
||||
test := d.rawBuffer[:d.nready]
|
||||
|
||||
err = yaml.Unmarshal(test, &metadata)
|
||||
if !d.shouldInclude(err, &metadata) {
|
||||
// skip reading the current [0:d.nready] range
|
||||
d.nread = copy(d.rawBuffer, d.rawBuffer[d.nready:d.nread])
|
||||
for i := d.nread; i < cap(d.rawBuffer); i++ {
|
||||
d.rawBuffer[i] = 0
|
||||
}
|
||||
d.nready = 0
|
||||
}
|
||||
|
||||
n, err := d.Read(buf[written:])
|
||||
return written + n, err
|
||||
}
|
||||
|
||||
func (d *filteredReader) Close() error {
|
||||
return d.reader.Close()
|
||||
}
|
@ -0,0 +1,221 @@
|
||||
/*
|
||||
Copyright The Helm 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 kube
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
"testing/iotest"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type testCase struct {
|
||||
name string
|
||||
input string
|
||||
output string
|
||||
}
|
||||
|
||||
var testCases = []testCase{
|
||||
{
|
||||
name: "testcase 1",
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
--- # aaaa
|
||||
# aaaaa
|
||||
apiVersion: test/v1
|
||||
kind: Test
|
||||
field: value
|
||||
---
|
||||
apiVersion: test/v9
|
||||
kind: Test
|
||||
---`,
|
||||
output: ` # aaaa
|
||||
# aaaaa
|
||||
apiVersion: test/v1
|
||||
kind: Test
|
||||
field: value
|
||||
---`,
|
||||
},
|
||||
{
|
||||
name: "testcase 2",
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
--- # aaaa
|
||||
# aaaaa
|
||||
apiVersion: test/v1
|
||||
kind: Test
|
||||
field: value
|
||||
---
|
||||
apiVersion: test/v9
|
||||
kind: Test
|
||||
---
|
||||
# llllll
|
||||
aaa: whut
|
||||
|
||||
`,
|
||||
output: ` # aaaa
|
||||
# aaaaa
|
||||
apiVersion: test/v1
|
||||
kind: Test
|
||||
field: value
|
||||
---`,
|
||||
},
|
||||
{
|
||||
name: "testcase 3",
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
--- # aaaa
|
||||
# aaaaa
|
||||
apiVersion: test/v1
|
||||
kind: Test
|
||||
field: value
|
||||
---
|
||||
apiVersion: test/v9
|
||||
kind: Test
|
||||
---
|
||||
# llllll
|
||||
aaa: aaaaaaaaaaa
|
||||
|
||||
`,
|
||||
output: ` # aaaa
|
||||
# aaaaa
|
||||
apiVersion: test/v1
|
||||
kind: Test
|
||||
field: value
|
||||
---`,
|
||||
},
|
||||
{
|
||||
name: "testcase 4",
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
--- # aaaa
|
||||
# aaaaa
|
||||
apiVersion: test/v1
|
||||
kind: Test
|
||||
field: value
|
||||
---
|
||||
apiVersion: test/v9
|
||||
kind: Test
|
||||
---`,
|
||||
output: ` # aaaa
|
||||
# aaaaa
|
||||
apiVersion: test/v1
|
||||
kind: Test
|
||||
field: value
|
||||
---`,
|
||||
},
|
||||
{
|
||||
name: "testcase 5",
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
--- # aaaa
|
||||
# aaaaa
|
||||
apiVersion: test/v1
|
||||
kind: Test
|
||||
field: value---
|
||||
---
|
||||
apiVersion: test/v9
|
||||
kind: Test
|
||||
---
|
||||
apiVersion: test/v1
|
||||
kind: Test
|
||||
---
|
||||
apiVersion: test/v1
|
||||
kind: Test
|
||||
---
|
||||
---
|
||||
--
|
||||
-----
|
||||
---
|
||||
apiVersion: test/v1
|
||||
kind: Test
|
||||
---`,
|
||||
output: ` # aaaa
|
||||
# aaaaa
|
||||
apiVersion: test/v1
|
||||
kind: Test
|
||||
field: value---
|
||||
---
|
||||
apiVersion: test/v1
|
||||
kind: Test
|
||||
---
|
||||
apiVersion: test/v1
|
||||
kind: Test
|
||||
---
|
||||
apiVersion: test/v1
|
||||
kind: Test
|
||||
---`,
|
||||
},
|
||||
{
|
||||
name: "testcase 6",
|
||||
input: `apiVersion: test/v1
|
||||
kind: Test`,
|
||||
output: `apiVersion: test/v1
|
||||
kind: Test`,
|
||||
},
|
||||
}
|
||||
|
||||
func testTestCase(t *testing.T, initialBufferSize int, input io.Reader, output []byte) {
|
||||
t.Helper()
|
||||
|
||||
reader := filterManifest(io.NopCloser(input), func(err error, o *metav1.PartialObjectMetadata) bool {
|
||||
return (err == nil) && (o.GroupVersionKind().Kind == "Test" &&
|
||||
o.GroupVersionKind().Version == "v1" &&
|
||||
o.GroupVersionKind().Group == "test")
|
||||
}, initialBufferSize, 9999999)
|
||||
|
||||
err := iotest.TestReader(reader, output)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAll(t *testing.T) {
|
||||
for _, test := range testCases {
|
||||
t.Log(test.name)
|
||||
testTestCase(t, 10, bytes.NewBufferString(test.input), []byte(test.output))
|
||||
testTestCase(t, 10, iotest.DataErrReader(bytes.NewBufferString(test.input)), []byte(test.output))
|
||||
testTestCase(t, 10, iotest.HalfReader(bytes.NewBufferString(test.input)), []byte(test.output))
|
||||
testTestCase(t, 10, iotest.OneByteReader(bytes.NewBufferString(test.input)), []byte(test.output))
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzBufferSize(f *testing.F) {
|
||||
f.Add(0)
|
||||
f.Add(1)
|
||||
f.Add(2)
|
||||
f.Add(9)
|
||||
f.Add(10000)
|
||||
f.Fuzz(func(t *testing.T, initialBuffSize int) {
|
||||
if initialBuffSize < 50*1024*1024 && initialBuffSize >= 0 {
|
||||
for _, test := range testCases {
|
||||
t.Log(test.name)
|
||||
testTestCase(t, initialBuffSize, bytes.NewBufferString(test.input), []byte(test.output))
|
||||
testTestCase(t, initialBuffSize, iotest.DataErrReader(bytes.NewBufferString(test.input)), []byte(test.output))
|
||||
testTestCase(t, initialBuffSize, iotest.HalfReader(bytes.NewBufferString(test.input)), []byte(test.output))
|
||||
testTestCase(t, initialBuffSize, iotest.OneByteReader(bytes.NewBufferString(test.input)), []byte(test.output))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
Loading…
Reference in new issue