diff --git a/api/filters/replacement/replacement.go b/api/filters/replacement/replacement.go index bff8bba411..edc40e9123 100644 --- a/api/filters/replacement/replacement.go +++ b/api/filters/replacement/replacement.go @@ -119,13 +119,14 @@ func applyToNode(node *yaml.RNode, value *yaml.RNode, target *types.TargetSelect if target.Options != nil && target.Options.Create { t, err = node.Pipe(yaml.LookupCreate(value.YNode().Kind, fieldPath...)) } else { - t, err = node.Pipe(yaml.Lookup(fieldPath...)) + // t, err = node.Pipe(yaml.Lookup(fieldPath...)) + t, err = node.Pipe(&yaml.PathMatcher{Path: fieldPath}) } if err != nil { return err } if t != nil { - if err = setTargetValue(target.Options, t, value); err != nil { + if err = applyToOneNode(target.Options, t, value); err != nil { return err } } @@ -133,6 +134,27 @@ func applyToNode(node *yaml.RNode, value *yaml.RNode, target *types.TargetSelect return nil } +func applyToOneNode(options *types.FieldOptions, t *yaml.RNode, value *yaml.RNode) error { + if len(t.YNode().Content) == 0 { + if err := setTargetValue(options, t, value); err != nil { + return err + } + return nil + } + + for _, scalarNode := range t.YNode().Content { + if options != nil && options.Create { + return fmt.Errorf("cannot use create option in a multi-value target") + } + rn := yaml.NewRNode(scalarNode) + if err := setTargetValue(options, rn, value); err != nil { + return err + } + } + + return nil +} + func setTargetValue(options *types.FieldOptions, t *yaml.RNode, value *yaml.RNode) error { value = value.Copy() if options != nil && options.Delimiter != "" { @@ -152,7 +174,9 @@ func setTargetValue(options *types.FieldOptions, t *yaml.RNode, value *yaml.RNod } value.YNode().Value = strings.Join(tv, options.Delimiter) } + t.SetYNode(value.YNode()) + return nil } diff --git a/kyaml/yaml/fns.go b/kyaml/yaml/fns.go index 4be2dba47e..999ef53e02 100644 --- a/kyaml/yaml/fns.go +++ b/kyaml/yaml/fns.go @@ -782,6 +782,13 @@ func IsListIndex(p string) bool { return strings.HasPrefix(p, "[") && strings.HasSuffix(p, "]") } +// IsIdxNumber returns true if p is an index number. +// e.g. 1 +func IsIdxNumber(p string) bool { + idx, err := strconv.Atoi(p) + return err == nil && idx >= 0 +} + // SplitIndexNameValue splits a lookup part Val index into the field name // and field value to match. // e.g. splits [name=nginx] into (name, nginx) diff --git a/kyaml/yaml/match.go b/kyaml/yaml/match.go index 3b80f8b7be..713dd25438 100644 --- a/kyaml/yaml/match.go +++ b/kyaml/yaml/match.go @@ -5,6 +5,7 @@ package yaml import ( "regexp" + "strconv" "strings" ) @@ -42,9 +43,10 @@ type PathMatcher struct { // This is useful for if the nodes are to be printed in FlowStyle. StripComments bool - val *RNode - field string - matchRegex string + val *RNode + field string + matchRegex string + indexNumber int } func (p *PathMatcher) stripComments(n *Node) { @@ -79,6 +81,10 @@ func (p *PathMatcher) filter(rn *RNode) (*RNode, error) { return p.val, nil } + if IsIdxNumber(p.Path[0]) { + return p.doIndexSeq(rn) + } + if IsListIndex(p.Path[0]) { // match seq elements return p.doSeq(rn) @@ -98,7 +104,6 @@ func (p *PathMatcher) doMatchEvery(rn *RNode) (*RNode, error) { return nil, err } - // fmt.Println(p.val.String()) return p.val, nil } @@ -134,6 +139,36 @@ func (p *PathMatcher) doField(rn *RNode) (*RNode, error) { return p.val, err } +// doIndexSeq iterates over a sequence and appends elements matching the index p.Val +func (p *PathMatcher) doIndexSeq(rn *RNode) (*RNode, error) { + // parse to index number + idx, err := strconv.Atoi(p.Path[0]) + if err != nil { + return nil, err + } + p.indexNumber = idx + + elements, err := rn.Elements() + if err != nil { + return nil, err + } + + // get target element + element := elements[idx] + + // recurse on the matching element + pm := &PathMatcher{Path: p.Path[1:]} + add, err := pm.filter(element) + for k, v := range pm.Matches { + p.Matches[k] = v + } + if err != nil || add == nil { + return nil, err + } + p.append("", add.Content()...) + return p.val, nil +} + // doSeq iterates over a sequence and appends elements matching the path regex to p.Val func (p *PathMatcher) doSeq(rn *RNode) (*RNode, error) { // parse the field + match pair