mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-11 17:12:51 +00:00
Merge pull request #4424 from koba1t/feature/allow_setting_every_array_element_in_replacements
Allow setting every array element in replacements
This commit is contained in:
@@ -119,13 +119,13 @@ func applyToNode(node *yaml.RNode, value *yaml.RNode, target *types.TargetSelect
|
|||||||
if target.Options != nil && target.Options.Create {
|
if target.Options != nil && target.Options.Create {
|
||||||
t, err = node.Pipe(yaml.LookupCreate(value.YNode().Kind, fieldPath...))
|
t, err = node.Pipe(yaml.LookupCreate(value.YNode().Kind, fieldPath...))
|
||||||
} else {
|
} else {
|
||||||
t, err = node.Pipe(yaml.Lookup(fieldPath...))
|
t, err = node.Pipe(&yaml.PathMatcher{Path: fieldPath})
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if t != nil {
|
if t != nil {
|
||||||
if err = setTargetValue(target.Options, t, value); err != nil {
|
if err = applyToOneNode(target.Options, t, value); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,6 +133,27 @@ func applyToNode(node *yaml.RNode, value *yaml.RNode, target *types.TargetSelect
|
|||||||
return nil
|
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 {
|
func setTargetValue(options *types.FieldOptions, t *yaml.RNode, value *yaml.RNode) error {
|
||||||
value = value.Copy()
|
value = value.Copy()
|
||||||
if options != nil && options.Delimiter != "" {
|
if options != nil && options.Delimiter != "" {
|
||||||
@@ -152,7 +173,9 @@ func setTargetValue(options *types.FieldOptions, t *yaml.RNode, value *yaml.RNod
|
|||||||
}
|
}
|
||||||
value.YNode().Value = strings.Join(tv, options.Delimiter)
|
value.YNode().Value = strings.Join(tv, options.Delimiter)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.SetYNode(value.YNode())
|
t.SetYNode(value.YNode())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1431,6 +1431,208 @@ spec:
|
|||||||
name: second
|
name: second
|
||||||
version: latest
|
version: latest
|
||||||
property: second`,
|
property: second`,
|
||||||
|
},
|
||||||
|
"one replacements target has multiple value": {
|
||||||
|
input: `apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: sample-deploy
|
||||||
|
name: sample-deploy
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: sample-deploy
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: sample-deploy
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: nginx
|
||||||
|
name: main
|
||||||
|
env:
|
||||||
|
- name: deployment-name
|
||||||
|
value: XXXXX
|
||||||
|
- name: foo
|
||||||
|
value: bar
|
||||||
|
- image: nginx
|
||||||
|
name: sidecar
|
||||||
|
env:
|
||||||
|
- name: deployment-name
|
||||||
|
value: YYYYY
|
||||||
|
`,
|
||||||
|
replacements: `replacements:
|
||||||
|
- source:
|
||||||
|
kind: Deployment
|
||||||
|
name: sample-deploy
|
||||||
|
fieldPath: metadata.name
|
||||||
|
targets:
|
||||||
|
- select:
|
||||||
|
kind: Deployment
|
||||||
|
fieldPaths:
|
||||||
|
- spec.template.spec.containers.[image=nginx].env.[name=deployment-name].value
|
||||||
|
`,
|
||||||
|
expected: `apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: sample-deploy
|
||||||
|
name: sample-deploy
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: sample-deploy
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: sample-deploy
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: nginx
|
||||||
|
name: main
|
||||||
|
env:
|
||||||
|
- name: deployment-name
|
||||||
|
value: sample-deploy
|
||||||
|
- name: foo
|
||||||
|
value: bar
|
||||||
|
- image: nginx
|
||||||
|
name: sidecar
|
||||||
|
env:
|
||||||
|
- name: deployment-name
|
||||||
|
value: sample-deploy`,
|
||||||
|
},
|
||||||
|
"index contains '*' character": {
|
||||||
|
input: `apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: sample-deploy
|
||||||
|
name: sample-deploy
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: sample-deploy
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: sample-deploy
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: nginx
|
||||||
|
name: main
|
||||||
|
env:
|
||||||
|
- name: deployment-name
|
||||||
|
value: XXXXX
|
||||||
|
`,
|
||||||
|
replacements: `replacements:
|
||||||
|
- source:
|
||||||
|
kind: Deployment
|
||||||
|
name: sample-deploy
|
||||||
|
fieldPath: metadata.name
|
||||||
|
targets:
|
||||||
|
- select:
|
||||||
|
kind: Deployment
|
||||||
|
fieldPaths:
|
||||||
|
- spec.template.spec.containers.*.env.[name=deployment-name].value
|
||||||
|
`,
|
||||||
|
expected: `apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: sample-deploy
|
||||||
|
name: sample-deploy
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: sample-deploy
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: sample-deploy
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: nginx
|
||||||
|
name: main
|
||||||
|
env:
|
||||||
|
- name: deployment-name
|
||||||
|
value: sample-deploy`,
|
||||||
|
},
|
||||||
|
"list index contains '*' character": {
|
||||||
|
input: `apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: sample-deploy
|
||||||
|
name: sample-deploy
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: sample-deploy
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: sample-deploy
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: nginx
|
||||||
|
name: main
|
||||||
|
env:
|
||||||
|
- name: deployment-name
|
||||||
|
value: XXXXX
|
||||||
|
- name: foo
|
||||||
|
value: bar
|
||||||
|
- image: nginx
|
||||||
|
name: sidecar
|
||||||
|
env:
|
||||||
|
- name: deployment-name
|
||||||
|
value: YYYYY
|
||||||
|
`,
|
||||||
|
replacements: `replacements:
|
||||||
|
- source:
|
||||||
|
kind: Deployment
|
||||||
|
name: sample-deploy
|
||||||
|
fieldPath: metadata.name
|
||||||
|
targets:
|
||||||
|
- select:
|
||||||
|
kind: Deployment
|
||||||
|
fieldPaths:
|
||||||
|
- spec.template.spec.containers.*.env.[name=deployment-name].value
|
||||||
|
`,
|
||||||
|
expected: `apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: sample-deploy
|
||||||
|
name: sample-deploy
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: sample-deploy
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: sample-deploy
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: nginx
|
||||||
|
name: main
|
||||||
|
env:
|
||||||
|
- name: deployment-name
|
||||||
|
value: sample-deploy
|
||||||
|
- name: foo
|
||||||
|
value: bar
|
||||||
|
- image: nginx
|
||||||
|
name: sidecar
|
||||||
|
env:
|
||||||
|
- name: deployment-name
|
||||||
|
value: sample-deploy`,
|
||||||
},
|
},
|
||||||
"multiple field paths in target": {
|
"multiple field paths in target": {
|
||||||
input: `apiVersion: v1
|
input: `apiVersion: v1
|
||||||
@@ -1556,7 +1758,6 @@ spec:
|
|||||||
name: postgresdb
|
name: postgresdb
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
|
||||||
"replacement source.fieldPath does not exist": {
|
"replacement source.fieldPath does not exist": {
|
||||||
input: `apiVersion: v1
|
input: `apiVersion: v1
|
||||||
kind: ConfigMap
|
kind: ConfigMap
|
||||||
|
|||||||
@@ -782,6 +782,19 @@ func IsListIndex(p string) bool {
|
|||||||
return strings.HasPrefix(p, "[") && strings.HasSuffix(p, "]")
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsWildcard returns true if p is matching every elements.
|
||||||
|
// e.g. "*"
|
||||||
|
func IsWildcard(p string) bool {
|
||||||
|
return p == "*"
|
||||||
|
}
|
||||||
|
|
||||||
// SplitIndexNameValue splits a lookup part Val index into the field name
|
// SplitIndexNameValue splits a lookup part Val index into the field name
|
||||||
// and field value to match.
|
// and field value to match.
|
||||||
// e.g. splits [name=nginx] into (name, nginx)
|
// e.g. splits [name=nginx] into (name, nginx)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package yaml
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -45,6 +46,7 @@ type PathMatcher struct {
|
|||||||
val *RNode
|
val *RNode
|
||||||
field string
|
field string
|
||||||
matchRegex string
|
matchRegex string
|
||||||
|
indexNumber int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PathMatcher) stripComments(n *Node) {
|
func (p *PathMatcher) stripComments(n *Node) {
|
||||||
@@ -79,14 +81,49 @@ func (p *PathMatcher) filter(rn *RNode) (*RNode, error) {
|
|||||||
return p.val, nil
|
return p.val, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if IsIdxNumber(p.Path[0]) {
|
||||||
|
return p.doIndexSeq(rn)
|
||||||
|
}
|
||||||
|
|
||||||
if IsListIndex(p.Path[0]) {
|
if IsListIndex(p.Path[0]) {
|
||||||
// match seq elements
|
// match seq elements
|
||||||
return p.doSeq(rn)
|
return p.doSeq(rn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if IsWildcard(p.Path[0]) {
|
||||||
|
// match every elements (*)
|
||||||
|
return p.doMatchEvery(rn)
|
||||||
|
}
|
||||||
// match a field
|
// match a field
|
||||||
return p.doField(rn)
|
return p.doField(rn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *PathMatcher) doMatchEvery(rn *RNode) (*RNode, error) {
|
||||||
|
|
||||||
|
if err := rn.VisitElements(p.visitEveryElem); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PathMatcher) visitEveryElem(elem *RNode) error {
|
||||||
|
|
||||||
|
fieldName := p.Path[0]
|
||||||
|
// recurse on the matching element
|
||||||
|
pm := &PathMatcher{Path: p.Path[1:]}
|
||||||
|
add, err := pm.filter(elem)
|
||||||
|
for k, v := range pm.Matches {
|
||||||
|
p.Matches[k] = v
|
||||||
|
}
|
||||||
|
if err != nil || add == nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.append(fieldName, add.Content()...)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (p *PathMatcher) doField(rn *RNode) (*RNode, error) {
|
func (p *PathMatcher) doField(rn *RNode) (*RNode, error) {
|
||||||
// lookup the field
|
// lookup the field
|
||||||
field, err := rn.Pipe(Get(p.Path[0]))
|
field, err := rn.Pipe(Get(p.Path[0]))
|
||||||
@@ -102,6 +139,36 @@ func (p *PathMatcher) doField(rn *RNode) (*RNode, error) {
|
|||||||
return p.val, err
|
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
|
// doSeq iterates over a sequence and appends elements matching the path regex to p.Val
|
||||||
func (p *PathMatcher) doSeq(rn *RNode) (*RNode, error) {
|
func (p *PathMatcher) doSeq(rn *RNode) (*RNode, error) {
|
||||||
// parse the field + match pair
|
// parse the field + match pair
|
||||||
|
|||||||
@@ -77,6 +77,12 @@ spec:
|
|||||||
{[]string{
|
{[]string{
|
||||||
"spec", "template", "spec", "containers", "[name=s.*]", "ports", "[containerPort=.*2]"},
|
"spec", "template", "spec", "containers", "[name=s.*]", "ports", "[containerPort=.*2]"},
|
||||||
""},
|
""},
|
||||||
|
{[]string{
|
||||||
|
"spec", "template", "spec", "containers", "*", "image"},
|
||||||
|
"- nginx:1.7.9\n- sidecar:1.0.0\n"},
|
||||||
|
{[]string{
|
||||||
|
"spec", "template", "spec", "containers", "*", "ports", "*"},
|
||||||
|
"- containerPort: 80\n- containerPort: 8081\n- containerPort: 9090\n"},
|
||||||
}
|
}
|
||||||
for i, u := range updates {
|
for i, u := range updates {
|
||||||
result, err := node.Pipe(&PathMatcher{Path: u.path})
|
result, err := node.Pipe(&PathMatcher{Path: u.path})
|
||||||
|
|||||||
Reference in New Issue
Block a user