1
2
3
4 package order
5
6 import (
7 "bytes"
8 "strings"
9 "testing"
10
11 "github.com/stretchr/testify/assert"
12 "sigs.k8s.io/kustomize/kyaml/kio"
13 "sigs.k8s.io/kustomize/kyaml/yaml"
14 )
15
16 func TestSyncOrder(t *testing.T) {
17 testCases := []struct {
18 name string
19 from string
20 to string
21 expected string
22 }{
23 {
24 name: "sort data fields configmap with comments",
25 from: `apiVersion: v1
26 kind: ConfigMap
27 metadata:
28 name: setters-config
29 data:
30 # This should be the name of your Config Controller instance
31 cluster-name: cluster-name
32 # This should be the project where you deployed Config Controller
33 project-id: project-id # pro
34 project-number: "1234567890123"
35 # You can leave these defaults
36 namespace: config-control
37 deployment-repo: deployment-repo
38 source-repo: source-repo
39 `,
40 to: `apiVersion: v1
41 kind: ConfigMap
42 metadata: # kpt-merge: /setters-config
43 name: setters-config
44 data:
45 # You can leave these defaults
46 namespace: config-control
47 # This should be the name of your Config Controller instance
48 cluster-name: cluster-name
49 deployment-repo: deployment-repo
50 # This should be the project where you deployed Config Controller
51 project-id: project-id # project
52 project-number: "1234567890123"
53 source-repo: source-repo
54 `,
55 expected: `apiVersion: v1
56 kind: ConfigMap
57 metadata: # kpt-merge: /setters-config
58 name: setters-config
59 data:
60 # This should be the name of your Config Controller instance
61 cluster-name: cluster-name
62 # This should be the project where you deployed Config Controller
63 project-id: project-id # project
64 project-number: "1234567890123"
65 # You can leave these defaults
66 namespace: config-control
67 deployment-repo: deployment-repo
68 source-repo: source-repo
69 `,
70 },
71 {
72 name: "sort data fields configmap but retain order of extra fields",
73 from: `apiVersion: v1
74 kind: ConfigMap
75 data:
76 baz: bar
77 cluster-name: cluster-name
78 foo: config-control
79 `,
80 to: `kind: ConfigMap
81 apiVersion: v1
82 metadata:
83 name: foo
84 data:
85 color: orange
86 foo: config-control
87 abc: def
88 cluster-name: cluster-name
89 `,
90 expected: `apiVersion: v1
91 kind: ConfigMap
92 data:
93 cluster-name: cluster-name
94 foo: config-control
95 color: orange
96 abc: def
97 metadata:
98 name: foo
99 `,
100 },
101 {
102 name: "sort containers list node with sequence head comments preservation",
103 from: `apiVersion: apps/v1
104 kind: Deployment
105 metadata:
106 name: before
107 spec:
108 containers:
109 - name: nginx
110 # nginx image
111 image: "nginx:1.16.1"
112 ports:
113 - protocol: TCP # tcp protocol
114 containerPort: 80
115 `,
116 to: `apiVersion: apps/v1
117 kind: Deployment
118 metadata:
119 name: after
120 spec:
121 containers:
122 # ports comment
123 - ports:
124 - containerPort: 80
125 protocol: TCP # tcp protocol
126 # nginx image
127 image: "nginx:1.16.2"
128 # nginx container
129 name: nginx
130 # end of resource
131 `,
132 expected: `apiVersion: apps/v1
133 kind: Deployment
134 metadata:
135 name: after
136 spec:
137 containers:
138 # ports comment
139 # nginx container
140 - name: nginx
141 # nginx image
142 image: "nginx:1.16.2"
143 ports:
144 - protocol: TCP # tcp protocol
145 containerPort: 80
146 # end of resource
147 `,
148 },
149 {
150 name: "Do not alter sequence order",
151 from: `apiVersion: v1
152 kind: KRMFile
153 metadata:
154 name: before
155 pipeline:
156 mutators:
157 - image: apply-setters:v0.1
158 configPath: setters.yaml
159 - image: set-namespace:v0.1
160 configPath: ns.yaml
161 `,
162 to: `apiVersion: v1
163 kind: KRMFile
164 metadata:
165 name: after
166 pipeline:
167 mutators:
168 - configPath: sr.yaml
169 image: search-replace:v0.1
170 - image: apply-setters:v0.1
171 configPath: setters.yaml
172 - image: set-namespace:v0.1
173 configPath: ns.yaml
174 `,
175 expected: `apiVersion: v1
176 kind: KRMFile
177 metadata:
178 name: after
179 pipeline:
180 mutators:
181 - image: search-replace:v0.1
182 configPath: sr.yaml
183 - image: apply-setters:v0.1
184 configPath: setters.yaml
185 - image: set-namespace:v0.1
186 configPath: ns.yaml
187 `,
188 },
189 {
190 name: "sort fields with null value",
191 from: `apiVersion: v1
192 kind: ConfigMap
193 metadata:
194 creationTimestamp: null
195 name: workspaces.app.terraform.io
196 `,
197 to: `apiVersion: v1
198 kind: ConfigMap
199 metadata:
200 name: workspaces.app.terraform.io
201 creationTimestamp: null
202 `,
203 expected: `apiVersion: v1
204 kind: ConfigMap
205 metadata:
206 creationTimestamp: null
207 name: workspaces.app.terraform.io
208 `,
209 },
210 {
211 name: "Complex ASM reorder example",
212 from: `apiVersion: apiextensions.k8s.io/v1beta1
213 kind: CustomResourceDefinition
214 metadata:
215 annotations:
216 controller-gen.kubebuilder.io/version: (unknown)
217 creationTimestamp: null
218 name: controlplanerevisions.mesh.cloud.google.com
219 spec:
220 group: mesh.cloud.google.com
221 names:
222 kind: ControlPlaneRevision
223 listKind: ControlPlaneRevisionList
224 plural: controlplanerevisions
225 singular: controlplanerevision
226 scope: Namespaced
227 subresources:
228 status: {}
229 validation:
230 openAPIV3Schema:
231 description: ControlPlaneRevision is the Schema for the ControlPlaneRevision API
232 properties:
233 apiVersion:
234 description: 'APIVersion'
235 type: string
236 kind:
237 description: 'Kind'
238 type: string
239 metadata:
240 type: object
241 spec:
242 description: ControlPlaneRevisionSpec defines the desired state of ControlPlaneRevision
243 properties:
244 channel:
245 description: ReleaseChannel determines the aggressiveness of upgrades.
246 enum:
247 - regular
248 - rapid
249 - stable
250 type: string
251 type:
252 description: ControlPlaneRevisionType determines how the revision should be managed.
253 enum:
254 - managed_service
255 type: string
256 type: object
257 status:
258 description: ControlPlaneRevisionStatus defines the observed state of ControlPlaneRevision.
259 properties:
260 conditions:
261 items:
262 description: ControlPlaneRevisionCondition is a repeated struct defining the current conditions of a ControlPlaneRevision.
263 properties:
264 lastTransitionTime:
265 description: Last time the condition transitioned from one status to another
266 format: date-time
267 type: string
268 message:
269 description: Human-readable message indicating details about last transition
270 type: string
271 reason:
272 description: Unique, one-word, CamelCase reason for the condition's last transition
273 type: string
274 status:
275 description: Status is the status of the condition. Can be True, False, or Unknown.
276 type: string
277 type:
278 description: Type is the type of the condition.
279 type: string
280 type: object
281 type: array
282 type: object
283 type: object
284 version: v1alpha1
285 versions:
286 - name: v1alpha1
287 served: true
288 storage: true
289 status:
290 acceptedNames:
291 kind: ""
292 plural: ""
293 conditions: []
294 storedVersions: []
295 `,
296 to: `apiVersion: apiextensions.k8s.io/v1beta1
297 kind: CustomResourceDefinition
298 metadata:
299 name: controlplanerevisions.mesh.cloud.google.com
300 annotations:
301 controller-gen.kubebuilder.io/version: (unknown)
302 creationTimestamp: null
303 spec:
304 group: mesh.cloud.google.com
305 names:
306 kind: ControlPlaneRevision
307 listKind: ControlPlaneRevisionList
308 plural: controlplanerevisions
309 singular: controlplanerevision
310 scope: Namespaced
311 subresources:
312 status: {}
313 validation:
314 openAPIV3Schema:
315 type: object
316 description: ControlPlaneRevision is the Schema for the ControlPlaneRevision API
317 properties:
318 apiVersion:
319 type: string
320 description: 'APIVersion'
321 kind:
322 type: string
323 description: 'Kind'
324 metadata:
325 type: object
326 spec:
327 type: object
328 description: ControlPlaneRevisionSpec defines the desired state of ControlPlaneRevision
329 properties:
330 type:
331 type: string
332 description: ControlPlaneRevisionType determines how the revision should be managed.
333 enum:
334 - managed_service
335 channel:
336 type: string
337 description: ReleaseChannel determines the aggressiveness of upgrades.
338 enum:
339 - regular
340 - rapid
341 - stable
342 status:
343 type: object
344 description: ControlPlaneRevisionStatus defines the observed state of ControlPlaneRevision.
345 properties:
346 conditions:
347 type: array
348 items:
349 type: object
350 description: ControlPlaneRevisionCondition is a repeated struct defining the current conditions of a ControlPlaneRevision.
351 properties:
352 type:
353 type: string
354 description: Type is the type of the condition.
355 status:
356 type: string
357 description: Status is the status of the condition. Can be True, False, or Unknown.
358 lastTransitionTime:
359 type: string
360 description: Last time the condition transitioned from one status to another
361 format: date-time
362 message:
363 type: string
364 description: Human-readable message indicating details about last transition
365 reason:
366 type: string
367 description: Unique, one-word, CamelCase reason for the condition's last transition
368 version: v1alpha1
369 versions:
370 - name: v1alpha1
371 served: true
372 storage: true
373 status:
374 acceptedNames:
375 kind: ""
376 plural: ""
377 conditions: []
378 storedVersions: []
379 `,
380 expected: `test.from`,
381 },
382 }
383
384 for i := range testCases {
385 tc := testCases[i]
386 t.Run(tc.name, func(t *testing.T) {
387 from, err := yaml.Parse(tc.from)
388 if !assert.NoError(t, err) {
389 t.FailNow()
390 }
391
392 to, err := yaml.Parse(tc.to)
393 if !assert.NoError(t, err) {
394 t.FailNow()
395 }
396
397 err = SyncOrder(from, to)
398 if !assert.NoError(t, err) {
399 t.FailNow()
400 }
401
402 out := &bytes.Buffer{}
403 kio.ByteWriter{
404 Writer: out,
405 KeepReaderAnnotations: false,
406 }.Write([]*yaml.RNode{to})
407
408
409
410 if tc.expected == "test.from" {
411 tc.expected = tc.from
412 }
413
414 if !assert.Equal(t, tc.expected, out.String()) {
415 t.FailNow()
416 }
417
418 actualFrom, err := from.String()
419 if !assert.NoError(t, err) {
420 t.FailNow()
421 }
422
423 if !assert.Equal(t, strings.TrimSpace(tc.from), strings.TrimSpace(actualFrom)) {
424 t.FailNow()
425 }
426 })
427 }
428 }
429
View as plain text