go-package-manager/rpm_common.go
2026-07-01 17:47:49 -05:00

158 lines
4.6 KiB
Go

package pkgmgr
import (
"bufio"
"context"
"fmt"
"io"
"os"
"path/filepath"
"strings"
)
// YUM_REPO_DIR is where rpm-based managers (dnf, yum, zypper) write repo files.
const YUM_REPO_DIR = "/etc/yum.repos.d"
// rpmBase provides the repo, signing-key, and package-listing behavior shared
// by the rpm-based managers. Each concrete manager embeds it and supplies its
// own Name, Format, Path, and action subcommands.
type rpmBase struct {
baseManager
}
func (p *rpmBase) repoPath(name string) string {
return filepath.Join(YUM_REPO_DIR, name+".repo")
}
// AddRepo adds a repo from a configuration string.
func (p *rpmBase) AddRepo(name, config string) error {
return writeRepoFile(p.repoPath(name), config)
}
// AddRepoURL adds a repo whose configuration is downloaded from repoURL.
func (p *rpmBase) AddRepoURL(ctx context.Context, name, repoURL string) error {
return downloadRepoFile(ctx, p.repoPath(name), repoURL)
}
// RemoveRepo removes a repo.
func (p *rpmBase) RemoveRepo(name string) error {
return removeRepoFile(p.repoPath(name))
}
// GetRepo returns a repo's configuration, or "" if it does not exist.
func (p *rpmBase) GetRepo(name string) string {
return readRepoFile(p.repoPath(name))
}
// ListRepos returns every repo file in YUM_REPO_DIR keyed by file stem. The
// value matches what GetRepo returns for that name.
func (p *rpmBase) ListRepos(ctx context.Context, args []string) (map[string]string, error) {
return listRepoFiles(YUM_REPO_DIR, ".repo")
}
// importKey runs `rpm --import` on a key file.
func (p *rpmBase) importKey(ctx context.Context, keyFile string) error {
rpm := findBinary("rpm")
if rpm == "" {
return fmt.Errorf("unable to find rpm")
}
return p.Command(ctx, rpm, "--import", keyFile).Run()
}
// AddRepoKey adds a key for repo package verification.
func (p *rpmBase) AddRepoKey(ctx context.Context, key string) error {
fd, err := os.CreateTemp("", "GPG")
if err != nil {
return err
}
name := fd.Name()
_, werr := fd.WriteString(key)
cerr := fd.Close()
if werr != nil {
os.Remove(name)
return werr
}
if cerr != nil {
os.Remove(name)
return cerr
}
err = p.importKey(ctx, name)
os.Remove(name)
return err
}
// AddRepoKeyFile adds a key for repo package verification from a file.
func (p *rpmBase) AddRepoKeyFile(ctx context.Context, keyFile string) error {
return p.importKey(ctx, keyFile)
}
// AddRepoKeyURL adds a key for repo package verification from a URL.
func (p *rpmBase) AddRepoKeyURL(ctx context.Context, keyURL string) error {
tmp, err := downloadToTemp(ctx, keyURL, "GPG")
if err != nil {
return err
}
err = p.importKey(ctx, tmp)
os.Remove(tmp)
return err
}
// ListInstalled returns installed packages mapped to their version. It queries
// rpm directly so the listing is consistent across dnf, yum, and zypper.
func (p *rpmBase) ListInstalled(ctx context.Context, args []string) (map[string]string, error) {
rpm := findBinary("rpm")
if rpm == "" {
return nil, fmt.Errorf("unable to find rpm")
}
args = joinArgs(args, "-qa", "--queryformat", "%{NAME} := %|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
return runVersionList(p.Command(ctx, rpm, args...), " := ", true)
}
// parseRpmSearch parses the "name.arch : summary" lines printed by `dnf search`
// and `yum search` into search results, skipping the "==== ... ====" section
// headers. Neither tool reports a version in its search output.
func parseRpmSearch(r io.Reader) ([]SearchResult, error) {
var out []SearchResult
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "==") {
continue
}
name, summary, ok := strings.Cut(line, " : ")
if !ok {
continue
}
name = strings.TrimSpace(name)
// The arch is the final dot-separated component of the name.
if i := strings.LastIndex(name, "."); i != -1 {
name = name[:i]
}
out = append(out, SearchResult{Name: name, Summary: strings.TrimSpace(summary)})
}
return out, scanner.Err()
}
// parseRpmUpgradable parses the "name.arch version repo" rows printed by
// `dnf list --upgrades` and `yum list updates` into a name->version map. The
// trailing ".arch" qualifier is stripped from the package name.
func parseRpmUpgradable(r io.Reader) (map[string]string, error) {
out := make(map[string]string)
scanner := bufio.NewScanner(r)
for scanner.Scan() {
fields := strings.Fields(scanner.Text())
// Data rows have exactly three columns; headers and notices do not.
if len(fields) != 3 {
continue
}
name := fields[0]
// The arch is always the final dot-separated component of the name.
if i := strings.LastIndex(name, "."); i != -1 {
name = name[:i]
}
out[name] = fields[1]
}
return out, scanner.Err()
}