2026-01-23 17:52:47 +05:00
|
|
|
|
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)
|
|
|
|
|
|
}
|
2026-01-30 17:40:25 +05:00
|
|
|
|
output, err := RunCommand(cmdStr, action.RunAs)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
if action.AssignTo != "" {
|
|
|
|
|
|
assigned := false
|
|
|
|
|
|
for i := range application.Variables {
|
|
|
|
|
|
v := &application.Variables[i]
|
|
|
|
|
|
if v.Name == action.AssignTo {
|
|
|
|
|
|
v.Value = output
|
|
|
|
|
|
assigned = true
|
|
|
|
|
|
logrus.Infof("Присвоение значения '%s' переменной '%s'", output, v.Name)
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if !assigned {
|
|
|
|
|
|
return fmt.Errorf("переменная '%s' для присвоения не найдена", action.AssignTo)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
2026-01-23 17:52:47 +05:00
|
|
|
|
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 запускает выполнение переданной команды от указанного лица
|
2026-01-30 17:40:25 +05:00
|
|
|
|
func RunCommand(cmdStr, runAs string) (string, error) {
|
2026-01-23 17:52:47 +05:00
|
|
|
|
if cmdStr == "" {
|
2026-01-30 17:40:25 +05:00
|
|
|
|
return "", nil
|
2026-01-23 17:52:47 +05:00
|
|
|
|
}
|
|
|
|
|
|
var cmd *exec.Cmd
|
|
|
|
|
|
if runAs != "" && runAs != "root" {
|
2026-02-02 17:56:36 +05:00
|
|
|
|
cmd = exec.Command("sudo", "-u", runAs, "bash", "-c", cmdStr)
|
2026-01-23 17:52:47 +05:00
|
|
|
|
} else {
|
|
|
|
|
|
cmd = exec.Command("bash", "-c", cmdStr)
|
|
|
|
|
|
}
|
|
|
|
|
|
output, err := cmd.CombinedOutput()
|
|
|
|
|
|
if err != nil {
|
2026-01-30 17:40:25 +05:00
|
|
|
|
return string(output), fmt.Errorf("ошибка выполнения команды: %v\nВывод: %s", err, string(output))
|
2026-01-23 17:52:47 +05:00
|
|
|
|
}
|
2026-01-30 17:40:25 +05:00
|
|
|
|
return strings.TrimSpace(string(output)), nil
|
2026-01-23 17:52:47 +05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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 {
|
2026-02-02 17:56:36 +05:00
|
|
|
|
return fmt.Errorf("ошибка подстановки значений в шаблон: %v", err)
|
2026-01-23 17:52:47 +05:00
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|