Merge pull request #4187 from monopole/deanchorCall

Do YAML anchor expansion shortly after reading YAML.
This commit is contained in:
Kubernetes Prow Robot
2021-09-22 13:14:33 -07:00
committed by GitHub
4 changed files with 111 additions and 32 deletions

View File

@@ -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)
if err != nil {
return nil, err
@@ -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)
@@ -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 {

View File

@@ -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
`},
},
},

View File

@@ -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()
}
@@ -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{}
@@ -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
}

View File

@@ -758,7 +758,7 @@ items:
metadata:
name: deployment-b
spec:
<<: *hostAliases
*hostAliases
`),
exp: expected{
sOut: []string{`
@@ -769,7 +769,7 @@ items:
kind: Deployment
metadata:
name: deployment-a
spec: &hostAliases
spec:
template:
spec:
hostAliases:
@@ -781,7 +781,12 @@ items:
metadata:
name: deployment-b
spec:
!!merge <<: *hostAliases
template:
spec:
hostAliases:
- hostnames:
- a.example.com
ip: 8.8.8.8
`},
},
},
@@ -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]
@@ -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)
@@ -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)
assert.Equal(t, expected, strings.TrimSpace(yaml))
// 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)
// 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