Skip to content
47 changes: 34 additions & 13 deletions kyaml/kio/byteio_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ type ByteReadWriter struct {
// the Resources, otherwise they will be cleared.
KeepReaderAnnotations bool

// PreserveInitialDocSep if true adds kioutil.InitialDocSepAnnotation to the first resource
PreserveInitialDocSep bool

// PreserveSeqIndent if true adds kioutil.SeqIndentAnnotation to each resource
PreserveSeqIndent bool

Expand All @@ -63,6 +66,7 @@ func (rw *ByteReadWriter) Read() ([]*yaml.RNode, error) {
b := &ByteReader{
Reader: rw.Reader,
OmitReaderAnnotations: rw.OmitReaderAnnotations,
PreserveInitialDocSep: rw.PreserveInitialDocSep,
PreserveSeqIndent: rw.PreserveSeqIndent,
WrapBareSeqNode: rw.WrapBareSeqNode,
}
Expand Down Expand Up @@ -160,6 +164,9 @@ type ByteReader struct {
// AnchorsAweigh set to true attempts to replace all YAML anchor aliases
// with their definitions (anchor values) immediately after the read.
AnchorsAweigh bool

// PreserveInitialDocSep if true adds the kioutil.PreserveInitialDocSep annotation to the first resource
PreserveInitialDocSep bool
}

var _ Reader = &ByteReader{}
Expand Down Expand Up @@ -196,8 +203,8 @@ func splitDocuments(s string) ([]string, error) {
}

func (r *ByteReader) Read() ([]*yaml.RNode, error) {
if r.PreserveSeqIndent && r.OmitReaderAnnotations {
return nil, errors.Errorf(`"PreserveSeqIndent" option adds a reader annotation, please set "OmitReaderAnnotations" to false`)
if (r.PreserveSeqIndent || r.PreserveInitialDocSep) && r.OmitReaderAnnotations {
return nil, errors.Errorf(`"PreserveSeqIndent" and "PreserveInitialDocSep" options add a reader annotation, please set "OmitReaderAnnotations" to false`)
}

output := ResourceNodeSlice{}
Expand Down Expand Up @@ -319,20 +326,10 @@ func (r *ByteReader) decode(originalYAML string, index int, decoder *yaml.Decode
r.SetAnnotations = map[string]string{}
}
if !r.OmitReaderAnnotations {
err := kioutil.CopyLegacyAnnotations(n)
err := r.setResourceAnnotations(n, index, originalYAML)
if err != nil {
return nil, err
}
r.SetAnnotations[kioutil.IndexAnnotation] = fmt.Sprintf("%d", index)
r.SetAnnotations[kioutil.LegacyIndexAnnotation] = fmt.Sprintf("%d", index)

if r.PreserveSeqIndent {
// derive and add the seqindent annotation
seqIndentStyle := yaml.DeriveSeqIndentStyle(originalYAML)
if seqIndentStyle != "" {
r.SetAnnotations[kioutil.SeqIndentAnnotation] = seqIndentStyle
}
}
}
var keys []string
for k := range r.SetAnnotations {
Expand All @@ -347,3 +344,27 @@ func (r *ByteReader) decode(originalYAML string, index int, decoder *yaml.Decode
}
return n, nil
}

func (r *ByteReader) setResourceAnnotations(n *yaml.RNode, index int, originalYAML string) error {
err := kioutil.CopyLegacyAnnotations(n)
if err != nil {
return fmt.Errorf("copying legacy annotations failed: %w", err)
}
r.SetAnnotations[kioutil.IndexAnnotation] = fmt.Sprintf("%d", index)
r.SetAnnotations[kioutil.LegacyIndexAnnotation] = fmt.Sprintf("%d", index)

if r.PreserveSeqIndent {
// derive and add the seqindent annotation
seqIndentStyle := yaml.DeriveSeqIndentStyle(originalYAML)
if seqIndentStyle != "" {
r.SetAnnotations[kioutil.SeqIndentAnnotation] = seqIndentStyle
}
}
if index == 0 && r.PreserveInitialDocSep &&
strings.HasPrefix(originalYAML, "---") {
r.SetAnnotations[kioutil.InitialDocSepAnnotation] = "true"
} else {
delete(r.SetAnnotations, kioutil.InitialDocSepAnnotation)
}
return nil
}
97 changes: 91 additions & 6 deletions kyaml/kio/byteio_reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
package kio_test

import (
"bytes"
"strings"
"testing"

Expand Down Expand Up @@ -443,7 +442,7 @@ c: d
tc := testCases[i]
t.Run(tc.name, func(t *testing.T) {
r := tc.instance
r.Reader = bytes.NewBufferString(tc.input)
r.Reader = strings.NewReader(tc.input)
nodes, err := r.Read()
if tc.err != "" {
if !assert.EqualError(t, err, tc.err) {
Expand Down Expand Up @@ -885,7 +884,7 @@ data:
rNodes, err := (&ByteReader{
OmitReaderAnnotations: true,
AnchorsAweigh: false,
Reader: bytes.NewBuffer([]byte(input)),
Reader: strings.NewReader(input),
}).Read()
require.NoError(t, err)
assert.Equal(t, 1, len(rNodes))
Expand Down Expand Up @@ -947,7 +946,7 @@ data:
rNodes, err := (&ByteReader{
OmitReaderAnnotations: true,
AnchorsAweigh: true,
Reader: bytes.NewBuffer([]byte(input)),
Reader: strings.NewReader(input),
}).Read()
require.NoError(t, err)
assert.Equal(t, 1, len(rNodes))
Expand Down Expand Up @@ -1080,7 +1079,7 @@ env:
- bar
`,
OmitReaderAnnotations: true,
err: `"PreserveSeqIndent" option adds a reader annotation, please set "OmitReaderAnnotations" to false`,
err: `"PreserveSeqIndent" and "PreserveInitialDocSep" options add a reader annotation, please set "OmitReaderAnnotations" to false`,
},
}

Expand All @@ -1090,7 +1089,7 @@ env:
rNodes, err := (&ByteReader{
OmitReaderAnnotations: tc.OmitReaderAnnotations,
PreserveSeqIndent: true,
Reader: bytes.NewBuffer([]byte(tc.input)),
Reader: strings.NewReader(tc.input),
}).Read()
if tc.err != "" {
require.Error(t, err)
Expand All @@ -1103,3 +1102,89 @@ env:
})
}
}

// TestByteReader_AddPreserveInitialDocSepAnnotation tests if the internal.config.kubernetes.io/initial-doc-sep
// annotation is added to resources appropriately
func TestByteReader_AddPreserveInitialDocSepAnnotation(t *testing.T) {
type testCase struct {
name string
err string
input string
expectedAnnoValue string
OmitReaderAnnotations bool
}

testCases := []testCase{
{
name: "read with initial document separator",
input: `---
apiVersion: apps/v1
kind: Deployment
spec:
- foo
- bar
- baz
`,
expectedAnnoValue: "true",
},
{
name: "read with initial document separator after comments",
input: `#a comment
---
apiVersion: apps/v1
kind: Deployment
spec:
- foo
- bar
- baz
`,
expectedAnnoValue: "",
},
{
name: "read without initial document separator",
input: `
apiVersion: apps/v1
kind: Deployment
spec:
- foo
- bar
- baz
`,
expectedAnnoValue: "",
},
{
name: "error if conflicting options",
input: `apiVersion: apps/v1
kind: Deployment
spec:
- foo
- bar
- baz
env:
- foo
- bar
`,
OmitReaderAnnotations: true,
err: `"PreserveSeqIndent" and "PreserveInitialDocSep" options add a reader annotation, please set "OmitReaderAnnotations" to false`,
},
}

for i := range testCases {
tc := testCases[i]
t.Run(tc.name, func(t *testing.T) {
rNodes, err := (&ByteReader{
OmitReaderAnnotations: tc.OmitReaderAnnotations,
PreserveInitialDocSep: true,
Reader: strings.NewReader(tc.input),
}).Read()
if tc.err != "" {
require.Error(t, err)
assert.Equal(t, tc.err, err.Error())
return
}
require.NoError(t, err)
actual := rNodes[0].GetAnnotations()[kioutil.InitialDocSepAnnotation]
assert.Equal(t, tc.expectedAnnoValue, actual)
})
}
}
84 changes: 84 additions & 0 deletions kyaml/kio/byteio_readwriter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,90 @@ env:
}
}

func TestByteReadWriter_PreserveInitialDocSep(t *testing.T) {
type testCase struct {
name string
err string
input string
expectedOutput string
instance kio.ByteReadWriter
preserveDocSep bool
}

testCases := []testCase{
{
name: "preserve initial document separator",
input: `---
apiVersion: apps/v1
kind: Deployment
spec:
- foo
- bar
`,
expectedOutput: `---
apiVersion: apps/v1
kind: Deployment
spec:
- foo
- bar
`,
preserveDocSep: true,
},
{
name: "disregard initial document separator, when annotation isn't present",
input: `---
apiVersion: apps/v1
kind: Deployment
spec:
- foo
- bar
`,
expectedOutput: `
apiVersion: apps/v1
kind: Deployment
spec:
- foo
- bar
`,
},
}

for i := range testCases {
tc := testCases[i]
t.Run(tc.name, func(t *testing.T) {
var in, out bytes.Buffer
in.WriteString(tc.input)
w := tc.instance
w.Writer = &out
w.Reader = &in
w.PreserveInitialDocSep = tc.preserveDocSep

nodes, err := w.Read()
if !assert.NoError(t, err) {
t.FailNow()
}

w.WrappingKind = ""
err = w.Write(nodes)
if !assert.NoError(t, err) {
t.FailNow()
}

if tc.err != "" {
if !assert.EqualError(t, err, tc.err) {
t.FailNow()
}
return
}

if !assert.Equal(t,
strings.TrimSpace(tc.expectedOutput), strings.TrimSpace(out.String())) {
t.FailNow()
}
})
}
}

func TestByteReadWriter_WrapBareSeqNode(t *testing.T) {
type testCase struct {
name string
Expand Down
19 changes: 19 additions & 0 deletions kyaml/kio/byteio_writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ func (w ByteWriter) Write(inputNodes []*yaml.RNode) error {
// Even though we use the this value further down we must check this before removing annotations
jsonEncodeSingleBareNode := w.shouldJSONEncodeSingleBareNode(nodes)

// Check for InitialDocSepAnnotation on a node
var retainInitialDocSep bool
for i := range nodes {
if len(nodes[i].GetAnnotations(kioutil.InitialDocSepAnnotation)) > 0 {
retainInitialDocSep = true
break
}
}
// store seqindent annotation value for each node in order to set the encoder indentation
var seqIndentsForNodes []string
for i := range nodes {
Expand All @@ -85,6 +93,11 @@ func (w ByteWriter) Write(inputNodes []*yaml.RNode) error {
if err != nil {
return errors.Wrap(err)
}

_, err = nodes[i].Pipe(yaml.ClearAnnotation(kioutil.InitialDocSepAnnotation))
if err != nil {
return errors.Wrap(err)
}
}
for _, a := range w.ClearAnnotations {
_, err := nodes[i].Pipe(yaml.ClearAnnotation(a))
Expand All @@ -108,7 +121,13 @@ func (w ByteWriter) Write(inputNodes []*yaml.RNode) error {
return errors.Wrap(encoder.Encode(nodes[0]))
}

if retainInitialDocSep {
if _, err := w.Writer.Write([]byte("---\n")); err != nil {
return errors.Wrap(err)
}
}
encoder := yaml.NewEncoder(w.Writer)

defer encoder.Close()
// don't wrap the elements
if w.WrappingKind == "" {
Expand Down
Loading