GoSetup-bubble/setup/utils/actions.go

179 lines
6.5 KiB
Go
Raw Permalink 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 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)
}
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
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) (string, error) {
if cmdStr == "" {
return "", nil
}
var cmd *exec.Cmd
if runAs != "" && runAs != "root" {
cmd = exec.Command("sudo", "-u", runAs, "bash", "-c", cmdStr)
} else {
cmd = exec.Command("bash", "-c", cmdStr)
}
output, err := cmd.CombinedOutput()
if err != nil {
return string(output), fmt.Errorf("ошибка выполнения команды: %v\nВывод: %s", err, string(output))
}
return strings.TrimSpace(string(output)), 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("ошибка подстановки значений в шаблон: %v", err)
}
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
}