kubelatte-ce
Форк от sbertech/kubelatte-ce
353 строки · 9.5 Кб
1package location
2
3import (
4"errors"
5"fmt"
6errors2 "github.com/pkg/errors"
7"reflect"
8"regexp"
9"strings"
10)
11
12var ErrWrongLocationPath = errors.New("wrong location path")
13var ErrYamlStructure = errors.New("yaml content error")
14
15type NodeType int
16
17const (
18SimplePath NodeType = iota
19GenericFilter
20NameFilter
21ValueFilter
22)
23
24type Node struct {
25nodeType NodeType
26path string
27filter *filter
28child *Node
29}
30
31type filter struct {
32name string
33value string
34}
35
36type Rules struct {
37ValueFilter *regexp.Regexp
38Filter *regexp.Regexp
39Generic *regexp.Regexp
40Validation *regexp.Regexp
41Location *regexp.Regexp
42}
43
44func initRules() {
45rules = &Rules{
46ValueFilter: regexp.MustCompile("[a-zA-Z0-9-_]+\\[@\\.[a-zA-Z0-9-_]+\\s*==\\s*`[a-zA-Z0-9-_]+`\\]"),
47Filter: regexp.MustCompile("[a-zA-Z0-9-_]+\\[@\\.[a-zA-Z0-9-_]+\\]"),
48Generic: regexp.MustCompile("[a-zA-Z0-9-_]+\\[\\]"),
49Validation: regexp.MustCompile("^([a-zA-Z0-9-_]+(\\[(@\\.[a-zA-Z0-9-_]+(\\s*==\\s*`[a-zA-Z0-9-_]+`)?)?\\](\\.|$))?\\.?)+"),
50Location: regexp.MustCompile("[a-zA-Z0-9-_]+(\\[\\]|\\[@\\.[a-zA-Z0-9-_]+(\\s*==\\s*`[a-zA-Z0-9-_]+`)?\\])?\\.?"),
51}
52}
53
54var rules *Rules
55
56func ValidateLocation(path string) error {
57if rules == nil {
58initRules()
59}
60
61findString := rules.Validation.FindString(path)
62if findString != path {
63return fmt.Errorf("%s: '%s'; an incorrect character was found '%s', starting from the first",
64ErrWrongLocationPath.Error(), path, strings.ReplaceAll(path, findString, ""))
65}
66return nil
67}
68
69func CompilePathToLocation(path string) (*Node, error) {
70if path == "" {
71return nil, errors2.Wrap(ErrWrongLocationPath, "the path is empty")
72}
73
74node := &Node{}
75head := node
76
77if err := ValidateLocation(path); err != nil {
78return nil, err
79}
80
81for _, part := range rules.Location.FindAllString(path, -1) {
82if res := rules.Location.FindString(part); res == "" {
83return nil, ErrWrongLocationPath
84}
85if !strings.Contains(part, "[") {
86node.path = strings.ReplaceAll(part, ".", "")
87node.nodeType = SimplePath
88}
89if res, _ := regexp.MatchString(rules.ValueFilter.String(), part); res {
90node.nodeType = ValueFilter
91vals := strings.Split(part, "[")
92node.path = vals[0]
93filterVals := strings.Split(vals[1], "==") // quick fix, latter change to regex
94filterVals[0] = strings.TrimSpace(filterVals[0])
95filterVals[1] = strings.TrimSpace(filterVals[1])
96node.filter = &filter{
97name: filterVals[0][2:],
98value: filterVals[1][1 : strings.Index(filterVals[1][1:], "`")+1],
99}
100}
101if res, _ := regexp.MatchString(rules.Filter.String(), part); res {
102node.nodeType = NameFilter
103vals := strings.Split(part, "[")
104if vals[1][len(vals[1])-1] == '.' {
105vals[1] = vals[1][:len(vals[1])-1]
106}
107node.path = vals[0]
108node.filter = &filter{name: vals[1][2 : len(vals[1])-1]}
109}
110if res, _ := regexp.MatchString(rules.Generic.String(), part); res {
111node.nodeType = GenericFilter
112vals := strings.Split(part, "[")
113node.path = vals[0][:len(vals[0])]
114}
115
116newNode := &Node{}
117node.child = newNode
118node = newNode
119}
120return 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
133func (l *Node) MergeByLocation(reference interface{}, replacement map[string]interface{}, location *Node) (map[string]interface{}, error) {
134var err error
135if location.path == "" {
136return replacement, nil
137}
138
139mapReference := reference.(map[string]interface{})[location.path]
140target := make(map[string]interface{})
141
142switch location.nodeType {
143case SimplePath:
144target[location.path] = make(map[string]interface{})
145
146if _, ok := replacement[location.path]; ok && location.child.path == "" && len(replacement) == 1 {
147target[location.path] = replacement[location.path]
148return target, nil
149}
150target[location.path], err = l.MergeByLocation(mapReference, replacement, location.child)
151if err != nil {
152return nil, err
153}
154case GenericFilter:
155
156target[location.path], err = l.mergeFilter(mapReference, replacement, location, func(target any, location *Node) (bool, error) {
157return true, nil
158})
159if err != nil {
160return nil, err
161}
162
163case NameFilter:
164target[location.path], err = l.mergeFilter(mapReference, replacement, location, checkField)
165if err != nil {
166return nil, err
167}
168case ValueFilter:
169target[location.path], err = l.mergeFilter(mapReference, replacement, location, checkValue)
170if err != nil {
171return nil, err
172}
173}
174
175return target, nil
176}
177
178//nolint:funlen
179//nolint:gocognit
180func (l *Node) ReplaceByLocation(target any, replacement map[string]interface{}, location *Node) (map[string]interface{}, error) {
181var err error
182if location.path == "" {
183return replacement, nil
184}
185
186targetMap, ok := target.(map[string]interface{})
187if !ok {
188return nil, errors2.Wrap(ErrYamlStructure, fmt.Sprintf("can't replace \"%q\" is not map", location.path))
189}
190switch location.nodeType {
191case SimplePath:
192if _, ok := targetMap[location.path]; !ok {
193targetMap[location.path] = make(map[string]interface{})
194}
195if _, ok := targetMap[location.path].(map[string]interface{}); !ok {
196targetMap[location.path] = make(map[string]interface{})
197}
198if _, ok := replacement[location.path]; ok && location.child.path == "" && len(replacement) == 1 {
199targetMap[location.path] = replacement[location.path]
200return targetMap, nil
201}
202targetMap[location.path], err = l.ReplaceByLocation(targetMap[location.path].(map[string]interface{}), replacement, location.child)
203
204case GenericFilter:
205targetMap[location.path], err = l.replaceFilter(targetMap[location.path], replacement, location, func(target any, node *Node) (bool, error) {
206return true, nil
207})
208
209case NameFilter:
210targetMap[location.path], err = l.replaceFilter(targetMap[location.path], replacement, location, checkField)
211if err != nil {
212return nil, err
213}
214
215case ValueFilter:
216targetMap[location.path], err = l.replaceFilter(targetMap[location.path], replacement, location, checkValue)
217if err != nil {
218return nil, err
219}
220}
221
222return targetMap, err
223}
224
225func checkField(target any, location *Node) (bool, error) {
226targetMap, ok := target.(map[string]interface{})
227if !ok {
228return false, errors2.Wrap(ErrWrongLocationPath, fmt.Sprintf("can't filter \"%q\" is not map", location.path))
229}
230if _, ok = targetMap[location.filter.name]; !ok {
231return false, nil
232}
233return true, nil
234}
235
236func checkValue(target any, location *Node) (bool, error) {
237ok, err := checkField(target, location)
238if err != nil {
239return false, err
240}
241if ok {
242if target.(map[string]interface{})[location.filter.name] == location.filter.value {
243return true, nil
244}
245}
246
247return false, nil
248}
249
250func (l *Node) replaceFilter(target any, replacement map[string]interface{}, location *Node, filterFunc func(target any, location *Node) (bool, error)) (any, error) {
251
252switch v := reflect.ValueOf(target); v.Kind() {
253
254case reflect.Slice | reflect.Array:
255arr, ok := target.([]interface{})
256if !ok {
257return nil, ErrYamlStructure
258}
259for i := 0; i < len(arr); i++ {
260ok, err := filterFunc(arr[i], location)
261if err != nil {
262return nil, err
263}
264if ok {
265arr[i], err = l.ReplaceByLocation(arr[i], replacement, location.child)
266if err != nil {
267return nil, err
268}
269}
270}
271case reflect.Map:
272iter, ok := target.(map[string]any)
273if !ok {
274return nil, ErrYamlStructure
275}
276for key := range iter {
277ok, err := filterFunc(iter[key], location)
278if err != nil {
279return nil, err
280}
281if ok {
282iter[key], err = l.ReplaceByLocation(iter[key], replacement, location.child)
283if err != nil {
284return nil, err
285}
286}
287}
288default:
289return nil, errors2.Wrap(ErrWrongLocationPath, fmt.Sprintf("can't filter got unexpected type at \"%q\" ", location.path))
290}
291
292return target, nil
293}
294
295func (l *Node) mergeFilter(reference any, replacement map[string]interface{}, location *Node, filterFunc func(target any, location *Node) (bool, error)) (any, error) {
296
297switch v := reflect.ValueOf(reference); v.Kind() {
298
299case reflect.Slice | reflect.Array:
300arr := reference.([]interface{})
301target := make([]interface{}, 0, len(arr))
302for _, el := range arr {
303yaml, ok := el.(map[string]interface{})
304if !ok {
305return nil, ErrYamlStructure
306}
307ok, err := filterFunc(el, location)
308if err != nil {
309return nil, err
310}
311if ok {
312res, _ := l.MergeByLocation(yaml, replacement, location.child)
313if _, ok := res["name"]; !ok {
314mapEl, ok := el.(map[string]interface{})
315if ok {
316if name, ok := mapEl["name"]; ok {
317res["name"] = name
318}
319}
320}
321
322target = append(target, res)
323}
324}
325return target, nil
326case reflect.Map:
327iter := reference.(map[string]any)
328target := make(map[string]any)
329for key := range iter {
330ok, err := filterFunc(iter[key], location)
331if err != nil {
332return nil, err
333}
334if ok {
335res, _ := l.MergeByLocation(iter[key], replacement, location.child)
336target[key] = res
337}
338}
339return target, nil
340
341default:
342return nil, errors2.Wrap(ErrWrongLocationPath, fmt.Sprintf("can't filter got unexpected type at \"%q\" ", location.path))
343}
344
345}
346
347func (l *Node) GetLastItem() string {
348var lastPath string
349for last := l; last.child != nil; last = last.child {
350lastPath = last.path
351}
352return lastPath
353}
354