-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Retain field order after running any arbitrary functions on resources (…
…#4021) * Reorder resource fields * Fix comment conflict * Update e2e test ordering * Suggested changes
- Loading branch information
1 parent
d13eef7
commit e1804cb
Showing
7 changed files
with
624 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
// Copyright 2021 The Kubernetes Authors. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package order | ||
|
||
import ( | ||
"sigs.k8s.io/kustomize/kyaml/errors" | ||
"sigs.k8s.io/kustomize/kyaml/yaml" | ||
) | ||
|
||
// SyncOrder recursively sorts the map node keys in 'to' node to match the order of | ||
// map node keys in 'from' node at same tree depth, additional keys are moved to the end | ||
// Field order might be altered due to round-tripping in arbitrary functions. | ||
// This functionality helps to retain the original order of fields to avoid unnecessary diffs. | ||
func SyncOrder(from, to *yaml.RNode) error { | ||
if err := syncOrder(from, to); err != nil { | ||
return errors.Errorf("failed to sync field order: %q", err.Error()) | ||
} | ||
rearrangeHeadCommentOfSeqNode(to.YNode()) | ||
return nil | ||
} | ||
|
||
func syncOrder(from, to *yaml.RNode) error { | ||
if from.IsNilOrEmpty() || to.IsNilOrEmpty() { | ||
return nil | ||
} | ||
switch from.YNode().Kind { | ||
case yaml.DocumentNode: | ||
// Traverse the child of the documents | ||
return syncOrder(yaml.NewRNode(from.YNode()), yaml.NewRNode(to.YNode())) | ||
case yaml.MappingNode: | ||
return VisitFields(from, to, func(fNode, tNode *yaml.MapNode) error { | ||
// Traverse each field value | ||
if fNode == nil || tNode == nil { | ||
return nil | ||
} | ||
return syncOrder(fNode.Value, tNode.Value) | ||
}) | ||
case yaml.SequenceNode: | ||
return VisitElements(from, to, func(fNode, tNode *yaml.RNode) error { | ||
// Traverse each list element | ||
return syncOrder(fNode, tNode) | ||
}) | ||
} | ||
return nil | ||
} | ||
|
||
// VisitElements calls fn for each element in a SequenceNode. | ||
// Returns an error for non-SequenceNodes | ||
func VisitElements(from, to *yaml.RNode, fn func(fNode, tNode *yaml.RNode) error) error { | ||
fElements, err := from.Elements() | ||
if err != nil { | ||
return errors.Wrap(err) | ||
} | ||
|
||
tElements, err := to.Elements() | ||
if err != nil { | ||
return errors.Wrap(err) | ||
} | ||
for i := range fElements { | ||
if i >= len(tElements) { | ||
return nil | ||
} | ||
if err := fn(fElements[i], tElements[i]); err != nil { | ||
return errors.Wrap(err) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// VisitFields calls fn for each field in the RNode. | ||
// Returns an error for non-MappingNodes. | ||
func VisitFields(from, to *yaml.RNode, fn func(fNode, tNode *yaml.MapNode) error) error { | ||
srcFieldNames, err := from.Fields() | ||
if err != nil { | ||
return nil | ||
} | ||
yaml.SyncMapNodesOrder(from, to) | ||
// visit each field | ||
for _, fieldName := range srcFieldNames { | ||
if err := fn(from.Field(fieldName), to.Field(fieldName)); err != nil { | ||
return errors.Wrap(err) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// rearrangeHeadCommentOfSeqNode addresses a remote corner case due to moving a | ||
// map node in a sequence node with a head comment to the top | ||
func rearrangeHeadCommentOfSeqNode(node *yaml.Node) { | ||
if node == nil { | ||
return | ||
} | ||
switch node.Kind { | ||
case yaml.DocumentNode: | ||
for _, node := range node.Content { | ||
rearrangeHeadCommentOfSeqNode(node) | ||
} | ||
|
||
case yaml.MappingNode: | ||
for _, node := range node.Content { | ||
rearrangeHeadCommentOfSeqNode(node) | ||
} | ||
|
||
case yaml.SequenceNode: | ||
for _, node := range node.Content { | ||
// for each child mapping node, transfer the head comment of it's | ||
// first child scalar node to the head comment of itself | ||
if len(node.Content) > 0 && node.Content[0].Kind == yaml.ScalarNode { | ||
if node.HeadComment == "" { | ||
node.HeadComment = node.Content[0].HeadComment | ||
continue | ||
} | ||
|
||
if node.Content[0].HeadComment != "" { | ||
node.HeadComment += "\n" + node.Content[0].HeadComment | ||
node.Content[0].HeadComment = "" | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.