336 lines
9.2 KiB
Go
336 lines
9.2 KiB
Go
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
|
||
}
|