mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-05-17 18:25:26 +00:00
Merge pull request #3773 from natasha41575/ReplacementFieldOptions
use field options to refine replacements
This commit is contained in:
@@ -66,18 +66,50 @@ func rejectId(rejects []*types.Selector, nodeId *types.KrmId) bool {
|
||||
|
||||
func applyToNode(node *yaml.RNode, value *yaml.RNode, target *types.TargetSelector) error {
|
||||
for _, fp := range target.FieldPaths {
|
||||
t, err := node.Pipe(yaml.Lookup(strings.Split(fp, ".")...))
|
||||
fieldPath := strings.Split(fp, ".")
|
||||
var t *yaml.RNode
|
||||
var err error
|
||||
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...))
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if t != nil {
|
||||
// TODO (#3492): Use the field options to refine interpretation of the field
|
||||
t.SetYNode(value.YNode())
|
||||
if err = setTargetValue(target.Options, t, value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setTargetValue(options *types.FieldOptions, t *yaml.RNode, value *yaml.RNode) error {
|
||||
if options != nil && options.Delimiter != "" {
|
||||
|
||||
if t.YNode().Kind != yaml.ScalarNode {
|
||||
return fmt.Errorf("delimiter option can only be used with scalar nodes")
|
||||
}
|
||||
|
||||
tv := strings.Split(t.YNode().Value, options.Delimiter)
|
||||
v := yaml.GetValue(value)
|
||||
// TODO: Add a way to remove an element
|
||||
switch {
|
||||
case options.Index < 0: // prefix
|
||||
tv = append([]string{v}, tv...)
|
||||
case options.Index >= len(tv): // suffix
|
||||
tv = append(tv, v)
|
||||
default: // replace an element
|
||||
tv[options.Index] = v
|
||||
}
|
||||
value.YNode().Value = strings.Join(tv, options.Delimiter)
|
||||
}
|
||||
t.SetYNode(value.YNode())
|
||||
return nil
|
||||
}
|
||||
|
||||
func getReplacement(nodes []*yaml.RNode, r *types.Replacement) (*yaml.RNode, error) {
|
||||
source, err := selectSourceNode(nodes, r.Source)
|
||||
if err != nil {
|
||||
@@ -93,10 +125,28 @@ func getReplacement(nodes []*yaml.RNode, r *types.Replacement) (*yaml.RNode, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO (#3492): Use the field options to refine interpretation of the field
|
||||
if !rn.IsNilOrEmpty() {
|
||||
return getRefinedValue(r.Source.Options, rn)
|
||||
}
|
||||
return rn, nil
|
||||
}
|
||||
|
||||
func getRefinedValue(options *types.FieldOptions, rn *yaml.RNode) (*yaml.RNode, error) {
|
||||
if options == nil || options.Delimiter == "" {
|
||||
return rn, nil
|
||||
}
|
||||
if rn.YNode().Kind != yaml.ScalarNode {
|
||||
return nil, fmt.Errorf("delimiter option can only be used with scalar nodes")
|
||||
}
|
||||
value := strings.Split(yaml.GetValue(rn), options.Delimiter)
|
||||
if options.Index >= len(value) || options.Index < 0 {
|
||||
return nil, fmt.Errorf("options.index %d is out of bounds for value %s", options.Index, yaml.GetValue(rn))
|
||||
}
|
||||
n := rn.Copy()
|
||||
n.YNode().Value = value[options.Index]
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// selectSourceNode finds the node that matches the selector, returning
|
||||
// an error if multiple or none are found
|
||||
func selectSourceNode(nodes []*yaml.RNode, selector *types.SourceSelector) (*yaml.RNode, error) {
|
||||
|
||||
@@ -761,6 +761,583 @@ spec:
|
||||
name: postgresdb
|
||||
`,
|
||||
},
|
||||
"partial string replacement - replace": {
|
||||
input: `apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:1.7.9
|
||||
name: nginx-tagged
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy2
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:1.7.9
|
||||
name: nginx-tagged
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
`,
|
||||
replacements: `replacements:
|
||||
- source:
|
||||
kind: Deployment
|
||||
name: deploy2
|
||||
fieldPath: spec.template.spec.containers.0.image
|
||||
options:
|
||||
delimiter: ':'
|
||||
targets:
|
||||
- select:
|
||||
kind: Deployment
|
||||
name: deploy1
|
||||
fieldPaths:
|
||||
- spec.template.spec.containers.1.image
|
||||
options:
|
||||
delimiter: ':'
|
||||
`,
|
||||
expected: `apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:1.7.9
|
||||
name: nginx-tagged
|
||||
- image: nginx:1.8.0
|
||||
name: postgresdb
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy2
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:1.7.9
|
||||
name: nginx-tagged
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
`,
|
||||
},
|
||||
"partial string replacement - prefix": {
|
||||
input: `apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod1
|
||||
spec:
|
||||
volumes:
|
||||
- projected:
|
||||
sources:
|
||||
- configMap:
|
||||
name: myconfigmap
|
||||
items:
|
||||
- key: config
|
||||
path: my/group
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod2
|
||||
spec:
|
||||
volumes:
|
||||
- projected:
|
||||
sources:
|
||||
- configMap:
|
||||
name: myconfigmap
|
||||
items:
|
||||
- key: config
|
||||
path: group/config
|
||||
`,
|
||||
replacements: `replacements:
|
||||
- source:
|
||||
kind: Pod
|
||||
name: pod1
|
||||
fieldPath: spec.volumes.0.projected.sources.0.configMap.items.0.path
|
||||
options:
|
||||
delimiter: '/'
|
||||
index: 0
|
||||
targets:
|
||||
- select:
|
||||
kind: Pod
|
||||
name: pod2
|
||||
fieldPaths:
|
||||
- spec.volumes.0.projected.sources.0.configMap.items.0.path
|
||||
options:
|
||||
delimiter: '/'
|
||||
index: -1
|
||||
`,
|
||||
expected: `apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod1
|
||||
spec:
|
||||
volumes:
|
||||
- projected:
|
||||
sources:
|
||||
- configMap:
|
||||
name: myconfigmap
|
||||
items:
|
||||
- key: config
|
||||
path: my/group
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod2
|
||||
spec:
|
||||
volumes:
|
||||
- projected:
|
||||
sources:
|
||||
- configMap:
|
||||
name: myconfigmap
|
||||
items:
|
||||
- key: config
|
||||
path: my/group/config
|
||||
`,
|
||||
},
|
||||
"partial string replacement - suffix": {
|
||||
input: `apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod1
|
||||
spec:
|
||||
volumes:
|
||||
- projected:
|
||||
sources:
|
||||
- configMap:
|
||||
name: myconfigmap
|
||||
items:
|
||||
- key: config
|
||||
path: my/group
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod2
|
||||
spec:
|
||||
volumes:
|
||||
- projected:
|
||||
sources:
|
||||
- configMap:
|
||||
name: myconfigmap
|
||||
items:
|
||||
- key: config
|
||||
path: group/config
|
||||
`,
|
||||
replacements: `replacements:
|
||||
- source:
|
||||
kind: Pod
|
||||
name: pod2
|
||||
fieldPath: spec.volumes.0.projected.sources.0.configMap.items.0.path
|
||||
options:
|
||||
delimiter: '/'
|
||||
index: 1
|
||||
targets:
|
||||
- select:
|
||||
kind: Pod
|
||||
name: pod1
|
||||
fieldPaths:
|
||||
- spec.volumes.0.projected.sources.0.configMap.items.0.path
|
||||
options:
|
||||
delimiter: '/'
|
||||
index: 2
|
||||
`,
|
||||
expected: `apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod1
|
||||
spec:
|
||||
volumes:
|
||||
- projected:
|
||||
sources:
|
||||
- configMap:
|
||||
name: myconfigmap
|
||||
items:
|
||||
- key: config
|
||||
path: my/group/config
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod2
|
||||
spec:
|
||||
volumes:
|
||||
- projected:
|
||||
sources:
|
||||
- configMap:
|
||||
name: myconfigmap
|
||||
items:
|
||||
- key: config
|
||||
path: group/config
|
||||
`,
|
||||
},
|
||||
"partial string replacement - last element": {
|
||||
input: `apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod1
|
||||
spec:
|
||||
volumes:
|
||||
- projected:
|
||||
sources:
|
||||
- configMap:
|
||||
name: myconfigmap
|
||||
items:
|
||||
- key: config
|
||||
path: my/group1
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod2
|
||||
spec:
|
||||
volumes:
|
||||
- projected:
|
||||
sources:
|
||||
- configMap:
|
||||
name: myconfigmap
|
||||
items:
|
||||
- key: config
|
||||
path: group2
|
||||
`,
|
||||
replacements: `replacements:
|
||||
- source:
|
||||
kind: Pod
|
||||
name: pod2
|
||||
fieldPath: spec.volumes.0.projected.sources.0.configMap.items.0.path
|
||||
options:
|
||||
delimiter: '/'
|
||||
index: 0
|
||||
targets:
|
||||
- select:
|
||||
kind: Pod
|
||||
name: pod1
|
||||
fieldPaths:
|
||||
- spec.volumes.0.projected.sources.0.configMap.items.0.path
|
||||
options:
|
||||
delimiter: '/'
|
||||
index: 1
|
||||
`,
|
||||
expected: `apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod1
|
||||
spec:
|
||||
volumes:
|
||||
- projected:
|
||||
sources:
|
||||
- configMap:
|
||||
name: myconfigmap
|
||||
items:
|
||||
- key: config
|
||||
path: my/group2
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod2
|
||||
spec:
|
||||
volumes:
|
||||
- projected:
|
||||
sources:
|
||||
- configMap:
|
||||
name: myconfigmap
|
||||
items:
|
||||
- key: config
|
||||
path: group2
|
||||
`,
|
||||
},
|
||||
"partial string replacement - first element": {
|
||||
input: `apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod1
|
||||
spec:
|
||||
volumes:
|
||||
- projected:
|
||||
sources:
|
||||
- configMap:
|
||||
name: myconfigmap
|
||||
items:
|
||||
- key: config
|
||||
path: group1/config
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod2
|
||||
spec:
|
||||
volumes:
|
||||
- projected:
|
||||
sources:
|
||||
- configMap:
|
||||
name: myconfigmap
|
||||
items:
|
||||
- key: config
|
||||
path: group2
|
||||
`,
|
||||
replacements: `replacements:
|
||||
- source:
|
||||
kind: Pod
|
||||
name: pod2
|
||||
fieldPath: spec.volumes.0.projected.sources.0.configMap.items.0.path
|
||||
options:
|
||||
delimiter: '/'
|
||||
index: 0
|
||||
targets:
|
||||
- select:
|
||||
kind: Pod
|
||||
name: pod1
|
||||
fieldPaths:
|
||||
- spec.volumes.0.projected.sources.0.configMap.items.0.path
|
||||
options:
|
||||
delimiter: '/'
|
||||
index: 0
|
||||
`,
|
||||
expected: `apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod1
|
||||
spec:
|
||||
volumes:
|
||||
- projected:
|
||||
sources:
|
||||
- configMap:
|
||||
name: myconfigmap
|
||||
items:
|
||||
- key: config
|
||||
path: group2/config
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod2
|
||||
spec:
|
||||
volumes:
|
||||
- projected:
|
||||
sources:
|
||||
- configMap:
|
||||
name: myconfigmap
|
||||
items:
|
||||
- key: config
|
||||
path: group2
|
||||
`,
|
||||
},
|
||||
"options.index out of bounds": {
|
||||
input: `apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod1
|
||||
spec:
|
||||
volumes:
|
||||
- projected:
|
||||
sources:
|
||||
- configMap:
|
||||
name: myconfigmap
|
||||
items:
|
||||
- key: config
|
||||
path: my/group1
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod2
|
||||
spec:
|
||||
volumes:
|
||||
- projected:
|
||||
sources:
|
||||
- configMap:
|
||||
name: myconfigmap
|
||||
items:
|
||||
- key: config
|
||||
path: group2
|
||||
`,
|
||||
replacements: `replacements:
|
||||
- source:
|
||||
kind: Pod
|
||||
name: pod2
|
||||
fieldPath: spec.volumes.0.projected.sources.0.configMap.items.0.path
|
||||
options:
|
||||
delimiter: '/'
|
||||
index: -1
|
||||
targets:
|
||||
- select:
|
||||
kind: Pod
|
||||
name: pod1
|
||||
fieldPaths:
|
||||
- spec.volumes.0.projected.sources.0.configMap.items.0.path
|
||||
options:
|
||||
delimiter: '/'
|
||||
index: 1
|
||||
`,
|
||||
expectedErr: "options.index -1 is out of bounds for value group2",
|
||||
},
|
||||
"create": {
|
||||
input: `apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod
|
||||
spec:
|
||||
containers:
|
||||
- image: busybox
|
||||
name: myapp-container
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy2
|
||||
`,
|
||||
replacements: `replacements:
|
||||
- source:
|
||||
kind: Pod
|
||||
name: pod
|
||||
fieldPath: spec.containers
|
||||
targets:
|
||||
- select:
|
||||
name: deploy1
|
||||
fieldPaths:
|
||||
- spec.template.spec.containers
|
||||
options:
|
||||
create: true
|
||||
- source:
|
||||
kind: Pod
|
||||
name: pod
|
||||
fieldPath: spec.containers
|
||||
targets:
|
||||
- select:
|
||||
name: deploy2
|
||||
fieldPaths:
|
||||
- spec.template.spec.containers
|
||||
`,
|
||||
expected: `apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod
|
||||
spec:
|
||||
containers:
|
||||
- image: busybox
|
||||
name: myapp-container
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: busybox
|
||||
name: myapp-container
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy2
|
||||
`,
|
||||
},
|
||||
"complex type with delimiter in source": {
|
||||
input: `apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod
|
||||
spec:
|
||||
containers:
|
||||
- image: busybox
|
||||
name: myapp-container
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy2
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers: {}
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy3
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers: {}
|
||||
`,
|
||||
replacements: `replacements:
|
||||
- source:
|
||||
kind: Pod
|
||||
name: pod
|
||||
fieldPath: spec.containers
|
||||
options:
|
||||
delimiter: "/"
|
||||
targets:
|
||||
- select:
|
||||
kind: Deployment
|
||||
fieldPaths:
|
||||
- spec.template.spec.containers
|
||||
`,
|
||||
expectedErr: "delimiter option can only be used with scalar nodes",
|
||||
},
|
||||
"complex type with delimiter in target": {
|
||||
input: `apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod
|
||||
spec:
|
||||
containers:
|
||||
- image: busybox
|
||||
name: myapp-container
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy2
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers: {}
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy3
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers: {}
|
||||
`,
|
||||
replacements: `replacements:
|
||||
- source:
|
||||
kind: Pod
|
||||
name: pod
|
||||
fieldPath: spec.containers
|
||||
targets:
|
||||
- select:
|
||||
kind: Deployment
|
||||
fieldPaths:
|
||||
- spec.template.spec.containers
|
||||
options:
|
||||
delimiter: "/"
|
||||
`,
|
||||
expectedErr: "delimiter option can only be used with scalar nodes",
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
|
||||
@@ -43,8 +43,6 @@ type TargetSelector struct {
|
||||
}
|
||||
|
||||
// FieldOptions refine the interpretation of FieldPaths.
|
||||
// TODO (#3492): Implement use of these options, they are
|
||||
// currently unused
|
||||
type FieldOptions struct {
|
||||
// Used to split/join the field.
|
||||
Delimiter string `json:"delimiter" yaml:"delimiter"`
|
||||
@@ -52,8 +50,9 @@ type FieldOptions struct {
|
||||
// Which position in the split to consider.
|
||||
Index int `json:"index" yaml:"index"`
|
||||
|
||||
// None, Base64, URL, Hex, etc.
|
||||
Encoding string `json:"encoding" yaml:"index"`
|
||||
// TODO (#3492): Implement use of this option
|
||||
// None, Base64, URL, Hex, etc
|
||||
Encoding string `json:"encoding" yaml:"encoding"`
|
||||
|
||||
// If field missing, add it.
|
||||
Create bool `json:"create" yaml:"create"`
|
||||
|
||||
Reference in New Issue
Block a user