package builtin import ( "context" "fmt" "github.com/vladimir/goherence/internal/module" "github.com/vladimir/goherence/internal/ssh" ) // PackageRPMModule — аналог ansible.builtin.yum/dnf для RPM-based систем // (RHEL/CentOS/ALT Linux с apt-rpm тоже подходит частично, но здесь // используется универсальный `rpm -q` для проверки + `dnf`/`yum` для установки). type PackageRPMModule struct{} func (m *PackageRPMModule) Name() string { return "rpm_package" } func (m *PackageRPMModule) Run(ctx context.Context, in module.Input, conn *ssh.Conn) (module.Result, error) { name, ok := in.Args["name"].(string) if !ok { return module.Result{}, fmt.Errorf("rpm_package: аргумент name обязателен") } state, _ := in.Args["state"].(string) if state == "" { state = "present" } installed, err := m.isInstalled(conn, name) if err != nil { return module.Result{}, err } wantInstalled := state == "present" || state == "latest" if installed == wantInstalled { return module.Result{SchemaVersion: module.SchemaVersion, Changed: false, Msg: name + ": уже в нужном состоянии"}, nil } if in.CheckMode { return module.Result{SchemaVersion: module.SchemaVersion, Changed: true, Msg: fmt.Sprintf("check mode: %s был бы %s", name, state)}, nil } pm, err := m.packageManager(conn) if err != nil { return module.Result{}, err } var cmd string if wantInstalled { cmd = fmt.Sprintf("%s install -y %s", pm, shArg(name)) } else { cmd = fmt.Sprintf("%s remove -y %s", pm, shArg(name)) } res, err := conn.Run(cmd) if err != nil { return module.Result{}, err } if res.ExitCode != 0 { return module.Result{SchemaVersion: module.SchemaVersion, Failed: true, Msg: fmt.Sprintf("%s завершился с кодом %d: %s", pm, res.ExitCode, res.Stderr)}, nil } return module.Result{SchemaVersion: module.SchemaVersion, Changed: true, Msg: name + ": " + state}, nil } func (m *PackageRPMModule) isInstalled(conn *ssh.Conn, name string) (bool, error) { res, err := conn.Run(fmt.Sprintf("rpm -q %s > /dev/null 2>&1", shArg(name))) if err != nil { return false, err } return res.ExitCode == 0, nil } // packageManager определяет dnf/yum/apt-rpm по наличию бинарника — // нужно, потому что RPM-based дистрибутивы разошлись по пакетным менеджерам. func (m *PackageRPMModule) packageManager(conn *ssh.Conn) (string, error) { for _, candidate := range []string{"dnf", "yum", "apt-get"} { res, err := conn.Run("command -v " + candidate) if err == nil && res.ExitCode == 0 { return candidate, nil } } return "", fmt.Errorf("не найден ни dnf, ни yum, ни apt-get на хосте") }