Skip to content

Commit

Permalink
get fieldpath working
Browse files Browse the repository at this point in the history
  • Loading branch information
logandavies181 committed Jun 4, 2023
1 parent 743759d commit 38f2c31
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 14 deletions.
35 changes: 21 additions & 14 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ func contains(list []string, str string) bool {
}

func (c Config) walkSequenceNode(in *yaml.RNode) error {
in.AppendToFieldPath("[]")

_, err := c.Filter(in)
if err != nil {
return err
Expand All @@ -57,6 +59,7 @@ func (c Config) walkMapNode(in *yaml.MapNode) error {
if err != nil {
return err
}
in.Value.AppendToFieldPath(strings.TrimSuffix(key, "\n"))
if key == "annotations\n" || key == "labels\n" {
return in.Value.VisitFields(c.walkMetadataNode)
}
Expand All @@ -72,27 +75,28 @@ func (c Config) walkMapNode(in *yaml.MapNode) error {
// walkMetadataNode is the same as Filter for a scalar node,
// except that it ensures the value is always treated as a string
func (c Config) walkMetadataNode(in *yaml.MapNode) error {
return c.processScalarNode(in.Value, true)
_, err := c.processScalarNode(in.Value, true)
return err
}

func (c Config) processScalarNode(in *yaml.RNode, alwaysString bool) error {
func (c Config) processScalarNode(in *yaml.RNode, alwaysString bool) (*yaml.RNode, error) {
str, err := in.String()
if err != nil {
return fmt.Errorf("Could not parse node into string: %v", err)
return nil, fmt.Errorf("Could not parse node into string: %v", err)
}

substed, err := envsubst.EvalAdvanced(str, envsubst.AdvancedMapping(c.envMapping))
if err != nil {
return fmt.Errorf("Could not envsubst: %v", err)
return nil, fmt.Errorf("Could not envsubst: %v", err)
}

if substed == str {
return nil
return in, nil
}

if isEmpty(substed) {
if !c.AllowEmpty {
return fmt.Errorf(
return nil, fmt.Errorf(
"Value `%s` evaluated to empty string. Did you forget to set an environment variable?",
strings.TrimSuffix(str, "\n"))
}
Expand All @@ -109,39 +113,42 @@ func (c Config) processScalarNode(in *yaml.RNode, alwaysString bool) error {
}
node, err := yaml.Parse(substed)
if err != nil {
return fmt.Errorf("Could not parse node after envsubsting: %v", err)
return nil, fmt.Errorf("Could not parse node after envsubsting: %v", err)
}

if node.YNode().Kind != yaml.ScalarNode {
return fmt.Errorf("Invalid output: `%s` did not evaluate to a scalar", str)
return nil, fmt.Errorf("Invalid output: `%s` did not evaluate to a scalar", str)
}

_, err = in.Pipe(yaml.Set(node))

return err
return in.Pipe(yaml.Set(node))
}

func (c Config) Filter(in *yaml.RNode) (*yaml.RNode, error) {
if in.IsNil() {
return nil, nil
}

if len(in.FieldPath()) == 0 {
fmt.Fprintln(os.Stderr, in.GetApiVersion())
fmt.Fprintln(os.Stderr, in.GetKind())
}

switch y := in.YNode().Kind; y {
case yaml.MappingNode:
err := in.VisitFields(c.walkMapNode)
err := visitFields(in, c.walkMapNode)
if err != nil {
return nil, err
}
return in, nil
case yaml.SequenceNode:
err := in.VisitElements(c.walkSequenceNode)
err := visitElements(in, c.walkSequenceNode)
if err != nil {
return nil, err
}

return in, nil
case yaml.ScalarNode:
return nil, c.processScalarNode(in, false)
return c.processScalarNode(in, false)
case yaml.AliasNode, yaml.DocumentNode:
fallthrough
default:
Expand Down
124 changes: 124 additions & 0 deletions walk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package main

import (
"sigs.k8s.io/kustomize/kyaml/yaml"
)

// visitFields is a copy of yaml.(*RNode).VisitFields that preserves
// the FieldPath of the parent
func visitFields(rn *yaml.RNode, fn func(node *yaml.MapNode) error) error {
// get the list of srcFieldNames
srcFieldNames, err := rn.Fields()
if err != nil {
return err
}

// visit each field
for _, fieldName := range srcFieldNames {
if err := fn(field(rn, fieldName)); err != nil {
return err
}
}
return nil
}

// field is a copy of yaml(*RNode).Field that preserves the FieldPath
// of the parent
func field(rn *yaml.RNode, field string) *yaml.MapNode {
if rn.YNode().Kind != yaml.MappingNode {
return nil
}
var result *yaml.MapNode
visitMappingNodeFields(rn.Content(), func(key, value *yaml.Node) {
valOut := yaml.NewRNode(value)
valOut.AppendToFieldPath(rn.FieldPath()...)

result = &yaml.MapNode{Key: yaml.NewRNode(key), Value: valOut}
}, field)
return result
}

// visitMappingNodeFields calls fn for fields in the content, in content order.
// The caller is responsible to ensure the node is a mapping node. If fieldNames
// are specified, then fn is called only for the fields that match the given
// fieldNames.
func visitMappingNodeFields(content []*yaml.Node, fn func(key, value *yaml.Node), fieldNames ...string) {
switch len(fieldNames) {
case 0: // visit all fields
visitFieldsWhileTrue(content, func(key, value *yaml.Node, _ int) bool {
fn(key, value)
return true
})
case 1: // visit single field
visitFieldsWhileTrue(content, func(key, value *yaml.Node, _ int) bool {
if key == nil {
return true
}
if fieldNames[0] == key.Value {
fn(key, value)
return false
}
return true
})
default: // visit specified fields
fieldsStillToVisit := make(map[string]bool, len(fieldNames))
for _, fieldName := range fieldNames {
fieldsStillToVisit[fieldName] = true
}
visitFieldsWhileTrue(content, func(key, value *yaml.Node, _ int) bool {
if key == nil {
return true
}
if fieldsStillToVisit[key.Value] {
fn(key, value)
delete(fieldsStillToVisit, key.Value)
}
return len(fieldsStillToVisit) > 0
})
}
}

// visitFieldsWhileTrue calls fn for the fields in content, in content order,
// until either fn returns false or all fields have been visited. The caller
// should ensure that content is from a mapping node, or fits the same expected
// pattern (consecutive key/value entries in the slice).
func visitFieldsWhileTrue(content []*yaml.Node, fn func(key, value *yaml.Node, keyIndex int) bool) {
for i := 0; i < len(content); i += 2 {
continueVisiting := fn(content[i], content[i+1], i)
if !continueVisiting {
return
}
}
}

// visitElements is a copy of yaml.(*RNode).VisitElements that preserves
// the FieldPath of the parent
func visitElements(rn *yaml.RNode, fn func(node *yaml.RNode) error) error {
elements, err := elements(rn)
if err != nil {
return err
}

for i := range elements {
if err := fn(elements[i]); err != nil {
return err
}
}
return nil
}

// elements is a copy of yaml.(*RNode).Elements that preserves the FieldPath
// of the parent
func elements(rn *yaml.RNode) ([]*yaml.RNode, error) {
if err := yaml.ErrorIfInvalid(rn, yaml.SequenceNode); err != nil {
return nil, err
}
var elements []*yaml.RNode
for i := 0; i < len(rn.Content()); i++ {
elem := yaml.NewRNode(rn.Content()[i])
elem.AppendToFieldPath(rn.FieldPath()...)

elements = append(elements, elem)
}
return elements, nil
}

0 comments on commit 38f2c31

Please sign in to comment.