kubelatte-ce

Форк
2
Форк от sbertech/kubelatte-ce
353 строки · 9.5 Кб
1
package location
2

3
import (
4
	"errors"
5
	"fmt"
6
	errors2 "github.com/pkg/errors"
7
	"reflect"
8
	"regexp"
9
	"strings"
10
)
11

12
var ErrWrongLocationPath = errors.New("wrong location path")
13
var ErrYamlStructure = errors.New("yaml content error")
14

15
type NodeType int
16

17
const (
18
	SimplePath NodeType = iota
19
	GenericFilter
20
	NameFilter
21
	ValueFilter
22
)
23

24
type Node struct {
25
	nodeType NodeType
26
	path     string
27
	filter   *filter
28
	child    *Node
29
}
30

31
type filter struct {
32
	name  string
33
	value string
34
}
35

36
type Rules struct {
37
	ValueFilter *regexp.Regexp
38
	Filter      *regexp.Regexp
39
	Generic     *regexp.Regexp
40
	Validation  *regexp.Regexp
41
	Location    *regexp.Regexp
42
}
43

44
func initRules() {
45
	rules = &Rules{
46
		ValueFilter: regexp.MustCompile("[a-zA-Z0-9-_]+\\[@\\.[a-zA-Z0-9-_]+\\s*==\\s*`[a-zA-Z0-9-_]+`\\]"),
47
		Filter:      regexp.MustCompile("[a-zA-Z0-9-_]+\\[@\\.[a-zA-Z0-9-_]+\\]"),
48
		Generic:     regexp.MustCompile("[a-zA-Z0-9-_]+\\[\\]"),
49
		Validation:  regexp.MustCompile("^([a-zA-Z0-9-_]+(\\[(@\\.[a-zA-Z0-9-_]+(\\s*==\\s*`[a-zA-Z0-9-_]+`)?)?\\](\\.|$))?\\.?)+"),
50
		Location:    regexp.MustCompile("[a-zA-Z0-9-_]+(\\[\\]|\\[@\\.[a-zA-Z0-9-_]+(\\s*==\\s*`[a-zA-Z0-9-_]+`)?\\])?\\.?"),
51
	}
52
}
53

54
var rules *Rules
55

56
func ValidateLocation(path string) error {
57
	if rules == nil {
58
		initRules()
59
	}
60

61
	findString := rules.Validation.FindString(path)
62
	if findString != path {
63
		return fmt.Errorf("%s: '%s'; an incorrect character was found '%s', starting from the first",
64
			ErrWrongLocationPath.Error(), path, strings.ReplaceAll(path, findString, ""))
65
	}
66
	return nil
67
}
68

69
func CompilePathToLocation(path string) (*Node, error) {
70
	if path == "" {
71
		return nil, errors2.Wrap(ErrWrongLocationPath, "the path is empty")
72
	}
73

74
	node := &Node{}
75
	head := node
76

77
	if err := ValidateLocation(path); err != nil {
78
		return nil, err
79
	}
80

81
	for _, part := range rules.Location.FindAllString(path, -1) {
82
		if res := rules.Location.FindString(part); res == "" {
83
			return nil, ErrWrongLocationPath
84
		}
85
		if !strings.Contains(part, "[") {
86
			node.path = strings.ReplaceAll(part, ".", "")
87
			node.nodeType = SimplePath
88
		}
89
		if res, _ := regexp.MatchString(rules.ValueFilter.String(), part); res {
90
			node.nodeType = ValueFilter
91
			vals := strings.Split(part, "[")
92
			node.path = vals[0]
93
			filterVals := strings.Split(vals[1], "==") // quick fix, latter change to regex
94
			filterVals[0] = strings.TrimSpace(filterVals[0])
95
			filterVals[1] = strings.TrimSpace(filterVals[1])
96
			node.filter = &filter{
97
				name:  filterVals[0][2:],
98
				value: filterVals[1][1 : strings.Index(filterVals[1][1:], "`")+1],
99
			}
100
		}
101
		if res, _ := regexp.MatchString(rules.Filter.String(), part); res {
102
			node.nodeType = NameFilter
103
			vals := strings.Split(part, "[")
104
			if vals[1][len(vals[1])-1] == '.' {
105
				vals[1] = vals[1][:len(vals[1])-1]
106
			}
107
			node.path = vals[0]
108
			node.filter = &filter{name: vals[1][2 : len(vals[1])-1]}
109
		}
110
		if res, _ := regexp.MatchString(rules.Generic.String(), part); res {
111
			node.nodeType = GenericFilter
112
			vals := strings.Split(part, "[")
113
			node.path = vals[0][:len(vals[0])]
114
		}
115

116
		newNode := &Node{}
117
		node.child = newNode
118
		node = newNode
119
	}
120
	return head, nil
121
}
122

123
//nolint:gocyclo
124
//nolint:funlen
125
//nolint:gocognit
126

127
// MergeByLocation создает мапу с содержимым replacement по пути location.path
128
//
129
//	если элемент последний и он обернут в путь (такое может быть, если темплейт был не yaml)
130
//	входящий файл: - name: test - это не ямл, будет обернут в последний элемент location, например:
131
//	containers:
132
//	  - name: test
133
func (l *Node) MergeByLocation(reference interface{}, replacement map[string]interface{}, location *Node) (map[string]interface{}, error) {
134
	var err error
135
	if location.path == "" {
136
		return replacement, nil
137
	}
138

139
	mapReference := reference.(map[string]interface{})[location.path]
140
	target := make(map[string]interface{})
141

142
	switch location.nodeType {
143
	case SimplePath:
144
		target[location.path] = make(map[string]interface{})
145

146
		if _, ok := replacement[location.path]; ok && location.child.path == "" && len(replacement) == 1 {
147
			target[location.path] = replacement[location.path]
148
			return target, nil
149
		}
150
		target[location.path], err = l.MergeByLocation(mapReference, replacement, location.child)
151
		if err != nil {
152
			return nil, err
153
		}
154
	case GenericFilter:
155

156
		target[location.path], err = l.mergeFilter(mapReference, replacement, location, func(target any, location *Node) (bool, error) {
157
			return true, nil
158
		})
159
		if err != nil {
160
			return nil, err
161
		}
162

163
	case NameFilter:
164
		target[location.path], err = l.mergeFilter(mapReference, replacement, location, checkField)
165
		if err != nil {
166
			return nil, err
167
		}
168
	case ValueFilter:
169
		target[location.path], err = l.mergeFilter(mapReference, replacement, location, checkValue)
170
		if err != nil {
171
			return nil, err
172
		}
173
	}
174

175
	return target, nil
176
}
177

178
//nolint:funlen
179
//nolint:gocognit
180
func (l *Node) ReplaceByLocation(target any, replacement map[string]interface{}, location *Node) (map[string]interface{}, error) {
181
	var err error
182
	if location.path == "" {
183
		return replacement, nil
184
	}
185

186
	targetMap, ok := target.(map[string]interface{})
187
	if !ok {
188
		return nil, errors2.Wrap(ErrYamlStructure, fmt.Sprintf("can't replace \"%q\" is not map", location.path))
189
	}
190
	switch location.nodeType {
191
	case SimplePath:
192
		if _, ok := targetMap[location.path]; !ok {
193
			targetMap[location.path] = make(map[string]interface{})
194
		}
195
		if _, ok := targetMap[location.path].(map[string]interface{}); !ok {
196
			targetMap[location.path] = make(map[string]interface{})
197
		}
198
		if _, ok := replacement[location.path]; ok && location.child.path == "" && len(replacement) == 1 {
199
			targetMap[location.path] = replacement[location.path]
200
			return targetMap, nil
201
		}
202
		targetMap[location.path], err = l.ReplaceByLocation(targetMap[location.path].(map[string]interface{}), replacement, location.child)
203

204
	case GenericFilter:
205
		targetMap[location.path], err = l.replaceFilter(targetMap[location.path], replacement, location, func(target any, node *Node) (bool, error) {
206
			return true, nil
207
		})
208

209
	case NameFilter:
210
		targetMap[location.path], err = l.replaceFilter(targetMap[location.path], replacement, location, checkField)
211
		if err != nil {
212
			return nil, err
213
		}
214

215
	case ValueFilter:
216
		targetMap[location.path], err = l.replaceFilter(targetMap[location.path], replacement, location, checkValue)
217
		if err != nil {
218
			return nil, err
219
		}
220
	}
221

222
	return targetMap, err
223
}
224

225
func checkField(target any, location *Node) (bool, error) {
226
	targetMap, ok := target.(map[string]interface{})
227
	if !ok {
228
		return false, errors2.Wrap(ErrWrongLocationPath, fmt.Sprintf("can't filter \"%q\" is not map", location.path))
229
	}
230
	if _, ok = targetMap[location.filter.name]; !ok {
231
		return false, nil
232
	}
233
	return true, nil
234
}
235

236
func checkValue(target any, location *Node) (bool, error) {
237
	ok, err := checkField(target, location)
238
	if err != nil {
239
		return false, err
240
	}
241
	if ok {
242
		if target.(map[string]interface{})[location.filter.name] == location.filter.value {
243
			return true, nil
244
		}
245
	}
246

247
	return false, nil
248
}
249

250
func (l *Node) replaceFilter(target any, replacement map[string]interface{}, location *Node, filterFunc func(target any, location *Node) (bool, error)) (any, error) {
251

252
	switch v := reflect.ValueOf(target); v.Kind() {
253

254
	case reflect.Slice | reflect.Array:
255
		arr, ok := target.([]interface{})
256
		if !ok {
257
			return nil, ErrYamlStructure
258
		}
259
		for i := 0; i < len(arr); i++ {
260
			ok, err := filterFunc(arr[i], location)
261
			if err != nil {
262
				return nil, err
263
			}
264
			if ok {
265
				arr[i], err = l.ReplaceByLocation(arr[i], replacement, location.child)
266
				if err != nil {
267
					return nil, err
268
				}
269
			}
270
		}
271
	case reflect.Map:
272
		iter, ok := target.(map[string]any)
273
		if !ok {
274
			return nil, ErrYamlStructure
275
		}
276
		for key := range iter {
277
			ok, err := filterFunc(iter[key], location)
278
			if err != nil {
279
				return nil, err
280
			}
281
			if ok {
282
				iter[key], err = l.ReplaceByLocation(iter[key], replacement, location.child)
283
				if err != nil {
284
					return nil, err
285
				}
286
			}
287
		}
288
	default:
289
		return nil, errors2.Wrap(ErrWrongLocationPath, fmt.Sprintf("can't filter got unexpected type at \"%q\" ", location.path))
290
	}
291

292
	return target, nil
293
}
294

295
func (l *Node) mergeFilter(reference any, replacement map[string]interface{}, location *Node, filterFunc func(target any, location *Node) (bool, error)) (any, error) {
296

297
	switch v := reflect.ValueOf(reference); v.Kind() {
298

299
	case reflect.Slice | reflect.Array:
300
		arr := reference.([]interface{})
301
		target := make([]interface{}, 0, len(arr))
302
		for _, el := range arr {
303
			yaml, ok := el.(map[string]interface{})
304
			if !ok {
305
				return nil, ErrYamlStructure
306
			}
307
			ok, err := filterFunc(el, location)
308
			if err != nil {
309
				return nil, err
310
			}
311
			if ok {
312
				res, _ := l.MergeByLocation(yaml, replacement, location.child)
313
				if _, ok := res["name"]; !ok {
314
					mapEl, ok := el.(map[string]interface{})
315
					if ok {
316
						if name, ok := mapEl["name"]; ok {
317
							res["name"] = name
318
						}
319
					}
320
				}
321

322
				target = append(target, res)
323
			}
324
		}
325
		return target, nil
326
	case reflect.Map:
327
		iter := reference.(map[string]any)
328
		target := make(map[string]any)
329
		for key := range iter {
330
			ok, err := filterFunc(iter[key], location)
331
			if err != nil {
332
				return nil, err
333
			}
334
			if ok {
335
				res, _ := l.MergeByLocation(iter[key], replacement, location.child)
336
				target[key] = res
337
			}
338
		}
339
		return target, nil
340

341
	default:
342
		return nil, errors2.Wrap(ErrWrongLocationPath, fmt.Sprintf("can't filter got unexpected type at \"%q\" ", location.path))
343
	}
344

345
}
346

347
func (l *Node) GetLastItem() string {
348
	var lastPath string
349
	for last := l; last.child != nil; last = last.child {
350
		lastPath = last.path
351
	}
352
	return lastPath
353
}
354

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.