mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-05-17 18:25:26 +00:00
* Ahead-of-time wildcard path expansion solution * Wrapped PathGetter solution This approach doesn't work when multiple existing sequence elements should match, i.e. because the sequence contains maps and we're searching on a key they all contain (target all containers with a certain image would be one use case for this). PathGetter just takes the first match in that case, which is not what we want. * Add creation support to PathMatcher * Regression test for existing bug when creation is enabled and sequence query should match multiple elements * PathMatcher Create tests and support for sequence appending * revert hyphen append support PathGetter treats it as meaning 'last' not 'append' and does not have test coverage for its handling of this when create is set. Semantics are dubious given that multiple Replacement fieldPaths may be specified, which would cause successive appends. * This also provides a solution to issue 1493 * Review feedback
290 lines
10 KiB
Go
290 lines
10 KiB
Go
// Copyright 2019 The Kubernetes Authors.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package yaml
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"sigs.k8s.io/kustomize/kyaml/internal/forked/github.com/go-yaml/yaml"
|
|
)
|
|
|
|
func TestPathMatcher_Filter(t *testing.T) {
|
|
node := MustParse(`apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: nginx-deployment
|
|
labels:
|
|
app: nginx
|
|
spec:
|
|
replicas: 3
|
|
selector:
|
|
matchLabels:
|
|
app: nginx
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: nginx
|
|
spec:
|
|
containers:
|
|
- name: nginx
|
|
image: nginx:1.7.9
|
|
args: [-c, conf.yaml]
|
|
ports:
|
|
- containerPort: 80
|
|
- name: sidecar
|
|
image: sidecar:1.0.0
|
|
ports:
|
|
- containerPort: 8081
|
|
- containerPort: 9090
|
|
`)
|
|
|
|
updates := []struct {
|
|
path []string
|
|
value string
|
|
}{
|
|
{[]string{
|
|
"spec", "template", "spec", "containers", "[name=.*]"},
|
|
"- name: nginx\n image: nginx:1.7.9\n args: [-c, conf.yaml]\n ports:\n - containerPort: 80\n" +
|
|
"- name: sidecar\n image: sidecar:1.0.0\n ports:\n - containerPort: 8081\n - containerPort: 9090\n"},
|
|
{[]string{
|
|
"spec", "template", "spec", "containers", "[name=.*]", "image"},
|
|
"- nginx:1.7.9\n- sidecar:1.0.0\n"},
|
|
{[]string{
|
|
"spec", "template", "spec", "containers", "[name=n.*]", "image"},
|
|
"- nginx:1.7.9\n"},
|
|
{[]string{
|
|
"spec", "template", "spec", "containers", "[name=s.*]", "image"},
|
|
"- sidecar:1.0.0\n"},
|
|
{[]string{
|
|
"spec", "template", "spec", "containers", "[name=.*x]", "image"},
|
|
"- nginx:1.7.9\n"},
|
|
{[]string{
|
|
"spec", "template", "spec", "containers", "[name=.*]", "ports"},
|
|
"- - containerPort: 80\n- - containerPort: 8081\n - containerPort: 9090\n"},
|
|
{[]string{
|
|
"spec", "template", "spec", "containers", "[name=.*]", "ports", "[containerPort=8.*]"},
|
|
"- containerPort: 80\n- containerPort: 8081\n"},
|
|
{[]string{
|
|
"spec", "template", "spec", "containers", "[name=.*]", "ports", "[containerPort=.*1]"},
|
|
"- containerPort: 8081\n"},
|
|
{[]string{
|
|
"spec", "template", "spec", "containers", "[name=.*]", "ports", "[containerPort=9.*]"},
|
|
"- containerPort: 9090\n"},
|
|
{[]string{
|
|
"spec", "template", "spec", "containers", "[name=s.*]", "ports", "[containerPort=8.*]"},
|
|
"- containerPort: 8081\n"},
|
|
{[]string{
|
|
"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"},
|
|
{[]string{
|
|
"spec", "template", "spec", "containers", "[name=.*]", "args", "1"},
|
|
"- conf.yaml\n"},
|
|
}
|
|
for i, u := range updates {
|
|
result, err := node.Pipe(&PathMatcher{Path: u.path})
|
|
if !assert.NoError(t, err) {
|
|
return
|
|
}
|
|
assert.Equal(t, u.value, result.MustString(), fmt.Sprintf("%d", i))
|
|
}
|
|
}
|
|
|
|
func TestPathMatcher_Filter_Create(t *testing.T) {
|
|
testCases := map[string]struct {
|
|
path []string
|
|
matches []string
|
|
modifiedNodeMustContain string
|
|
create yaml.Kind
|
|
expectErr string
|
|
}{
|
|
"create non-primitive sequence item that does not exist": {
|
|
path: []string{"spec", "template", "spec", "containers", "[name=please-create-me]"},
|
|
matches: []string{
|
|
"name: please-create-me\n",
|
|
},
|
|
modifiedNodeMustContain: "- name: please-create-me",
|
|
create: yaml.MappingNode,
|
|
},
|
|
"create non-primitive item in empty sequence by index": {
|
|
path: []string{"spec", "template", "spec", "containers", "[name=nginx]", "envFrom", "0"},
|
|
matches: []string{"{}\n"},
|
|
modifiedNodeMustContain: "envFrom:\n - {}\n",
|
|
create: yaml.MappingNode,
|
|
},
|
|
"create primitive item in empty sequence by index": {
|
|
path: []string{"spec", "template", "spec", "containers", "[name=sidecar]", "args", "0"},
|
|
matches: []string{"\n"},
|
|
modifiedNodeMustContain: "args:\n -\n",
|
|
create: yaml.ScalarNode,
|
|
},
|
|
"append primitive item to sequence by index": {
|
|
path: []string{"spec", "template", "spec", "containers", "[name=nginx]", "args", "2"},
|
|
matches: []string{"\n"},
|
|
create: yaml.ScalarNode,
|
|
modifiedNodeMustContain: "args: [-c, conf.yaml, '']",
|
|
},
|
|
"append non-primitive item to sequence by index": {
|
|
path: []string{"spec", "template", "spec", "containers", "[name=nginx]", "ports", "1"},
|
|
matches: []string{"{}\n"},
|
|
modifiedNodeMustContain: "ports: [{containerPort: 80}, {}]",
|
|
create: yaml.MappingNode,
|
|
},
|
|
"appending non-primitive element in middle of sequence": {
|
|
path: []string{"spec", "template", "spec", "containers", "2", "imagePullPolicy"},
|
|
matches: []string{"\n"},
|
|
create: yaml.ScalarNode,
|
|
modifiedNodeMustContain: "\n - imagePullPolicy:\n",
|
|
},
|
|
"fail to create non-primitive item by non-zero index in created sequence": {
|
|
path: []string{"spec", "template", "spec", "containers", "[name=nginx]", "envFrom", "1"},
|
|
matches: []string{},
|
|
create: yaml.MappingNode,
|
|
expectErr: "index 1 specified but only 0 elements found",
|
|
},
|
|
"fail to create primitive item by non-zero index in created sequence": {
|
|
path: []string{"spec", "template", "spec", "containers", "[name=sidecar]", "args", "1"},
|
|
matches: []string{},
|
|
create: yaml.ScalarNode,
|
|
expectErr: "index 1 specified but only 0 elements found",
|
|
},
|
|
"fail to create non-primitive item by distant index in existing sequence": {
|
|
path: []string{"spec", "template", "spec", "containers", "3"},
|
|
matches: []string{},
|
|
create: yaml.MappingNode,
|
|
expectErr: "index 3 specified but only 2 elements found",
|
|
},
|
|
"fail to create primitive item by distant index in existing sequence": {
|
|
path: []string{"spec", "template", "spec", "containers", "[name=nginx]", "args", "3"},
|
|
matches: []string{},
|
|
create: yaml.ScalarNode,
|
|
expectErr: "index 3 specified but only 2 elements found",
|
|
},
|
|
"create primitive sequence item that does not exist": {
|
|
path: []string{"metadata", "finalizers", "[=create-me]"},
|
|
matches: []string{
|
|
"create-me\n",
|
|
},
|
|
modifiedNodeMustContain: "finalizers:\n - create-me\n",
|
|
create: yaml.ScalarNode,
|
|
},
|
|
"create series of maps that do not exist": {
|
|
path: []string{"spec", "selector", "matchLabels", "does-not-exist"},
|
|
matches: []string{
|
|
"{}\n",
|
|
},
|
|
modifiedNodeMustContain: "selector:\n matchLabels:\n app: nginx\n does-not-exist: {}\n",
|
|
create: yaml.MappingNode,
|
|
},
|
|
"create scalar below series of maps and sequences that do not exist": {
|
|
path: []string{"spec", "template", "spec", "containers", "[name=please-create-me]", "env", "[key=please-create-me]", "value"},
|
|
matches: []string{
|
|
"\n",
|
|
},
|
|
modifiedNodeMustContain: "- name: please-create-me\n env:\n - key: please-create-me\n value:\n",
|
|
create: yaml.ScalarNode,
|
|
},
|
|
"find sequence items that already exist": {
|
|
path: []string{"spec", "template", "spec", "containers", "[name=.*]"},
|
|
matches: []string{
|
|
"name: nginx\nimage: nginx:1.7.9\nargs: [-c, conf.yaml]\nports: [{containerPort: 80}]\nenv:\n- key: CONTAINER_NAME\n value: nginx\n",
|
|
"name: sidecar\nimage: sidecar:1.0.0\nports:\n- containerPort: 8081\n- containerPort: 9090\n",
|
|
},
|
|
create: yaml.MappingNode,
|
|
},
|
|
"find and create sequence below wildcard that exists on some sequence items": {
|
|
path: []string{"spec", "template", "spec", "containers", "[name=.*]", "env"},
|
|
matches: []string{
|
|
"- key: CONTAINER_NAME\n value: nginx\n",
|
|
"[]\n",
|
|
},
|
|
create: yaml.SequenceNode,
|
|
},
|
|
"find field below wildcard that exists on all sequence items": {
|
|
path: []string{"spec", "template", "spec", "containers", "[name=.*]", "ports"},
|
|
matches: []string{
|
|
"[{containerPort: 80}]\n",
|
|
"- containerPort: 8081\n- containerPort: 9090\n",
|
|
},
|
|
create: yaml.SequenceNode,
|
|
},
|
|
"find field below query that targets a specific item": {
|
|
path: []string{"spec", "template", "spec", "containers", "[name=nginx]", "env"},
|
|
matches: []string{
|
|
"- key: CONTAINER_NAME\n value: nginx\n",
|
|
},
|
|
create: yaml.SequenceNode,
|
|
},
|
|
"create field below query that targets any value of a field that does not exist": {
|
|
path: []string{"spec", "template", "spec", "containers", "[foo=.*]", "env"},
|
|
matches: []string{
|
|
"[]\n",
|
|
},
|
|
// This is kinda weird. The query doesn't match anything, and we can't tell that it is a
|
|
// wildcard rather than a literal, so we use the value to create the field.
|
|
modifiedNodeMustContain: "- foo: .*\n env: []\n",
|
|
create: yaml.SequenceNode,
|
|
},
|
|
}
|
|
nodeStr := `apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: nginx-deployment
|
|
labels:
|
|
app: nginx
|
|
spec:
|
|
replicas: 3
|
|
selector:
|
|
matchLabels:
|
|
app: nginx
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: nginx
|
|
spec:
|
|
containers:
|
|
- name: nginx
|
|
image: nginx:1.7.9
|
|
args: [-c, conf.yaml]
|
|
ports: [{containerPort: 80}]
|
|
env:
|
|
- key: CONTAINER_NAME
|
|
value: nginx
|
|
- name: sidecar
|
|
image: sidecar:1.0.0
|
|
ports:
|
|
- containerPort: 8081
|
|
- containerPort: 9090
|
|
`
|
|
for name, tc := range testCases {
|
|
t.Run(name, func(t *testing.T) {
|
|
node := MustParse(nodeStr)
|
|
result, err := node.Pipe(&PathMatcher{Path: tc.path, Create: tc.create})
|
|
if tc.expectErr != "" {
|
|
require.EqualError(t, err, tc.expectErr)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
matches, err := result.Elements()
|
|
require.NoError(t, err)
|
|
require.Equalf(t, len(tc.matches), len(matches), "Full sequence wrapper of result:\n%s", result.MustString())
|
|
|
|
modifiedNode := node.MustString()
|
|
for i, expected := range tc.matches {
|
|
assert.Equal(t, tc.create, matches[i].YNode().Kind)
|
|
assert.Equal(t, expected, matches[i].MustString())
|
|
assert.Contains(t, modifiedNode, tc.modifiedNodeMustContain)
|
|
}
|
|
})
|
|
}
|
|
}
|