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

165 lines
5.9 KiB
Go

package pkgmgr
import (
"context"
"io"
"os"
"os/exec"
)
// SearchResult is a single package match returned by Search. Version and
// Summary are best-effort: a field is left empty when the underlying tool does
// not report it in its search output.
type SearchResult struct {
Name string
Version string
Summary string
}
// Manager is the interface for working with a system package manager. Methods
// that run a subprocess or perform network I/O take a context.Context so the
// caller can apply cancellation and timeouts.
type Manager interface {
// Name is the package manager's name.
Name() string
// Format is the package format the manager installs (deb, rpm, ...).
Format() string
// Path is the resolved path to the manager's command, or "" if missing.
Path() string
// SetCmdWrapper sets a command wrapper, for example []string{"sudo"}.
SetCmdWrapper(wrapper []string)
// UseSudoWhenNeeded wraps privileged commands with sudo when not already
// root. It is a no-op as root, and managers that must not run as root
// (brew, yay, paru) bypass the wrapper for their own command.
UseSudoWhenNeeded()
// SetIO overrides the stdin, stdout, and stderr used by spawned commands.
// Passing nil for a stream restores its os default.
SetIO(stdin io.Reader, stdout, stderr io.Writer)
// AddRepo adds a repo from a configuration string.
AddRepo(name, config string) error
// AddRepoURL adds a repo whose configuration is downloaded from repoURL.
AddRepoURL(ctx context.Context, name, repoURL string) error
// RemoveRepo removes a repo.
RemoveRepo(name string) error
// GetRepo returns a repo's configuration, or "" if it does not exist.
GetRepo(name string) string
// ListRepos returns all configured repos mapped name->configuration. The
// value for each name matches what GetRepo returns for it.
ListRepos(ctx context.Context, args []string) (map[string]string, error)
// AddRepoKey adds a key for repo package verification.
AddRepoKey(ctx context.Context, key string) error
// AddRepoKeyFile adds a key for repo package verification from a file.
AddRepoKeyFile(ctx context.Context, keyFile string) error
// AddRepoKeyURL adds a key for repo package verification from a URL.
AddRepoKeyURL(ctx context.Context, keyURL string) error
// Sync updates repository metadata.
Sync(ctx context.Context, args []string) error
// Install installs packages from the repositories.
Install(ctx context.Context, args []string, packages ...string) error
// Remove removes packages.
Remove(ctx context.Context, args []string, packages ...string) error
// Upgrade upgrades the named packages.
Upgrade(ctx context.Context, args []string, packages ...string) error
// InstallFile installs a package from a local file.
InstallFile(ctx context.Context, args []string, packages ...string) error
// UpgradeAll upgrades all packages with available updates.
UpgradeAll(ctx context.Context, args []string) error
// Clean removes cached package data to reclaim disk space.
Clean(ctx context.Context, args []string) error
// Search searches the repositories for packages matching query and returns
// the matches.
Search(ctx context.Context, args []string, query string) ([]SearchResult, error)
// Info returns detailed information about the named packages as the
// underlying tool's native text output.
Info(ctx context.Context, args []string, packages ...string) (string, error)
// ListInstalled returns installed packages mapped to their version.
ListInstalled(ctx context.Context, args []string) (map[string]string, error)
// ListUpgradable returns packages with an available update mapped to the
// candidate version, without applying the updates.
ListUpgradable(ctx context.Context, args []string) (map[string]string, error)
}
// GetSystemManager finds the system manager with a priority of zypper, dnf,
// yum, apt, apt-get, pacman, apk, brew. It returns nil when none are found.
func GetSystemManager() Manager {
managers := []Manager{
&Zypper{},
&Dnf{},
&Yum{},
&Apt{},
&AptGet{},
&Pacman{},
&Apk{},
&Brew{},
}
for _, manager := range managers {
if manager.Path() != "" {
return manager
}
}
return nil
}
// baseManager holds state and helpers shared by every manager implementation.
type baseManager struct {
wrapper []string
stdin io.Reader
stdout io.Writer
stderr io.Writer
}
// SetCmdWrapper sets a command wrapper, for example []string{"sudo"}.
func (p *baseManager) SetCmdWrapper(wrapper []string) {
p.wrapper = wrapper
}
// SetIO overrides the stdin, stdout, and stderr used by spawned commands.
func (p *baseManager) SetIO(stdin io.Reader, stdout, stderr io.Writer) {
p.stdin = stdin
p.stdout = stdout
p.stderr = stderr
}
// stdinOrDefault returns the configured stdin, or os.Stdin when unset.
func (p *baseManager) stdinOrDefault() io.Reader {
if p.stdin != nil {
return p.stdin
}
return os.Stdin
}
// stdoutOrDefault returns the configured stdout, or os.Stdout when unset.
func (p *baseManager) stdoutOrDefault() io.Writer {
if p.stdout != nil {
return p.stdout
}
return os.Stdout
}
// stderrOrDefault returns the configured stderr, or os.Stderr when unset.
func (p *baseManager) stderrOrDefault() io.Writer {
if p.stderr != nil {
return p.stderr
}
return os.Stderr
}
// Command builds an *exec.Cmd for the manager, applying the configured command
// wrapper and I/O streams. The command is bound to ctx for cancellation.
func (p *baseManager) Command(ctx context.Context, command string, args ...string) *exec.Cmd {
if len(p.wrapper) == 1 {
args = append([]string{command}, args...)
command = p.wrapper[0]
} else if len(p.wrapper) > 1 {
argsA := append([]string{}, p.wrapper[1:]...)
argsA = append(argsA, command)
args = append(argsA, args...)
command = p.wrapper[0]
}
cmd := exec.CommandContext(ctx, command, args...)
cmd.Env = os.Environ()
cmd.Stdin = p.stdinOrDefault()
cmd.Stdout = p.stdoutOrDefault()
cmd.Stderr = p.stderrOrDefault()
return cmd
}