package setup import ( "bubble/types" "fmt" "github.com/expr-lang/expr" "github.com/sirupsen/logrus" "io" "net/http" "os" "os/exec" "reflect" "regexp" "strconv" "strings" ) // userRE - с начала строки, делит все по знаку двоеточия, берется первые 3 группы, пример user_name:x:1000: var userRE = regexp.MustCompile(`^([^:]+):[^:]*:([0-9]+):`) // ParamsForCondition — собирает все текущие значения переменных приложения // для использования в expr-условиях (condition) func ParamsForCondition(app *types.PackageApplication) map[string]interface{} { params := make(map[string]interface{}) for _, variable := range app.Variables { value := variable.Value if value == nil { value = variable.Default } // Приводим к нужному типу, как в твоём старом коде switch variable.Type { case "bool": if s, ok := value.(string); ok { value = strings.ToLower(s) == "true" } case "number", "int", "float": if s, ok := value.(string); ok { if n, err := strconv.ParseFloat(s, 64); err == nil { value = n } } } params[variable.Name] = value } // Также добавляем значения из полей секций (на случай, если переменная используется только в поле) for _, section := range app.Sections { for _, field := range section.Fields { if field.Variable == nil || !field.Active { continue } value := field.Variable.Value if value == nil { value = field.Variable.Default } switch field.Variable.Type { case "bool": if s, ok := value.(string); ok { value = strings.ToLower(s) == "true" } case "number", "int", "float": if s, ok := value.(string); ok { if n, err := strconv.ParseFloat(s, 64); err == nil { value = n } } } params[field.VariableName] = value } } return params } // CheckCondition — проверяет условие visibility (condition) для секции или поля func CheckCondition(condition string, app *types.PackageApplication) bool { if condition == "" { return true } result, err := expr.Eval(condition, ParamsForCondition(app)) if err != nil { logrus.Errorf("Ошибка в условии '%s': %v", condition, err) return false } boolResult, ok := result.(bool) if !ok { logrus.Warnf("Условие '%s' не вернуло bool, а %T: %v", condition, result, result) return false } return boolResult } // ParamsForCompute — аналогично, но только по переменным (как в твоём старом коде) func ParamsForCompute(app *types.PackageApplication) map[string]interface{} { params := make(map[string]interface{}) for i := range app.Variables { v := &app.Variables[i] value := v.Value if value == nil { value = v.Default } params[v.Name] = value } // Добавляем значения из активных полей for _, section := range app.Sections { for _, field := range section.Fields { if field.Variable == nil || !field.Active { continue } value := field.Variable.Value if value == nil { value = field.Variable.Default } params[field.VariableName] = value } } return params } // ComputeValue — вычисляет значение для compute-поля func ComputeValue(computeExpr string, app *types.PackageApplication) (interface{}, error) { if computeExpr == "" { return nil, nil } env := ParamsForCompute(app) options := append([]expr.Option{ expr.Env(env), expr.AsKind(reflect.String), // по умолчанию строка, если не указано иное expr.AllowUndefinedVariables(), }, DefineExprCustomFunctions()...) program, err := expr.Compile(computeExpr, options...) if err != nil { return nil, fmt.Errorf("ошибка компиляции compute: %v", err) } result, err := expr.Run(program, env) if err != nil { return nil, fmt.Errorf("ошибка выполнения compute: %v", err) } return result, nil } func Recompute(app *types.PackageApplication) { for i := range app.Sections { section := &app.Sections[i] for j := range section.Fields { field := §ion.Fields[j] if field.FieldType == "compute" && field.Compute != "" { if field.Variable != nil { continue //?почему мы пропускаем } value, err := ComputeValue(field.Compute, app) if err != nil { value = fmt.Sprintf("Ошибка вычисления: %v", err) } field.Variable.Value = value } } } } func DefineExprCustomFunctions() []expr.Option { return []expr.Option{ expr.Function("regex", func(args ...interface{}) (interface{}, error) { if len(args) != 3 { return "", fmt.Errorf("regex() ожидает 3 аргумента") } input := fmt.Sprintf("%v", args[0]) pattern := fmt.Sprintf("%v", args[1]) repl := fmt.Sprintf("%v", args[2]) re, err := regexp.Compile(pattern) if err != nil { return input, nil } return re.ReplaceAllString(input, repl), nil }, new(func(string, string, string) string), ), expr.Function("regex_match", func(args ...interface{}) (interface{}, error) { if len(args) != 2 { return false, fmt.Errorf("regex_match() ожидает 2 аргумента") } input := fmt.Sprintf("%v", args[0]) pattern := fmt.Sprintf("%v", args[1]) re, err := regexp.Compile(pattern) if err != nil { return false, fmt.Errorf("некоректный regex %s, err:%v", pattern, err) } return re.MatchString(input), nil }, new(func(string, string) bool), ), expr.Function("error", func(args ...interface{}) (interface{}, error) { if len(args) == 0 { return nil, fmt.Errorf("error() требует сообщения") } return nil, fmt.Errorf("%v", args[0]) }, new(func(string) error), ), expr.Function("http_get", func(args ...interface{}) (interface{}, error) { if len(args) == 0 { return "", nil } url := fmt.Sprintf("%v", args[0]) resp, err := http.Get(url) if err != nil { return "", err } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) return string(body), nil }, new(func(string) string), ), expr.Function("shell", func(args ...interface{}) (interface{}, error) { if len(args) == 0 { return "", nil } cmd := fmt.Sprintf("%v", args[0]) parts := strings.Fields(cmd) if len(parts) == 0 { return "", nil } out, err := exec.Command(parts[0], parts[1:]...).CombinedOutput() if err != nil { return "", err } return strings.TrimSpace(string(out)), nil }, new(func(string) error), ), expr.Function("env", func(args ...interface{}) (interface{}, error) { if len(args) == 0 { return "", nil } return os.Getenv(fmt.Sprintf("%v", args[0])), nil }, new(func(string) error), ), expr.Function("file_exists", func(args ...interface{}) (interface{}, error) { if len(args) == 0 { return false, nil } path := fmt.Sprintf("%v", args[0]) _, err := os.Stat(path) return err == nil, nil }, new(func(string) bool), ), expr.Function("dir_exists", func(args ...interface{}) (interface{}, error) { if len(args) == 0 { return false, nil } path := fmt.Sprintf("%v", args[0]) fileInfo, err := os.Stat(path) if err != nil { return nil, err } if fileInfo.IsDir() { return true, nil } return false, nil }, new(func(string) bool), ), expr.Function("port_free", func(args ...interface{}) (interface{}, error) { if len(args) == 0 { return false, nil } port := fmt.Sprintf("%v", args[0]) cmd := exec.Command("ss", "-ltn", "sport =: "+port) return cmd.Run() != nil, nil }, new(func(string) error), ), expr.Function("user_exists", func(args ...interface{}) (interface{}, error) { if len(args) == 0 { return false, nil } name := fmt.Sprintf("%v", args[0]) cmd := exec.Command("id", "-u", name) return cmd.Run() == nil, nil }, new(func(string) bool), ), } } func SpecialValueHandle(special string) interface{} { specialValue := strings.Split(special, "code:")[1] switch specialValue { case "users": return GetSystemUsers() } return true } // GetSystemUsers получение списка обычных пользователей в системе func GetSystemUsers() []string { //результат выполнения команды читающей файл passwd output, _ := exec.Command("getent", "passwd").Output() //массив строк разделенных новой строкой lines := strings.Split(string(output), "\n") var users []string for _, line := range lines { //линия совпадает с регулярным выражением if matches := userRE.FindStringSubmatch(line); len(matches) == 3 { //идентификатор больше или равен 1000, uid обычного пользователя в системе if uid, _ := strconv.Atoi(matches[2]); uid >= 1000 { users = append(users, matches[1]) } } } return users }