|
|
|
|
@ -34,6 +34,7 @@ import (
|
|
|
|
|
"text/template"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"go.yaml.in/yaml/v3"
|
|
|
|
|
"k8s.io/apimachinery/pkg/api/meta"
|
|
|
|
|
"k8s.io/cli-runtime/pkg/genericclioptions"
|
|
|
|
|
"k8s.io/client-go/discovery"
|
|
|
|
|
@ -204,6 +205,13 @@ func annotateAndMerge(files map[string]string) (string, error) {
|
|
|
|
|
if strings.TrimSpace(doc) == "" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
// kio.ParseAll flattens `kind: List` into one document per item; resolve aliases
|
|
|
|
|
// first so an anchor and its alias aren't split across documents (anchors are
|
|
|
|
|
// document-scoped), which would leave the alias dangling.
|
|
|
|
|
doc, err := resolveYAMLAliases(doc)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", fmt.Errorf("resolving aliases in %s: %w", fname, err)
|
|
|
|
|
}
|
|
|
|
|
manifests, err := kio.ParseAll(doc)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", fmt.Errorf("parsing %s: %w", fname, err)
|
|
|
|
|
@ -224,6 +232,68 @@ func annotateAndMerge(files map[string]string) (string, error) {
|
|
|
|
|
return merged, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// resolveYAMLAliases materializes YAML aliases within a single document, leaving
|
|
|
|
|
// documents that contain none unchanged.
|
|
|
|
|
func resolveYAMLAliases(doc string) (string, error) {
|
|
|
|
|
var node yaml.Node
|
|
|
|
|
if err := yaml.Unmarshal([]byte(doc), &node); err != nil {
|
|
|
|
|
// Preserve the surrounding code's leniency for badly-formed docs;
|
|
|
|
|
// kio.ParseAll reports them downstream.
|
|
|
|
|
return doc, nil
|
|
|
|
|
}
|
|
|
|
|
if !resolveAliasNodes(&node) {
|
|
|
|
|
return doc, nil
|
|
|
|
|
}
|
|
|
|
|
stripAnchors(&node)
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
|
enc := yaml.NewEncoder(&buf)
|
|
|
|
|
enc.SetIndent(2)
|
|
|
|
|
if err := enc.Encode(&node); err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
if err := enc.Close(); err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
return buf.String(), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// resolveAliasNodes replaces alias nodes with a deep copy of their anchor target,
|
|
|
|
|
// returning true if any replacement was made.
|
|
|
|
|
func resolveAliasNodes(n *yaml.Node) bool {
|
|
|
|
|
changed := false
|
|
|
|
|
for i, c := range n.Content {
|
|
|
|
|
if c.Kind == yaml.AliasNode && c.Alias != nil {
|
|
|
|
|
n.Content[i] = deepCopyClearAnchor(c.Alias)
|
|
|
|
|
changed = true
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if resolveAliasNodes(c) {
|
|
|
|
|
changed = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return changed
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// deepCopyClearAnchor returns a deep copy of n with anchors removed.
|
|
|
|
|
func deepCopyClearAnchor(n *yaml.Node) *yaml.Node {
|
|
|
|
|
cp := *n
|
|
|
|
|
cp.Anchor = ""
|
|
|
|
|
cp.Alias = nil
|
|
|
|
|
cp.Content = make([]*yaml.Node, len(n.Content))
|
|
|
|
|
for i, c := range n.Content {
|
|
|
|
|
cp.Content[i] = deepCopyClearAnchor(c)
|
|
|
|
|
}
|
|
|
|
|
return &cp
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// stripAnchors clears any remaining anchor names in the tree.
|
|
|
|
|
func stripAnchors(n *yaml.Node) {
|
|
|
|
|
n.Anchor = ""
|
|
|
|
|
for _, c := range n.Content {
|
|
|
|
|
stripAnchors(c)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// splitAndDeannotate reconstructs individual files from a merged YAML stream,
|
|
|
|
|
// removing filename annotations and grouping documents by their original filenames.
|
|
|
|
|
// Documents without a filename annotation are assigned a synthesized name of the
|
|
|
|
|
|