1
2
3
4 package yaml
5
6 import (
7 "fmt"
8 "testing"
9
10 "github.com/stretchr/testify/assert"
11 "github.com/stretchr/testify/require"
12 yaml "sigs.k8s.io/yaml/goyaml.v3"
13 )
14
15 func TestPathMatcher_Filter(t *testing.T) {
16 node := MustParse(`apiVersion: apps/v1
17 kind: Deployment
18 metadata:
19 name: nginx-deployment
20 labels:
21 app: nginx
22 spec:
23 replicas: 3
24 selector:
25 matchLabels:
26 app: nginx
27 template:
28 metadata:
29 labels:
30 app: nginx
31 spec:
32 containers:
33 - name: nginx
34 image: nginx:1.7.9
35 args: [-c, conf.yaml]
36 ports:
37 - containerPort: 80
38 - name: sidecar
39 image: sidecar:1.0.0
40 ports:
41 - containerPort: 8081
42 - containerPort: 9090
43 `)
44
45 updates := []struct {
46 path []string
47 value string
48 }{
49 {[]string{
50 "spec", "template", "spec", "containers", "[name=.*]"},
51 "- name: nginx\n image: nginx:1.7.9\n args: [-c, conf.yaml]\n ports:\n - containerPort: 80\n" +
52 "- name: sidecar\n image: sidecar:1.0.0\n ports:\n - containerPort: 8081\n - containerPort: 9090\n"},
53 {[]string{
54 "spec", "template", "spec", "containers", "[name=.*]", "image"},
55 "- nginx:1.7.9\n- sidecar:1.0.0\n"},
56 {[]string{
57 "spec", "template", "spec", "containers", "[name=n.*]", "image"},
58 "- nginx:1.7.9\n"},
59 {[]string{
60 "spec", "template", "spec", "containers", "[name=s.*]", "image"},
61 "- sidecar:1.0.0\n"},
62 {[]string{
63 "spec", "template", "spec", "containers", "[name=.*x]", "image"},
64 "- nginx:1.7.9\n"},
65 {[]string{
66 "spec", "template", "spec", "containers", "[name=.*]", "ports"},
67 "- - containerPort: 80\n- - containerPort: 8081\n - containerPort: 9090\n"},
68 {[]string{
69 "spec", "template", "spec", "containers", "[name=.*]", "ports", "[containerPort=8.*]"},
70 "- containerPort: 80\n- containerPort: 8081\n"},
71 {[]string{
72 "spec", "template", "spec", "containers", "[name=.*]", "ports", "[containerPort=.*1]"},
73 "- containerPort: 8081\n"},
74 {[]string{
75 "spec", "template", "spec", "containers", "[name=.*]", "ports", "[containerPort=9.*]"},
76 "- containerPort: 9090\n"},
77 {[]string{
78 "spec", "template", "spec", "containers", "[name=s.*]", "ports", "[containerPort=8.*]"},
79 "- containerPort: 8081\n"},
80 {[]string{
81 "spec", "template", "spec", "containers", "[name=s.*]", "ports", "[containerPort=.*2]"},
82 ""},
83 {[]string{
84 "spec", "template", "spec", "containers", "*", "image"},
85 "- nginx:1.7.9\n- sidecar:1.0.0\n"},
86 {[]string{
87 "spec", "template", "spec", "containers", "*", "ports", "*"},
88 "- containerPort: 80\n- containerPort: 8081\n- containerPort: 9090\n"},
89 {[]string{
90 "spec", "template", "spec", "containers", "[name=.*]", "args", "1"},
91 "- conf.yaml\n"},
92 }
93 for i, u := range updates {
94 result, err := node.Pipe(&PathMatcher{Path: u.path})
95 if !assert.NoError(t, err) {
96 return
97 }
98 assert.Equal(t, u.value, result.MustString(), fmt.Sprintf("%d", i))
99 }
100 }
101
102 func TestPathMatcher_Filter_Create(t *testing.T) {
103 testCases := map[string]struct {
104 path []string
105 matches []string
106 modifiedNodeMustContain string
107 create yaml.Kind
108 expectErr string
109 }{
110 "create non-primitive sequence item that does not exist": {
111 path: []string{"spec", "template", "spec", "containers", "[name=please-create-me]"},
112 matches: []string{
113 "name: please-create-me\n",
114 },
115 modifiedNodeMustContain: "- name: please-create-me",
116 create: yaml.MappingNode,
117 },
118 "create non-primitive item in empty sequence by index": {
119 path: []string{"spec", "template", "spec", "containers", "[name=nginx]", "envFrom", "0"},
120 matches: []string{"{}\n"},
121 modifiedNodeMustContain: "envFrom:\n - {}\n",
122 create: yaml.MappingNode,
123 },
124 "create primitive item in empty sequence by index": {
125 path: []string{"spec", "template", "spec", "containers", "[name=sidecar]", "args", "0"},
126 matches: []string{"\n"},
127 modifiedNodeMustContain: "args:\n -\n",
128 create: yaml.ScalarNode,
129 },
130 "append primitive item to sequence by index": {
131 path: []string{"spec", "template", "spec", "containers", "[name=nginx]", "args", "2"},
132 matches: []string{"\n"},
133 create: yaml.ScalarNode,
134 modifiedNodeMustContain: "args: [-c, conf.yaml, '']",
135 },
136 "append non-primitive item to sequence by index": {
137 path: []string{"spec", "template", "spec", "containers", "[name=nginx]", "ports", "1"},
138 matches: []string{"{}\n"},
139 modifiedNodeMustContain: "ports: [{containerPort: 80}, {}]",
140 create: yaml.MappingNode,
141 },
142 "appending non-primitive element in middle of sequence": {
143 path: []string{"spec", "template", "spec", "containers", "2", "imagePullPolicy"},
144 matches: []string{"\n"},
145 create: yaml.ScalarNode,
146 modifiedNodeMustContain: "\n - imagePullPolicy:\n",
147 },
148 "fail to create non-primitive item by non-zero index in created sequence": {
149 path: []string{"spec", "template", "spec", "containers", "[name=nginx]", "envFrom", "1"},
150 matches: []string{},
151 create: yaml.MappingNode,
152 expectErr: "index 1 specified but only 0 elements found",
153 },
154 "fail to create primitive item by non-zero index in created sequence": {
155 path: []string{"spec", "template", "spec", "containers", "[name=sidecar]", "args", "1"},
156 matches: []string{},
157 create: yaml.ScalarNode,
158 expectErr: "index 1 specified but only 0 elements found",
159 },
160 "fail to create non-primitive item by distant index in existing sequence": {
161 path: []string{"spec", "template", "spec", "containers", "3"},
162 matches: []string{},
163 create: yaml.MappingNode,
164 expectErr: "index 3 specified but only 2 elements found",
165 },
166 "fail to create primitive item by distant index in existing sequence": {
167 path: []string{"spec", "template", "spec", "containers", "[name=nginx]", "args", "3"},
168 matches: []string{},
169 create: yaml.ScalarNode,
170 expectErr: "index 3 specified but only 2 elements found",
171 },
172 "create primitive sequence item that does not exist": {
173 path: []string{"metadata", "finalizers", "[=create-me]"},
174 matches: []string{
175 "create-me\n",
176 },
177 modifiedNodeMustContain: "finalizers:\n - create-me\n",
178 create: yaml.ScalarNode,
179 },
180 "create series of maps that do not exist": {
181 path: []string{"spec", "selector", "matchLabels", "does-not-exist"},
182 matches: []string{
183 "{}\n",
184 },
185 modifiedNodeMustContain: "selector:\n matchLabels:\n app: nginx\n does-not-exist: {}\n",
186 create: yaml.MappingNode,
187 },
188 "create scalar below series of maps and sequences that do not exist": {
189 path: []string{"spec", "template", "spec", "containers", "[name=please-create-me]", "env", "[key=please-create-me]", "value"},
190 matches: []string{
191 "\n",
192 },
193 modifiedNodeMustContain: "- name: please-create-me\n env:\n - key: please-create-me\n value:\n",
194 create: yaml.ScalarNode,
195 },
196 "find sequence items that already exist": {
197 path: []string{"spec", "template", "spec", "containers", "[name=.*]"},
198 matches: []string{
199 "name: nginx\nimage: nginx:1.7.9\nargs: [-c, conf.yaml]\nports: [{containerPort: 80}]\nenv:\n- key: CONTAINER_NAME\n value: nginx\n",
200 "name: sidecar\nimage: sidecar:1.0.0\nports:\n- containerPort: 8081\n- containerPort: 9090\n",
201 },
202 create: yaml.MappingNode,
203 },
204 "find and create sequence below wildcard that exists on some sequence items": {
205 path: []string{"spec", "template", "spec", "containers", "[name=.*]", "env"},
206 matches: []string{
207 "- key: CONTAINER_NAME\n value: nginx\n",
208 "[]\n",
209 },
210 create: yaml.SequenceNode,
211 },
212 "find field below wildcard that exists on all sequence items": {
213 path: []string{"spec", "template", "spec", "containers", "[name=.*]", "ports"},
214 matches: []string{
215 "[{containerPort: 80}]\n",
216 "- containerPort: 8081\n- containerPort: 9090\n",
217 },
218 create: yaml.SequenceNode,
219 },
220 "find field below query that targets a specific item": {
221 path: []string{"spec", "template", "spec", "containers", "[name=nginx]", "env"},
222 matches: []string{
223 "- key: CONTAINER_NAME\n value: nginx\n",
224 },
225 create: yaml.SequenceNode,
226 },
227 "create field below query that targets any value of a field that does not exist": {
228 path: []string{"spec", "template", "spec", "containers", "[foo=.*]", "env"},
229 matches: []string{
230 "[]\n",
231 },
232
233
234 modifiedNodeMustContain: "- foo: .*\n env: []\n",
235 create: yaml.SequenceNode,
236 },
237 }
238 nodeStr := `apiVersion: apps/v1
239 kind: Deployment
240 metadata:
241 name: nginx-deployment
242 labels:
243 app: nginx
244 spec:
245 replicas: 3
246 selector:
247 matchLabels:
248 app: nginx
249 template:
250 metadata:
251 labels:
252 app: nginx
253 spec:
254 containers:
255 - name: nginx
256 image: nginx:1.7.9
257 args: [-c, conf.yaml]
258 ports: [{containerPort: 80}]
259 env:
260 - key: CONTAINER_NAME
261 value: nginx
262 - name: sidecar
263 image: sidecar:1.0.0
264 ports:
265 - containerPort: 8081
266 - containerPort: 9090
267 `
268 for name, tc := range testCases {
269 t.Run(name, func(t *testing.T) {
270 node := MustParse(nodeStr)
271 result, err := node.Pipe(&PathMatcher{Path: tc.path, Create: tc.create})
272 if tc.expectErr != "" {
273 require.EqualError(t, err, tc.expectErr)
274 return
275 }
276 require.NoError(t, err)
277 matches, err := result.Elements()
278 require.NoError(t, err)
279 require.Equalf(t, len(tc.matches), len(matches), "Full sequence wrapper of result:\n%s", result.MustString())
280
281 modifiedNode := node.MustString()
282 for i, expected := range tc.matches {
283 assert.Equal(t, tc.create, matches[i].YNode().Kind)
284 assert.Equal(t, expected, matches[i].MustString())
285 assert.Contains(t, modifiedNode, tc.modifiedNodeMustContain)
286 }
287 })
288 }
289 }
290
View as plain text