mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-11 17:12:51 +00:00
Fix error during expansion of !!merge <<: anchor tags (#4383)
* WIP * Fix merge corner cases * Add test for explicit !!merge tag * Fix tests * Cleanup * Cleanup * Fix deanchoring lists * Add test case for keeping comments * Add MapEntrySetter and fix json marshalling after deanchoring * Keep duplicated keys * Move MergeTag definition to yaml alias * Remove go-spew from api * Add support for sequence nodes on merge tags * Add docstring to MapEntrySetter.Key * Add docstring to MapEntrySetter struct * Add tests to MapEntrySetter * Fix duplicate merge key * Revert whitespace changes on forked go-yaml * Remove AssocMapEntry function * Refactoring merge order * Return errors on VisitFields and PipeE * Add tests for each non-conforming map merges
This commit is contained in:
@@ -932,7 +932,17 @@ func deAnchor(yn *yaml.Node) (res *yaml.Node, err error) {
|
||||
return yn, nil
|
||||
case yaml.AliasNode:
|
||||
return deAnchor(yn.Alias)
|
||||
case yaml.DocumentNode, yaml.MappingNode, yaml.SequenceNode:
|
||||
case yaml.MappingNode:
|
||||
toMerge, err := removeMergeTags(yn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = mergeAll(yn, toMerge)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fallthrough
|
||||
case yaml.DocumentNode, yaml.SequenceNode:
|
||||
for i := range yn.Content {
|
||||
yn.Content[i], err = deAnchor(yn.Content[i])
|
||||
if err != nil {
|
||||
@@ -945,6 +955,103 @@ func deAnchor(yn *yaml.Node) (res *yaml.Node, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// isMerge returns if the node is tagged with !!merge
|
||||
func isMerge(yn *yaml.Node) bool {
|
||||
return yn.Tag == MergeTag
|
||||
}
|
||||
|
||||
// findMergeValues receives either a MappingNode, a AliasNode or a potentially
|
||||
// mixed list of MappingNodes and AliasNodes. It returns a list of MappingNodes.
|
||||
func findMergeValues(yn *yaml.Node) ([]*yaml.Node, error) {
|
||||
if yn == nil {
|
||||
return []*yaml.Node{}, nil
|
||||
}
|
||||
switch yn.Kind {
|
||||
case MappingNode:
|
||||
return []*yaml.Node{yn}, nil
|
||||
case AliasNode:
|
||||
if yn.Alias != nil && yn.Alias.Kind != MappingNode {
|
||||
return nil, errors.Errorf("invalid map merge: received alias for a non-map value")
|
||||
}
|
||||
return []*yaml.Node{yn.Alias}, nil
|
||||
case SequenceNode:
|
||||
mergeValues := []*yaml.Node{}
|
||||
for i := 0; i < len(yn.Content); i++ {
|
||||
if yn.Content[i].Kind == SequenceNode {
|
||||
return nil, errors.Errorf("invalid map merge: received a nested sequence")
|
||||
}
|
||||
newMergeValues, err := findMergeValues(yn.Content[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mergeValues = append(newMergeValues, mergeValues...)
|
||||
}
|
||||
return mergeValues, nil
|
||||
default:
|
||||
return nil, errors.Errorf("map merge requires map or sequence of maps as the value")
|
||||
}
|
||||
}
|
||||
|
||||
// getMergeTagValue receives a MappingNode yaml node, and it searches for
|
||||
// merge tagged keys and return its value yaml node. If the key is duplicated,
|
||||
// it fails.
|
||||
func getMergeTagValue(yn *yaml.Node) (*yaml.Node, error) {
|
||||
var result *yaml.Node
|
||||
for i := 0; i < len(yn.Content); i += 2 {
|
||||
key := yn.Content[i]
|
||||
value := yn.Content[i+1]
|
||||
if isMerge(key) {
|
||||
if result != nil {
|
||||
return nil, fmt.Errorf("duplicate merge key")
|
||||
}
|
||||
result = value
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// removeMergeTags removes all merge tags and returns a ordered list of yaml
|
||||
// nodes to merge and a error
|
||||
func removeMergeTags(yn *yaml.Node) ([]*yaml.Node, error) {
|
||||
if yn == nil || yn.Content == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if yn.Kind != yaml.MappingNode {
|
||||
return nil, nil
|
||||
}
|
||||
value, err := getMergeTagValue(yn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
toMerge, err := findMergeValues(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = NewRNode(yn).PipeE(Clear("<<"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toMerge, nil
|
||||
}
|
||||
|
||||
func mergeAll(yn *yaml.Node, toMerge []*yaml.Node) error {
|
||||
// We only need to start with a copy of the existing node because we need to
|
||||
// maintain duplicated keys and style
|
||||
rn := NewRNode(yn).Copy()
|
||||
toMerge = append(toMerge, yn)
|
||||
for i := range toMerge {
|
||||
rnToMerge := NewRNode(toMerge[i]).Copy()
|
||||
err := rnToMerge.VisitFields(func(node *MapNode) error {
|
||||
return rn.PipeE(MapEntrySetter{Key: node.Key, Value: node.Value})
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*yn = *rn.value
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetValidatedMetadata returns metadata after subjecting it to some tests.
|
||||
func (rn *RNode) GetValidatedMetadata() (ResourceMeta, error) {
|
||||
m, err := rn.GetMeta()
|
||||
|
||||
Reference in New Issue
Block a user