Merge pull request #3773 from natasha41575/ReplacementFieldOptions

use field options to refine replacements
This commit is contained in:
Kubernetes Prow Robot
2021-04-09 12:15:16 -07:00
committed by GitHub
3 changed files with 634 additions and 8 deletions

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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"`