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("su", "-c", cmdStr, runAs) } 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("ошибка подстановки значений в шаблон") } 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 }