Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do YAML anchor expansion shortly after reading YAML. #4187

Merged
merged 1 commit into from Sep 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 12 additions & 4 deletions api/resource/factory.go
Expand Up @@ -143,7 +143,7 @@ func (rf *Factory) resourcesFromRNodes(
return
}

func (rf *Factory) RNodesFromBytes(b []byte) (result []*yaml.RNode, err error) {
func (rf *Factory) RNodesFromBytes(b []byte) ([]*yaml.RNode, error) {
nodes, err := kio.FromBytes(b)
KnVerey marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, err
Expand All @@ -152,9 +152,17 @@ func (rf *Factory) RNodesFromBytes(b []byte) (result []*yaml.RNode, err error) {
if err != nil {
return nil, err
}
return rf.inlineAnyEmbeddedLists(nodes)
}

// inlineAnyEmbeddedLists scans the RNode slice for nodes named FooList.
// Such nodes are expected to be lists of resources, each of type Foo.
// These lists are replaced in the result by their inlined resources.
func (rf *Factory) inlineAnyEmbeddedLists(
nodes []*yaml.RNode) (result []*yaml.RNode, err error) {
var n0 *yaml.RNode
for len(nodes) > 0 {
n0 := nodes[0]
nodes = nodes[1:]
n0, nodes = nodes[0], nodes[1:]
kind := n0.GetKind()
if !strings.HasSuffix(kind, "List") {
result = append(result, n0)
Expand All @@ -164,7 +172,7 @@ func (rf *Factory) RNodesFromBytes(b []byte) (result []*yaml.RNode, err error) {
var m map[string]interface{}
m, err = n0.Map()
if err != nil {
return nil, err
return nil, fmt.Errorf("trouble expanding list of %s; %w", kind, err)
}
items, ok := m["items"]
if !ok {
Expand Down
7 changes: 2 additions & 5 deletions api/resource/factory_test.go
Expand Up @@ -639,17 +639,14 @@ data:
feeling: *color-used
`),
exp: expected{
// TODO(#3675) : the anchor should be replaced.
// Anchors are replaced in the List above due to a different code path
// (when the list is inlined).
out: []string{`
apiVersion: v1
kind: ConfigMap
metadata:
name: wildcard
data:
color: &color-used blue
feeling: *color-used
color: blue
feeling: blue
`},
},
},
Expand Down
12 changes: 12 additions & 0 deletions kyaml/kio/byteio_reader.go
Expand Up @@ -102,6 +102,7 @@ func ParseAll(inputs ...string) ([]*yaml.RNode, error) {
func FromBytes(bs []byte) ([]*yaml.RNode, error) {
return (&ByteReader{
OmitReaderAnnotations: true,
AnchorsAweigh: true,
Reader: bytes.NewBuffer(bs),
}).Read()
}
Expand Down Expand Up @@ -152,6 +153,10 @@ type ByteReader struct {
// sequence nodes into map node with key yaml.BareSeqNodeWrappingKey
// note that this wrapping is different and not related to ResourceList wrapping
WrapBareSeqNode bool

// AnchorsAweigh set to true attempts to replace all YAML anchor aliases
// with their definitions (anchor values) immediately after the read.
AnchorsAweigh bool
}

var _ Reader = &ByteReader{}
Expand Down Expand Up @@ -269,6 +274,13 @@ func (r *ByteReader) Read() ([]*yaml.RNode, error) {
// increment the index annotation value
index++
}
if r.AnchorsAweigh {
for _, n := range output {
if err = n.DeAnchor(); err != nil {
return nil, err
}
}
}
return output, nil
}

Expand Down
108 changes: 85 additions & 23 deletions kyaml/kio/byteio_reader_test.go
Expand Up @@ -758,7 +758,7 @@ items:
metadata:
name: deployment-b
spec:
<<: *hostAliases
*hostAliases
`),
exp: expected{
sOut: []string{`
Expand All @@ -769,7 +769,7 @@ items:
kind: Deployment
metadata:
name: deployment-a
spec: &hostAliases
spec:
template:
spec:
hostAliases:
Expand All @@ -781,7 +781,12 @@ items:
metadata:
name: deployment-b
spec:
!!merge <<: *hostAliases
template:
spec:
hostAliases:
- hostnames:
- a.example.com
ip: 8.8.8.8
`},
},
},
Expand All @@ -808,27 +813,21 @@ items:
}
}

// This test shows the lower level (go-yaml) representation of a small doc
// with an anchor. The anchor structure is there, in the sense that an
// alias pointer is readily available when a node's kind is an AliasNode.
// I.e. the anchor mapping name -> object was noted during unmarshalling.
// However, at the time of writing github.com/go-yaml/yaml/encoder.go
// doesn't appear to have an option to perform anchor replacements when
// encoding. It emits anchor definitions and references (aliases) intact.
func TestByteReader_AnchorBehavior(t *testing.T) {
// Show the low level (go-yaml) representation of a small doc with a
// YAML anchor and alias after reading it with anchor expansion on or off.
func TestByteReader_AnchorsAweigh(t *testing.T) {
const input = `
data:
color: &color-used blue
feeling: *color-used
`
expected := strings.TrimSpace(`
data:
color: &color-used blue
feeling: *color-used
`)
var rNode *yaml.RNode
{
rNodes, err := FromBytes([]byte(input))
rNodes, err := (&ByteReader{
OmitReaderAnnotations: true,
AnchorsAweigh: false,
Reader: bytes.NewBuffer([]byte(input)),
}).Read()
assert.NoError(t, err)
assert.Equal(t, 1, len(rNodes))
rNode = rNodes[0]
Expand Down Expand Up @@ -857,7 +856,7 @@ data:
assert.Equal(t, yaml.ScalarNode, yNodes[0].Kind)
assert.Equal(t, yaml.NodeTagString, yNodes[0].Tag)
assert.Equal(t, "color", yNodes[0].Value)
assert.Equal(t, "", yNodes[0].Anchor)
assert.Empty(t, yNodes[0].Anchor)
assert.Nil(t, yNodes[0].Alias)

assert.Equal(t, yaml.ScalarNode, yNodes[1].Kind)
Expand All @@ -869,19 +868,82 @@ data:
assert.Equal(t, yaml.ScalarNode, yNodes[2].Kind)
assert.Equal(t, yaml.NodeTagString, yNodes[2].Tag)
assert.Equal(t, "feeling", yNodes[2].Value)
assert.Equal(t, "", yNodes[2].Anchor)
assert.Empty(t, yNodes[2].Anchor)
assert.Nil(t, yNodes[2].Alias)

assert.Equal(t, yaml.AliasNode, yNodes[3].Kind)
assert.Equal(t, "", yNodes[3].Tag)
assert.Empty(t, yNodes[3].Tag)
assert.Equal(t, "color-used", yNodes[3].Value)
assert.Equal(t, "", yNodes[3].Anchor)
assert.Empty(t, yNodes[3].Anchor)
assert.NotNil(t, yNodes[3].Alias)
}

yaml, err := rNode.String()
str, err := rNode.String()
assert.NoError(t, err)
// The string version matches the input (it still has anchors and aliases).
assert.Equal(t, strings.TrimSpace(input), strings.TrimSpace(str))

// Now do same thing again, but this time set AnchorsAweigh = true.
{
rNodes, err := (&ByteReader{
OmitReaderAnnotations: true,
AnchorsAweigh: true,
Reader: bytes.NewBuffer([]byte(input)),
}).Read()
assert.NoError(t, err)
assert.Equal(t, 1, len(rNodes))
rNode = rNodes[0]
}
// Again make assertions on the internals.
{
yNode := rNode.YNode()

assert.Equal(t, yaml.NodeTagMap, yNode.Tag)

yNodes := yNode.Content
assert.Equal(t, 2, len(yNodes))

assert.Equal(t, yaml.NodeTagString, yNodes[0].Tag)
assert.Equal(t, "data", yNodes[0].Value)

assert.Equal(t, yaml.NodeTagMap, yNodes[1].Tag)

yNodes = yNodes[1].Content
assert.Equal(t, 4, len(yNodes))

assert.Equal(t, yaml.ScalarNode, yNodes[0].Kind)
assert.Equal(t, yaml.NodeTagString, yNodes[0].Tag)
assert.Equal(t, "color", yNodes[0].Value)
assert.Empty(t, yNodes[0].Anchor)
assert.Nil(t, yNodes[0].Alias)

assert.Equal(t, yaml.ScalarNode, yNodes[1].Kind)
assert.Equal(t, yaml.NodeTagString, yNodes[1].Tag)
assert.Equal(t, "blue", yNodes[1].Value)
assert.Empty(t, yNodes[1].Anchor)
assert.Nil(t, yNodes[1].Alias)

assert.Equal(t, yaml.ScalarNode, yNodes[2].Kind)
assert.Equal(t, yaml.NodeTagString, yNodes[2].Tag)
assert.Equal(t, "feeling", yNodes[2].Value)
assert.Empty(t, yNodes[2].Anchor)
assert.Nil(t, yNodes[2].Alias)

assert.Equal(t, yaml.ScalarNode, yNodes[3].Kind)
assert.Equal(t, yaml.NodeTagString, yNodes[3].Tag)
assert.Equal(t, "blue", yNodes[3].Value)
assert.Empty(t, yNodes[3].Anchor)
assert.Nil(t, yNodes[3].Alias)
}

str, err = rNode.String()
assert.NoError(t, err)
assert.Equal(t, expected, strings.TrimSpace(yaml))
// This time, the alias has been replaced with the anchor definition.
assert.Equal(t, strings.TrimSpace(`
data:
color: blue
feeling: blue
`), strings.TrimSpace(str))
}

// TestByteReader_AddSeqIndentAnnotation tests if the internal.config.kubernetes.io/seqindent
Expand Down