diff --git a/api/resource/factory.go b/api/resource/factory.go index fb01bc8cee..a452293382 100644 --- a/api/resource/factory.go +++ b/api/resource/factory.go @@ -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 { diff --git a/api/resource/factory_test.go b/api/resource/factory_test.go index 905b862028..f03cba8f46 100644 --- a/api/resource/factory_test.go +++ b/api/resource/factory_test.go @@ -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 `}, }, }, diff --git a/kyaml/kio/byteio_reader.go b/kyaml/kio/byteio_reader.go index cddc5f2200..b6c5ca43b6 100644 --- a/kyaml/kio/byteio_reader.go +++ b/kyaml/kio/byteio_reader.go @@ -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 } diff --git a/kyaml/kio/byteio_reader_test.go b/kyaml/kio/byteio_reader_test.go index 1ae73b545f..e3731e5df7 100644 --- a/kyaml/kio/byteio_reader_test.go +++ b/kyaml/kio/byteio_reader_test.go @@ -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) + // 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