1
2
3 package normalizer
4
5 import (
6 "fmt"
7 "sort"
8 "strings"
9 "sync"
10
11 "sigs.k8s.io/kustomize/kyaml/kio/filters"
12 "sigs.k8s.io/kustomize/kyaml/kio/kioutil"
13 "sigs.k8s.io/kustomize/kyaml/yaml"
14
15 "edge-infra.dev/pkg/edge/gitops/fn/v1alpha1"
16 )
17
18
19
20
21
22 var x = struct {
23 sync.RWMutex
24
25
26 ff filters.FormatFilter
27
28
29
30
31
32
33
34 wlsApis map[string]interface{}
35 wlsKinds map[string]interface{}
36 wlsFields map[string]string
37 }{
38 wlsApis: yaml.WhitelistedListSortApis,
39 wlsKinds: yaml.WhitelistedListSortKinds,
40 wlsFields: yaml.WhitelistedListSortFields,
41 }
42
43 func init() {
44 x.Lock()
45 defer x.Unlock()
46 const skey = "secretKey"
47 for _, key := range yaml.AssociativeSequenceKeys {
48 if key == skey {
49
50 return
51 }
52 }
53 yaml.AssociativeSequenceKeys = append(yaml.AssociativeSequenceKeys, skey)
54 }
55
56
57
58
59
60 func setWhitelistedListSortMaps(nodes []*yaml.RNode) error {
61
62 const msg = "WhitelistedListSortFields contains key %q with value %q, but another yaml document contains conflicting value %q for that key."
63 var (
64 apis = make(map[string]bool)
65 kinds = make(map[string]bool)
66 fields = make(map[string]string)
67 fieldErrs *conflictingWhitelistedListSortFieldsError
68
69
70 hasApis = true
71 hasKinds = true
72 hasFields = true
73 )
74
75 for _, node := range nodes {
76 meta, err := node.GetMeta()
77 if err != nil {
78 return err
79 }
80 apis[meta.TypeMeta.APIVersion] = true
81 kinds[meta.TypeMeta.Kind] = true
82 findSequenceNodeFieldsRecursive(node.YNode(), "", fields)
83 }
84
85
86 x.RLock()
87 for api := range apis {
88 if _, found := x.wlsApis[api]; !found {
89 hasApis = false
90 }
91 }
92 for kind := range kinds {
93 if _, found := x.wlsKinds[kind]; !found {
94 hasKinds = false
95 }
96 }
97 for field, value := range fields {
98 if existing, found := x.wlsFields[field]; !found {
99 hasFields = false
100 } else if value != existing {
101 var info = fmt.Sprintf(msg, field, existing, value)
102 if fieldErrs == nil {
103 fieldErrs = &conflictingWhitelistedListSortFieldsError{
104 info: make(map[string]interface{}),
105 }
106 }
107 fieldErrs.info[info] = nil
108 }
109 }
110 x.RUnlock()
111
112
113 if fieldErrs != nil {
114 return fieldErrs
115 } else if hasApis && hasKinds && hasFields {
116 return nil
117 }
118
119
120
121
122
123 x.Lock()
124 defer x.Unlock()
125
126 for api := range apis {
127 x.wlsApis[api] = nil
128 }
129 for kind := range kinds {
130 x.wlsKinds[kind] = nil
131 }
132 for field, value := range fields {
133 if existing, found := x.wlsFields[field]; !found {
134 x.wlsFields[field] = value
135 } else if value != existing {
136
137 var info = fmt.Sprintf(msg, field, existing, value)
138 if fieldErrs == nil {
139 fieldErrs = &conflictingWhitelistedListSortFieldsError{
140 info: make(map[string]interface{}),
141 }
142 }
143 fieldErrs.info[info] = nil
144 }
145 }
146 if fieldErrs != nil {
147 return fieldErrs
148 }
149 return nil
150 }
151
152
153
154
155 func findSequenceNodeFieldsRecursive(node *yaml.Node, path string, fields map[string]string) {
156 switch node.Kind {
157 case yaml.SequenceNode:
158
159
160
161 if v := yaml.NewRNode(node).GetAssociativeKey(); v != "" {
162 fields[path] = v
163 }
164 case yaml.MappingNode:
165
166
167
168
169
170 for i := range node.Content {
171 if i%2 != 0 {
172
173 continue
174 }
175 var fieldName = node.Content[i].Value
176
177 var nextPath = fmt.Sprintf("%s.%s", path, fieldName)
178
179 findSequenceNodeFieldsRecursive(node.Content[i+1], nextPath, fields)
180 }
181 }
182 }
183
184
185
186
187
188
189
190
191
192 func Normalize(input []*yaml.RNode) error {
193 err := setWhitelistedListSortMaps(input)
194 if err != nil {
195 return err
196 }
197
198
199 x.RLock()
200 _, err = x.ff.Filter(input)
201 x.RUnlock()
202 if err != nil {
203 return err
204 }
205
206
207 sortRNodes(input)
208
209
210
211
212
213
214
215 quoteCPULimitsForNodes(input)
216 return nil
217 }
218
219 func quoteCPULimitsForNodes(input []*yaml.RNode) {
220 for i := range input {
221 quoteCPULimits(input[i].YNode())
222 }
223 }
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254 func quoteCPULimits(node *yaml.Node) {
255 if node.Kind == yaml.ScalarNode {
256 return
257 }
258 for i := range node.Content {
259 if isKey("limits", node.Content, i) {
260
261 limits := node.Content[i+1]
262 for j := range limits.Content {
263 if isKey("cpu", limits.Content, j) {
264 limits.Content[j+1].Style = yaml.DoubleQuotedStyle
265 }
266 }
267 } else {
268 quoteCPULimits(node.Content[i])
269 }
270 }
271 }
272
273 func isKey(key string, nodes []*yaml.Node, idx int) bool {
274 if idx >= len(nodes)-1 {
275 return false
276 }
277 if nodes[idx].Value != key {
278 return false
279 }
280 return true
281 }
282
283
284
285
286 func (n *Normalizer) Run(input []*yaml.RNode) (output []*yaml.RNode, err error) {
287 err = Normalize(input)
288 if err != nil {
289 return
290 }
291 output = input
292 return
293 }
294
295
296
297
298 func sortRNodes(nodes []*yaml.RNode) {
299
300 sort.Stable(&rnodeSorter{n: nodes})
301 }
302
303
304 type rnodeSorter struct {
305
306 s []string
307
308
309 p []string
310
311
312 l []bool
313
314 n []*yaml.RNode
315 }
316
317 var apiVersionPrefixFnsEdgeNcrCom = fmt.Sprintf("%s/", v1alpha1.GroupVersion.Group)
318
319
320 func lexiographicallySortAPIVersion(apiVersion string) bool {
321 return !strings.HasPrefix(apiVersion, apiVersionPrefixFnsEdgeNcrCom)
322 }
323
324
325 func (s *rnodeSorter) Len() int {
326 s.s = make([]string, len(s.n))
327 s.p = make([]string, len(s.n))
328 s.l = make([]bool, len(s.n))
329 for k, n := range s.n {
330 meta, err := n.GetMeta()
331 if err != nil {
332 s.l[k] = true
333 continue
334 }
335 s.p[k] = meta.ObjectMeta.Annotations[kioutil.PathAnnotation]
336 s.l[k] = lexiographicallySortAPIVersion(meta.TypeMeta.APIVersion)
337 }
338
339 return len(s.n)
340 }
341
342
343 func (s *rnodeSorter) Less(i, j int) bool {
344
345 if s.p[i] != s.p[j] {
346 return s.p[i] < s.p[j]
347 }
348
349
350 if !s.l[i] && !s.l[j] {
351
352 return false
353 }
354
355
356 if s.s[i] == "" {
357 s.s[i], _ = s.n[i].String()
358 }
359 if s.s[j] == "" {
360 s.s[j], _ = s.n[j].String()
361 }
362 return s.s[i] < s.s[j]
363 }
364
365 func (s *rnodeSorter) Swap(i, j int) {
366 s.s[i], s.s[j] = s.s[j], s.s[i]
367 s.p[i], s.p[j] = s.p[j], s.p[i]
368 s.l[i], s.l[j] = s.l[j], s.l[i]
369 s.n[i], s.n[j] = s.n[j], s.n[i]
370 }
371
372
373 type conflictingWhitelistedListSortFieldsError struct {
374
375 info map[string]interface{}
376 }
377
378 func (err *conflictingWhitelistedListSortFieldsError) Error() string {
379 const msg = "yaml.WhitelistedListSortFields does not support conflicting values for the same field."
380 if len(err.info) == 0 {
381 return msg
382 }
383
384 var s = []interface{}{msg}
385 for info := range err.info {
386 s = append(s, info)
387 }
388 return fmt.Sprint(s...)
389 }
390
391
392
393
394
395
396
397
398 func IsConflictingWhitelistedListSortFieldsError(err error) bool {
399 _, b := err.(*conflictingWhitelistedListSortFieldsError)
400 return b
401 }
402
View as plain text