package setup import ( "bubble/setup/utils" "bubble/types" "errors" "fmt" "github.com/charmbracelet/bubbles/textinput" "github.com/expr-lang/expr" "github.com/sirupsen/logrus" "io" "io/fs" "net/http" "os" "os/exec" "reflect" "regexp" "strconv" "strings" ) // userRE - с начала строки, делит все по знаку двоеточия, берется первые 3 группы, пример user_name:x:1000: var userRE = regexp.MustCompile(`^([^:]+):[^:]*:([0-9]+):`) // ParamsForCondition получение словаря переменных с приведением типов для проверки условий 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 проверяет равно ли переданное условие 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 } // Recompute производит перерасчет всех полей 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 != "" { value, err := ComputeValue(field.Compute, app) if err != nil { value = fmt.Sprintf("Ошибка вычисления: %v", err) } field.Variable.Value = value } } } } func ValidateCheckField(fieldName, variableName string, validations []types.FieldValidate, app *types.PackageApplication) types.BoolValidateFunc { if len(validations) == 0 { return nil } return func(checked bool) error { env := ParamsForCompute(app) env[variableName] = checked options := append([]expr.Option{ expr.Env(env), expr.AllowUndefinedVariables(), }, DefineExprCustomFunctions()...) for _, validate := range validations { program, err := expr.Compile(validate.Rule, options...) prefix := fmt.Sprintf("Валидация поля %s,", fieldName) if err != nil { msg := fmt.Errorf("ошибка компиляции правила: %s, err: %v", validate.Rule, err) logrus.Errorf("%s %v", prefix, msg) return msg } result, err := expr.Run(program, env) if err != nil { msg := fmt.Errorf("ошибка выполнения правила: %s, err: %v", validate.Rule, err) logrus.Errorf("%s %v", prefix, msg) return msg } if boolValue, ok := result.(bool); ok && !boolValue { return fmt.Errorf("%s", validate.Message) } else if !ok { msg := fmt.Errorf("возвращаемое значение правила: %s не булево", validate.Rule) logrus.Errorf("%s %v", prefix, msg) return msg } } return nil } } // ValidateField создает функцию типа textinput.ValidateFunc для проверки значения полей func ValidateField(fieldName, variableName string, validations []types.FieldValidate, app *types.PackageApplication) textinput.ValidateFunc { if len(validations) == 0 { return nil } return func(text string) error { env := ParamsForCompute(app) env[variableName] = text options := append([]expr.Option{ expr.Env(env), expr.AllowUndefinedVariables(), }, DefineExprCustomFunctions()...) for _, validate := range validations { program, err := expr.Compile(validate.Rule, options...) prefix := fmt.Sprintf("Валидация поля %s,", fieldName) if err != nil { msg := fmt.Errorf("ошибка компиляции правила: %s, err: %v", validate.Rule, err) logrus.Errorf("%s %v", prefix, msg) return msg } result, err := expr.Run(program, env) if err != nil { msg := fmt.Errorf("ошибка выполнения правила: %s, err: %v", validate.Rule, err) logrus.Errorf("%s %v", prefix, msg) return msg } if boolValue, ok := result.(bool); ok && !boolValue { return fmt.Errorf("%s", validate.Message) } else if !ok { msg := fmt.Errorf("возвращаемое значение правила: %s не булево", validate.Rule) logrus.Errorf("%s %v", prefix, msg) return msg } } return nil } } // DefineExprCustomFunctions возвращает собственные функции expr 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), ), //возвращает значение указанной переменной из env 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]) fileInfo, err := os.Stat(path) if err == nil && !fileInfo.IsDir() { return true, nil } else if err == nil && fileInfo.IsDir() { return false, fmt.Errorf("file is dir") } if errors.Is(err, fs.ErrNotExist) { return false, fmt.Errorf("no such directory or file") } else { return false, err } }, 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 && fileInfo.IsDir() { return true, nil } else if err == nil && !fileInfo.IsDir() { return false, fmt.Errorf("file is not a dir") } if errors.Is(err, fs.ErrNotExist) { return false, fmt.Errorf("no such directory or file") } else { return false, err } }, 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), ), } } // SpecialValueHandle получаем необычные значения полей 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 } func ExecuteAction(action *types.SectionAction, application *types.PackageApplication) error { vars := ParamsForCondition(application) switch action.Type { case "command": cmdStr, cmdErr := utils.RenderTemplate(action.Command, vars) if cmdErr != nil { return fmt.Errorf("ошибка создания шаблона команды: %v", cmdErr) } return utils.RunCommand(cmdStr, action.RunAs) case "file_template": source, sourceErr := utils.RenderTemplate(action.FileSource, vars) if sourceErr != nil { return fmt.Errorf("ошибка создания шаблона файла источника: %v", sourceErr) } target, targetErr := utils.RenderTemplate(action.FileTarget, vars) if targetErr != nil { return fmt.Errorf("ошибка создания шаблона файла цели: %v", targetErr) } return utils.RenderFileTemplate(source, target, vars) default: return fmt.Errorf("неизвестный тип действия") } }