GoSetup-bubble/setup/condition.go

399 lines
12 KiB
Go
Raw Normal View History

2025-12-26 17:45:37 +05:00
package setup
import (
"bubble/types"
"errors"
2025-12-26 17:45:37 +05:00
"fmt"
"github.com/charmbracelet/bubbles/textinput"
2025-12-26 17:45:37 +05:00
"github.com/expr-lang/expr"
"github.com/sirupsen/logrus"
"io"
"io/fs"
2025-12-26 17:45:37 +05:00
"net/http"
"os"
"os/exec"
"reflect"
"regexp"
"strconv"
"strings"
)
// userRE - с начала строки, делит все по знаку двоеточия, берется первые 3 группы, пример user_name:x:1000:
var userRE = regexp.MustCompile(`^([^:]+):[^:]*:([0-9]+):`)
// ParamsForCondition получение словаря переменных с приведением типов для проверки условий
2025-12-26 17:45:37 +05:00
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 проверяет равно ли переданное условие
2025-12-26 17:45:37 +05:00
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 получение словаря переменных без приведения типов, для вычисления
2025-12-26 17:45:37 +05:00
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.
2025-12-26 17:45:37 +05:00
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 производит перерасчет всех полей
2025-12-26 17:45:37 +05:00
func Recompute(app *types.PackageApplication) {
for i := range app.Sections {
section := &app.Sections[i]
for j := range section.Fields {
field := &section.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
}
}
}
}
// 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 //todo text это строка, если использовать функцию типа textinput.ValidateFunc, то словим ошибку типов
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
2025-12-26 17:45:37 +05:00
func DefineExprCustomFunctions() []expr.Option {
return []expr.Option{
//вычисляет значение по заданному регулярному выражению
2025-12-26 17:45:37 +05:00
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),
),
//проверяет совпадение переданного значения и указанного регулярного выражения
2025-12-26 17:45:37 +05:00
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),
),
//возвращает собственный текст ошибки
2025-12-26 17:45:37 +05:00
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),
),
//возвращает тело ответа по указанному адресу
2025-12-26 17:45:37 +05:00
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),
),
//возвращает результат выполнения команды из оболочки
2025-12-26 17:45:37 +05:00
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
2025-12-26 17:45:37 +05:00
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),
),
//проверяет наличие файла в системе
2025-12-26 17:45:37 +05:00
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
}
2025-12-26 17:45:37 +05:00
}, new(func(string) bool),
),
//проверяет наличие директории в системе
2025-12-26 17:45:37 +05:00
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() {
2025-12-26 17:45:37 +05:00
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
2025-12-26 17:45:37 +05:00
}
}, new(func(string) bool),
),
//проверяет доступность порта
2025-12-26 17:45:37 +05:00
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),
),
//проверяет наличие пользователя в системе
2025-12-26 17:45:37 +05:00
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 получаем необычные значения полей
2025-12-26 17:45:37 +05:00
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
}