GoSetup-bubble/setup/condition.go

350 lines
10 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}
// Recompute производит перерасчет всех полей
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 != "" {
if field.Variable != nil {
// todo почему мы пропускаем?
continue
}
value, err := ComputeValue(field.Compute, app)
if err != nil {
value = fmt.Sprintf("Ошибка вычисления: %v", err)
}
field.Variable.Value = value
}
}
}
}
// 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])
_, 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),
),
}
}
// 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
}