Изменение вызова действий, проверка наличия действий секции, отображение счетчика выполнения действий в модальном окне, работа над подключением привязки горячих клавиш
This commit is contained in:
parent
91b1eea1d4
commit
c69a7ae84b
14 changed files with 886 additions and 706 deletions
160
setup/utils/actions.go
Normal file
160
setup/utils/actions.go
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"bubble/types"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/sirupsen/logrus"
|
||||
"html/template"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// InstallPackages устанавливает требуемые пакеты если их нет в системе
|
||||
func InstallPackages(requiredPackages []string) error {
|
||||
for _, requiredPackage := range requiredPackages {
|
||||
if result, checkError := IsPackageInstall(requiredPackage); !result {
|
||||
if checkError != nil {
|
||||
return fmt.Errorf("пакет %s: %v", requiredPackage, checkError)
|
||||
}
|
||||
installCmd := exec.Command("apt", "install", "-y", requiredPackage)
|
||||
out, err := installCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("пакет %s: %v\nOutput: %s", requiredPackage, err, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsPackageInstall проверяет установлен ли пакет в системе
|
||||
func IsPackageInstall(packageName string) (bool, error) {
|
||||
cmd := exec.Command("dpkg", "-s", packageName)
|
||||
if err := cmd.Run(); err != nil {
|
||||
var exitError *exec.ExitError
|
||||
if errors.As(err, &exitError) && exitError.ExitCode() == 1 {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// ExecuteAction выполняет указанное действие с использованием шаблонов из expr и картой параметров
|
||||
func ExecuteAction(action *types.SectionAction, application *types.PackageApplication) error {
|
||||
//Карта параметров
|
||||
vars := ParamsForCondition(application)
|
||||
switch action.Type {
|
||||
case "command":
|
||||
//Формируем строку команды по шаблону с подставленными значениями и передаем её для выполнения
|
||||
cmdStr, cmdErr := RenderTemplate(action.Command, vars)
|
||||
if cmdErr != nil {
|
||||
return fmt.Errorf("не удалось создать шаблон команды: %v", cmdErr)
|
||||
}
|
||||
return RunCommand(cmdStr, action.RunAs)
|
||||
case "file_template":
|
||||
//Формируем 2 строки пути, с подставленными значениями и передаем её для выполнения
|
||||
source, sourceErr := RenderTemplate(action.FileSource, vars)
|
||||
if sourceErr != nil {
|
||||
return fmt.Errorf("не удалось создать шаблон файла источника: %v", sourceErr)
|
||||
}
|
||||
target, targetErr := RenderTemplate(action.FileTarget, vars)
|
||||
if targetErr != nil {
|
||||
return fmt.Errorf("не удалось создать шаблон файла цели: %v", targetErr)
|
||||
}
|
||||
return RenderFileTemplate(source, target, vars)
|
||||
default:
|
||||
return fmt.Errorf("действие для выполнения '%s' не опознано", action.Type)
|
||||
}
|
||||
}
|
||||
|
||||
// RunCommand запускает выполнение переданной команды от указанного лица
|
||||
func RunCommand(cmdStr, runAs string) error {
|
||||
if cmdStr == "" {
|
||||
return nil
|
||||
}
|
||||
var cmd *exec.Cmd
|
||||
if runAs != "" && runAs != "root" {
|
||||
cmd = exec.Command("su", "-c", cmdStr, runAs)
|
||||
} else {
|
||||
cmd = exec.Command("bash", "-c", cmdStr)
|
||||
}
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("ошибка команды: %v\nВывод: %s", err, string(output))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RenderFileTemplate создает целевой файл на основе файла источника с подставленными значениями в нем
|
||||
func RenderFileTemplate(source, target string, vars map[string]interface{}) error {
|
||||
if source == "" || target == "" {
|
||||
return fmt.Errorf("source или target пустые")
|
||||
}
|
||||
var data []byte
|
||||
var err error
|
||||
//Получаем данные файла источника
|
||||
if strings.HasPrefix(source, "http") {
|
||||
resp, err := http.Get(source)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ошибка получения данных файла по адресу %s, err : %v", source, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
data, err = io.ReadAll(resp.Body)
|
||||
} else {
|
||||
data, err = os.ReadFile(source)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("ошибка чтения данных файла %s, err: %v", source, err)
|
||||
}
|
||||
|
||||
//Формируем файл с переменными
|
||||
tmpl, err := template.New("").Parse(string(data))
|
||||
if err != nil {
|
||||
return fmt.Errorf("ошибка формирования шаблона строки: %v", err)
|
||||
}
|
||||
var rendered bytes.Buffer
|
||||
|
||||
//В файл помещаем переменные
|
||||
if err = tmpl.Execute(&rendered, vars); err != nil {
|
||||
return fmt.Errorf("ошибка подстановки значений в шаблон")
|
||||
}
|
||||
if _, err = os.Stat(target); err == nil {
|
||||
backup := target + ".bak" + time.Now().Format("20060102-150405")
|
||||
if cpErr := CopyFile(target, backup); cpErr != nil {
|
||||
logrus.Warnf("Не удалось создать бекап: %v", cpErr)
|
||||
} else {
|
||||
logrus.Infof("Бекап %s создан", backup)
|
||||
}
|
||||
}
|
||||
//Создаем папку для целевого файла и записываем в него данные
|
||||
if err = os.MkdirAll(filepath.Dir(target), 0755); err != nil {
|
||||
return fmt.Errorf("ошибка создания директории для файла %s, err: %v", target, err)
|
||||
}
|
||||
if err = os.WriteFile(target, rendered.Bytes(), 0644); err != nil {
|
||||
return fmt.Errorf("ошибка записи в файл %s, err: %v", target, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RenderTemplate формирует строку с подставленными параметрами
|
||||
func RenderTemplate(tmpl string, data map[string]interface{}) (string, error) {
|
||||
if tmpl == "" {
|
||||
return "", nil
|
||||
}
|
||||
t, err := template.New("").Parse(tmpl)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("ошибка формирования шаблона строки: %v", err)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err = t.Execute(&buf, data); err != nil {
|
||||
return "", fmt.Errorf("ошибка подстановки значений в шаблон: %v", err)
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
221
setup/utils/field.go
Normal file
221
setup/utils/field.go
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"bubble/types"
|
||||
"fmt"
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
"github.com/expr-lang/expr"
|
||||
"github.com/sirupsen/logrus"
|
||||
"os/exec"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// userRE - с начала строки, делит все по знаку двоеточия, берется первые 3 группы, пример user_name:x:1000:
|
||||
var userRE = regexp.MustCompile(`^([^:]+):[^:]*:([0-9]+):`)
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// IsParent проверяет, является ли указанная переменная условием для поля или секции
|
||||
func IsParent(variableName string, sections []types.Section) bool {
|
||||
for _, section := range sections {
|
||||
for _, parent := range section.Parent {
|
||||
if parent == variableName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, field := range section.Fields {
|
||||
for _, parent := range field.Parent {
|
||||
if parent == variableName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsMultiField проверяет переменную на использование несколькими полями.
|
||||
func IsMultiField(variableName string, applicationSections []types.Section) bool {
|
||||
var count int
|
||||
for _, section := range applicationSections {
|
||||
for _, field := range section.Fields {
|
||||
if field.Name == variableName {
|
||||
count++
|
||||
}
|
||||
}
|
||||
}
|
||||
return count > 1
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// ValidateCheckField создает функцию типа types.BoolValidateFunc для проверки значения bool полей
|
||||
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 для проверки значения text полей
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +1,18 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"bubble/types"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/sirupsen/logrus"
|
||||
"html/template"
|
||||
"github.com/expr-lang/expr"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CalculateSelectOptionsIndex рассчитывает индекс первой и последней опции для отображения в поле выбора
|
||||
|
|
@ -44,91 +45,6 @@ func CalculateSelectOptionsIndex(focus, visibleCount, optionsCount int) (int, in
|
|||
return start, end
|
||||
}
|
||||
|
||||
// RenderFileTemplate создает целевой файл на основе файла источника с подставленными значениями в нем
|
||||
func RenderFileTemplate(source, target string, vars map[string]interface{}) error {
|
||||
if source == "" || target == "" {
|
||||
return fmt.Errorf("source или target пустые")
|
||||
}
|
||||
var data []byte
|
||||
var err error
|
||||
//Получаем данные файла источника
|
||||
if strings.HasPrefix(source, "http") {
|
||||
resp, err := http.Get(source)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ошибка получения данных файла по адресу %s, err : %v", source, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
data, err = io.ReadAll(resp.Body)
|
||||
} else {
|
||||
data, err = os.ReadFile(source)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("ошибка чтения данных файла %s, err: %v", source, err)
|
||||
}
|
||||
|
||||
//Формируем файл с переменными
|
||||
tmpl, err := template.New("").Parse(string(data))
|
||||
if err != nil {
|
||||
return fmt.Errorf("ошибка формирования шаблона строки: %v", err)
|
||||
}
|
||||
var rendered bytes.Buffer
|
||||
|
||||
//В файл помещаем переменные
|
||||
if err = tmpl.Execute(&rendered, vars); err != nil {
|
||||
return fmt.Errorf("ошибка подстановки значений в шаблон")
|
||||
}
|
||||
if _, err = os.Stat(target); err == nil {
|
||||
backup := target + ".bak" + time.Now().Format("20060102-150405")
|
||||
if cpErr := CopyFile(target, backup); cpErr != nil {
|
||||
logrus.Warnf("Не удалось создать бекап: %v", cpErr)
|
||||
} else {
|
||||
logrus.Infof("Бекап %s создан", backup)
|
||||
}
|
||||
}
|
||||
//Создаем папку для целевого файла и записываем в него данные
|
||||
if err = os.MkdirAll(filepath.Dir(target), 0755); err != nil {
|
||||
return fmt.Errorf("ошибка создания директории для файла %s, err: %v", target, err)
|
||||
}
|
||||
if err = os.WriteFile(target, rendered.Bytes(), 0644); err != nil {
|
||||
return fmt.Errorf("ошибка записи в файл %s, err: %v", target, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RenderTemplate формирует строку с подставленными параметрами
|
||||
func RenderTemplate(tmpl string, data map[string]interface{}) (string, error) {
|
||||
if tmpl == "" {
|
||||
return "", nil
|
||||
}
|
||||
t, err := template.New("").Parse(tmpl)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("ошибка формирования шаблона строки: %v", err)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err = t.Execute(&buf, data); err != nil {
|
||||
return "", fmt.Errorf("ошибка подстановки значений в шаблон: %v", err)
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// RunCommand запускает выполнение переданной команды от указанного лица
|
||||
func RunCommand(cmdStr, runAs string) error {
|
||||
if cmdStr == "" {
|
||||
return nil
|
||||
}
|
||||
var cmd *exec.Cmd
|
||||
if runAs != "" && runAs != "root" {
|
||||
cmd = exec.Command("su", "-c", cmdStr, runAs)
|
||||
} else {
|
||||
cmd = exec.Command("bash", "-c", cmdStr)
|
||||
}
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("ошибка команды: %v\nВывод: %s", err, string(output))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyFile копирование файла в новое место
|
||||
func CopyFile(src, dst string) error {
|
||||
in, err := os.Open(src)
|
||||
|
|
@ -145,3 +61,247 @@ func CopyFile(src, dst string) error {
|
|||
_, err = io.Copy(out, in)
|
||||
return err
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue