kubelatte-ce
Форк от sbertech/kubelatte-ce
353 строки · 9.5 Кб
1package location2
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 int16
17const (18SimplePath NodeType = iota19GenericFilter
20NameFilter
21ValueFilter
22)
23
24type Node struct {25nodeType NodeType
26path string27filter *filter28child *Node29}
30
31type filter struct {32name string33value string34}
35
36type Rules struct {37ValueFilter *regexp.Regexp38Filter *regexp.Regexp39Generic *regexp.Regexp40Validation *regexp.Regexp41Location *regexp.Regexp42}
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 *Rules55
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 nil67}
68
69func CompilePathToLocation(path string) (*Node, error) {70if path == "" {71return nil, errors2.Wrap(ErrWrongLocationPath, "the path is empty")72}73
74node := &Node{}75head := node76
77if err := ValidateLocation(path); err != nil {78return nil, err79}80
81for _, part := range rules.Location.FindAllString(path, -1) {82if res := rules.Location.FindString(part); res == "" {83return nil, ErrWrongLocationPath84}85if !strings.Contains(part, "[") {86node.path = strings.ReplaceAll(part, ".", "")87node.nodeType = SimplePath88}89if res, _ := regexp.MatchString(rules.ValueFilter.String(), part); res {90node.nodeType = ValueFilter91vals := strings.Split(part, "[")92node.path = vals[0]93filterVals := strings.Split(vals[1], "==") // quick fix, latter change to regex94filterVals[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 = NameFilter103vals := 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 = GenericFilter112vals := strings.Split(part, "[")113node.path = vals[0][:len(vals[0])]114}115
116newNode := &Node{}117node.child = newNode118node = newNode119}120return head, nil121}
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 error135if location.path == "" {136return replacement, nil137}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, nil149}150target[location.path], err = l.MergeByLocation(mapReference, replacement, location.child)151if err != nil {152return nil, err153}154case GenericFilter:155
156target[location.path], err = l.mergeFilter(mapReference, replacement, location, func(target any, location *Node) (bool, error) {157return true, nil158})159if err != nil {160return nil, err161}162
163case NameFilter:164target[location.path], err = l.mergeFilter(mapReference, replacement, location, checkField)165if err != nil {166return nil, err167}168case ValueFilter:169target[location.path], err = l.mergeFilter(mapReference, replacement, location, checkValue)170if err != nil {171return nil, err172}173}174
175return target, nil176}
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 error182if location.path == "" {183return replacement, nil184}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, nil201}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, nil207})208
209case NameFilter:210targetMap[location.path], err = l.replaceFilter(targetMap[location.path], replacement, location, checkField)211if err != nil {212return nil, err213}214
215case ValueFilter:216targetMap[location.path], err = l.replaceFilter(targetMap[location.path], replacement, location, checkValue)217if err != nil {218return nil, err219}220}221
222return targetMap, err223}
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, nil232}233return true, nil234}
235
236func checkValue(target any, location *Node) (bool, error) {237ok, err := checkField(target, location)238if err != nil {239return false, err240}241if ok {242if target.(map[string]interface{})[location.filter.name] == location.filter.value {243return true, nil244}245}246
247return false, nil248}
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, ErrYamlStructure258}259for i := 0; i < len(arr); i++ {260ok, err := filterFunc(arr[i], location)261if err != nil {262return nil, err263}264if ok {265arr[i], err = l.ReplaceByLocation(arr[i], replacement, location.child)266if err != nil {267return nil, err268}269}270}271case reflect.Map:272iter, ok := target.(map[string]any)273if !ok {274return nil, ErrYamlStructure275}276for key := range iter {277ok, err := filterFunc(iter[key], location)278if err != nil {279return nil, err280}281if ok {282iter[key], err = l.ReplaceByLocation(iter[key], replacement, location.child)283if err != nil {284return nil, err285}286}287}288default:289return nil, errors2.Wrap(ErrWrongLocationPath, fmt.Sprintf("can't filter got unexpected type at \"%q\" ", location.path))290}291
292return target, nil293}
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, ErrYamlStructure306}307ok, err := filterFunc(el, location)308if err != nil {309return nil, err310}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"] = name318}319}320}321
322target = append(target, res)323}324}325return target, nil326case 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, err333}334if ok {335res, _ := l.MergeByLocation(iter[key], replacement, location.child)336target[key] = res337}338}339return target, nil340
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 string349for last := l; last.child != nil; last = last.child {350lastPath = last.path351}352return lastPath353}
354