mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-11 17:12:51 +00:00
Replacements should throw errors on invalid targets (#4789)
* Replacements should throw errors on invalid targets * Refactor to satisfy complexity linter * Move new tests to filter suite
This commit is contained in:
@@ -4,13 +4,13 @@
|
|||||||
package replacement
|
package replacement
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/internal/utils"
|
"sigs.k8s.io/kustomize/api/internal/utils"
|
||||||
"sigs.k8s.io/kustomize/api/resource"
|
"sigs.k8s.io/kustomize/api/resource"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/errors"
|
||||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||||
kyaml_utils "sigs.k8s.io/kustomize/kyaml/utils"
|
kyaml_utils "sigs.k8s.io/kustomize/kyaml/utils"
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
@@ -105,7 +105,7 @@ func getRefinedValue(options *types.FieldOptions, rn *yaml.RNode) (*yaml.RNode,
|
|||||||
func applyReplacement(nodes []*yaml.RNode, value *yaml.RNode, targetSelectors []*types.TargetSelector) ([]*yaml.RNode, error) {
|
func applyReplacement(nodes []*yaml.RNode, value *yaml.RNode, targetSelectors []*types.TargetSelector) ([]*yaml.RNode, error) {
|
||||||
for _, selector := range targetSelectors {
|
for _, selector := range targetSelectors {
|
||||||
if selector.Select == nil {
|
if selector.Select == nil {
|
||||||
return nil, errors.New("target must specify resources to select")
|
return nil, errors.Errorf("target must specify resources to select")
|
||||||
}
|
}
|
||||||
if len(selector.FieldPaths) == 0 {
|
if len(selector.FieldPaths) == 0 {
|
||||||
selector.FieldPaths = []string{types.DefaultReplacementFieldPath}
|
selector.FieldPaths = []string{types.DefaultReplacementFieldPath}
|
||||||
@@ -179,37 +179,61 @@ func rejectId(rejects []*types.Selector, id *resid.ResId) bool {
|
|||||||
|
|
||||||
func copyValueToTarget(target *yaml.RNode, value *yaml.RNode, selector *types.TargetSelector) error {
|
func copyValueToTarget(target *yaml.RNode, value *yaml.RNode, selector *types.TargetSelector) error {
|
||||||
for _, fp := range selector.FieldPaths {
|
for _, fp := range selector.FieldPaths {
|
||||||
fieldPath := kyaml_utils.SmarterPathSplitter(fp, ".")
|
mutator := updateMatchingFields
|
||||||
create, err := shouldCreateField(selector.Options, fieldPath)
|
if selector.Options != nil && selector.Options.Create {
|
||||||
if err != nil {
|
mutator = createOrUpdateOneField
|
||||||
|
}
|
||||||
|
if err := mutator(target, value, selector.Options, fp); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var targetFields []*yaml.RNode
|
// updateMatchingFields updates all fields in target that match the given field path.
|
||||||
if create {
|
// If the field path does not already exist in the target, an error is returned.
|
||||||
createdField, createErr := target.Pipe(yaml.LookupCreate(value.YNode().Kind, fieldPath...))
|
func updateMatchingFields(target *yaml.RNode, value *yaml.RNode, opts *types.FieldOptions, fp string) error {
|
||||||
if createErr != nil {
|
fieldPath := kyaml_utils.SmarterPathSplitter(fp, ".")
|
||||||
return fmt.Errorf("error creating replacement node: %w", createErr)
|
// may return multiple fields, always wrapped in a sequence node
|
||||||
}
|
foundFieldSequence, lookupErr := target.Pipe(&yaml.PathMatcher{Path: fieldPath})
|
||||||
targetFields = append(targetFields, createdField)
|
if lookupErr != nil {
|
||||||
} else {
|
return fmt.Errorf("error finding field in replacement target: %w", lookupErr)
|
||||||
// may return multiple fields, always wrapped in a sequence node
|
}
|
||||||
foundFieldSequence, lookupErr := target.Pipe(&yaml.PathMatcher{Path: fieldPath})
|
targetFields, err := foundFieldSequence.Elements()
|
||||||
if lookupErr != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error finding field in replacement target: %w", lookupErr)
|
return fmt.Errorf("error fetching elements in replacement target: %w", err)
|
||||||
}
|
}
|
||||||
targetFields, err = foundFieldSequence.Elements()
|
if len(targetFields) == 0 {
|
||||||
if err != nil {
|
return errors.Errorf("unable to find field %s in replacement target", fp)
|
||||||
return fmt.Errorf("error fetching elements in replacement target: %w", err)
|
}
|
||||||
}
|
|
||||||
|
for _, t := range targetFields {
|
||||||
|
if err := setFieldValue(opts, t, value); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
for _, t := range targetFields {
|
// createOrUpdateOneField updates the field in the target that matches the given field path.
|
||||||
if err := setFieldValue(selector.Options, t, value); err != nil {
|
// If the field path does not already exist in the target, it is created.
|
||||||
return err
|
// Wildcard matching is not supported, nor is creating intermediate list nodes.
|
||||||
}
|
func createOrUpdateOneField(target *yaml.RNode, value *yaml.RNode, opts *types.FieldOptions, fp string) error {
|
||||||
|
fieldPath := kyaml_utils.SmarterPathSplitter(fp, ".")
|
||||||
|
for _, f := range fieldPath {
|
||||||
|
if f == "*" {
|
||||||
|
return fmt.Errorf("cannot support create option in a multi-value target")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
createdField, createErr := target.Pipe(yaml.LookupCreate(value.YNode().Kind, fieldPath...))
|
||||||
|
if createErr != nil {
|
||||||
|
return fmt.Errorf("error creating replacement node: %w", createErr)
|
||||||
|
}
|
||||||
|
if createdField == nil {
|
||||||
|
return errors.Errorf("unable to find or create field %s in replacement target", fp)
|
||||||
|
}
|
||||||
|
if err := setFieldValue(opts, createdField, value); err != nil {
|
||||||
|
return errors.WrapPrefixf(err, "unable to set field %s in replacement target", fp)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -243,16 +267,3 @@ func setFieldValue(options *types.FieldOptions, targetField *yaml.RNode, value *
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func shouldCreateField(options *types.FieldOptions, fieldPath []string) (bool, error) {
|
|
||||||
if options == nil || !options.Create {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
// create option is not supported in a wildcard matching
|
|
||||||
for _, f := range fieldPath {
|
|
||||||
if f == "*" {
|
|
||||||
return false, fmt.Errorf("cannot support create option in a multi-value target")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1198,11 +1198,6 @@ apiVersion: apps/v1
|
|||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: deploy1
|
name: deploy1
|
||||||
---
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: deploy2
|
|
||||||
`,
|
`,
|
||||||
replacements: `replacements:
|
replacements: `replacements:
|
||||||
- source:
|
- source:
|
||||||
@@ -1216,15 +1211,6 @@ metadata:
|
|||||||
- spec.template.spec.containers
|
- spec.template.spec.containers
|
||||||
options:
|
options:
|
||||||
create: true
|
create: true
|
||||||
- source:
|
|
||||||
kind: Pod
|
|
||||||
name: pod
|
|
||||||
fieldPath: spec.containers
|
|
||||||
targets:
|
|
||||||
- select:
|
|
||||||
name: deploy2
|
|
||||||
fieldPaths:
|
|
||||||
- spec.template.spec.containers
|
|
||||||
`,
|
`,
|
||||||
expected: `apiVersion: v1
|
expected: `apiVersion: v1
|
||||||
kind: Pod
|
kind: Pod
|
||||||
@@ -1245,11 +1231,6 @@ spec:
|
|||||||
containers:
|
containers:
|
||||||
- image: busybox
|
- image: busybox
|
||||||
name: myapp-container
|
name: myapp-container
|
||||||
---
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: deploy2
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
"complex type with delimiter in source": {
|
"complex type with delimiter in source": {
|
||||||
@@ -2416,6 +2397,86 @@ spec:
|
|||||||
restartPolicy: new
|
restartPolicy: new
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
"issue4761 - path not in target with create: true": {
|
||||||
|
input: `
|
||||||
|
---
|
||||||
|
apiVersion: networking.istio.io/v1alpha3
|
||||||
|
kind: EnvoyFilter
|
||||||
|
metadata:
|
||||||
|
name: request-id
|
||||||
|
spec:
|
||||||
|
configPatches:
|
||||||
|
- applyTo: NETWORK_FILTER
|
||||||
|
- applyTo: NETWORK_FILTER
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: istio-version
|
||||||
|
annotations:
|
||||||
|
config.kubernetes.io/local-config: true
|
||||||
|
data:
|
||||||
|
ISTIO_REGEX: '^1\.14.*'
|
||||||
|
`,
|
||||||
|
replacements: `
|
||||||
|
replacements:
|
||||||
|
- source:
|
||||||
|
kind: ConfigMap
|
||||||
|
name: istio-version
|
||||||
|
fieldPath: data.ISTIO_REGEX
|
||||||
|
targets:
|
||||||
|
- select:
|
||||||
|
kind: EnvoyFilter
|
||||||
|
fieldPaths:
|
||||||
|
- spec.configPatches.0.match.proxy.proxyVersion
|
||||||
|
- spec.configPatches.1.match.proxy.proxyVersion
|
||||||
|
- spec.configPatches.2.match.proxy.proxyVersion
|
||||||
|
- spec.configPatches.3.match.proxy.proxyVersion
|
||||||
|
options:
|
||||||
|
create: true
|
||||||
|
`,
|
||||||
|
expectedErr: "unable to find or create field spec.configPatches.2.match.proxy.proxyVersion in replacement target",
|
||||||
|
},
|
||||||
|
"issue4761 - path not in target with create: false": {
|
||||||
|
input: `
|
||||||
|
---
|
||||||
|
apiVersion: networking.istio.io/v1alpha3
|
||||||
|
kind: EnvoyFilter
|
||||||
|
metadata:
|
||||||
|
name: request-id
|
||||||
|
spec:
|
||||||
|
configPatches:
|
||||||
|
- applyTo: NETWORK_FILTER
|
||||||
|
- applyTo: NETWORK_FILTER
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: istio-version
|
||||||
|
annotations:
|
||||||
|
config.kubernetes.io/local-config: true
|
||||||
|
data:
|
||||||
|
ISTIO_REGEX: '^1\.14.*'
|
||||||
|
`,
|
||||||
|
replacements: `
|
||||||
|
replacements:
|
||||||
|
- source:
|
||||||
|
kind: ConfigMap
|
||||||
|
name: istio-version
|
||||||
|
fieldPath: data.ISTIO_REGEX
|
||||||
|
targets:
|
||||||
|
- select:
|
||||||
|
kind: EnvoyFilter
|
||||||
|
fieldPaths:
|
||||||
|
- spec.configPatches.0.match.proxy.proxyVersion
|
||||||
|
- spec.configPatches.1.match.proxy.proxyVersion
|
||||||
|
- spec.configPatches.2.match.proxy.proxyVersion
|
||||||
|
- spec.configPatches.3.match.proxy.proxyVersion
|
||||||
|
options:
|
||||||
|
create: false
|
||||||
|
`,
|
||||||
|
expectedErr: "unable to find field spec.configPatches.0.match.proxy.proxyVersion in replacement target",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for tn, tc := range testCases {
|
for tn, tc := range testCases {
|
||||||
|
|||||||
Reference in New Issue
Block a user