tests: beginning of tests using Docker

This commit is contained in:
2025-07-04 09:02:27 -05:00
parent 7be2679b91
commit 305b504ca1
52 changed files with 1423 additions and 521 deletions

View File

@ -1,18 +1,19 @@
package apt
import (
"bufio"
"bytes"
"fmt"
"regexp"
"strings"
"git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/pkgcommon"
packagemanagercommon "git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/common"
)
// AptManager implements PackageManager for systems using APT.
type AptManager struct {
useAuth bool // Whether to use an authentication command
authCommand string // The authentication command, e.g., "sudo"
Parser pkgcommon.PackageParser
Parser packagemanagercommon.PackageParser
}
// DefaultAuthCommand is the default command used for authentication.
@ -29,14 +30,13 @@ func NewAptManager() *AptManager {
}
// Install returns the command and arguments for installing a package.
func (a *AptManager) Install(pkg, version string, args []string) (string, []string) {
func (a *AptManager) Install(pkgs []packagemanagercommon.Package, args []string) (string, []string) {
baseCmd := a.prependAuthCommand(DefaultPackageCommand)
baseArgs := []string{"update", "&&", baseCmd, "install", "-y"}
if version != "" {
baseArgs = append(baseArgs, fmt.Sprintf("%s=%s", pkg, version))
} else {
baseArgs = append(baseArgs, pkg)
for _, p := range pkgs {
baseArgs = append(baseArgs, p.Name)
}
if args != nil {
baseArgs = append(baseArgs, args...)
}
@ -44,31 +44,34 @@ func (a *AptManager) Install(pkg, version string, args []string) (string, []stri
}
// Remove returns the command and arguments for removing a package.
func (a *AptManager) Remove(pkg string, args []string) (string, []string) {
func (a *AptManager) Remove(pkgs []packagemanagercommon.Package, args []string) (string, []string) {
baseCmd := a.prependAuthCommand(DefaultPackageCommand)
baseArgs := []string{"remove", "-y", pkg}
baseArgs := []string{"remove", "-y"}
for _, p := range pkgs {
baseArgs = append(baseArgs, p.Name)
}
if args != nil {
baseArgs = append(baseArgs, args...)
}
return baseCmd, baseArgs
}
// Upgrade returns the command and arguments for upgrading a specific package.
func (a *AptManager) Upgrade(pkg, version string) (string, []string) {
func (a *AptManager) Upgrade(pkgs []packagemanagercommon.Package) (string, []string) {
baseCmd := a.prependAuthCommand(DefaultPackageCommand)
baseArgs := []string{"update", "&&", baseCmd, "install", "--only-upgrade", "-y"}
if version != "" {
baseArgs = append(baseArgs, fmt.Sprintf("%s=%s", pkg, version))
} else {
baseArgs = append(baseArgs, pkg)
for _, p := range pkgs {
baseArgs = append(baseArgs, p.Name)
}
return baseCmd, baseArgs
}
// CheckVersion returns the command and arguments for checking the info of a specific package.
func (a *AptManager) CheckVersion(pkg, version string) (string, []string) {
func (a *AptManager) CheckVersion(pkgs []packagemanagercommon.Package) (string, []string) {
baseCmd := a.prependAuthCommand("apt-cache")
baseArgs := []string{"policy", pkg}
baseArgs := []string{"policy"}
for _, p := range pkgs {
baseArgs = append(baseArgs, p.Name)
}
return baseCmd, baseArgs
}
@ -81,7 +84,7 @@ func (a *AptManager) UpgradeAll() (string, []string) {
}
// Configure applies functional options to customize the package manager.
func (a *AptManager) Configure(options ...pkgcommon.PackageManagerOption) {
func (a *AptManager) Configure(options ...packagemanagercommon.PackageManagerOption) {
for _, opt := range options {
opt(a)
}
@ -106,25 +109,56 @@ func (a *AptManager) SetAuthCommand(authCommand string) {
}
// Parse parses the apt-cache policy output to extract Installed and Candidate versions.
func (a *AptManager) Parse(output string) (*pkgcommon.PackageVersion, error) {
func (a *AptManager) ParseRemotePackageManagerVersionOutput(output string) ([]packagemanagercommon.Package, []error) {
var (
packageName string
installedString string
candidateString string
countRelevantLines int
)
// Check for error message in the output
if strings.Contains(output, "Unable to locate package") {
return nil, fmt.Errorf("error: %s", strings.TrimSpace(output))
return nil, []error{fmt.Errorf("error: %s", strings.TrimSpace(output))}
}
packages := []packagemanagercommon.Package{}
outputBuf := bytes.NewBufferString(output)
outputScan := bufio.NewScanner(outputBuf)
for outputScan.Scan() {
line := outputScan.Text()
if !strings.HasPrefix(line, " ") && strings.HasSuffix(line, ":") {
// count++
packageName = strings.TrimSpace(strings.TrimSuffix(line, ":"))
}
if strings.Contains(line, "Installed:") {
countRelevantLines++
installedString = strings.TrimPrefix(strings.TrimSpace(line), "Installed:")
}
if strings.Contains(line, "Candidate:") {
countRelevantLines++
candidateString = strings.TrimPrefix(strings.TrimSpace(line), "Candidate:")
}
if countRelevantLines == 2 {
countRelevantLines = 0
packages = append(packages, packagemanagercommon.Package{
Name: packageName,
VersionCheck: packagemanagercommon.PackageVersion{
Installed: strings.TrimSpace(installedString),
Candidate: strings.TrimSpace(candidateString),
Match: installedString == candidateString,
}},
)
}
}
reInstalled := regexp.MustCompile(`Installed:\s*([^\s]+)`)
reCandidate := regexp.MustCompile(`Candidate:\s*([^\s]+)`)
installedMatch := reInstalled.FindStringSubmatch(output)
candidateMatch := reCandidate.FindStringSubmatch(output)
if len(installedMatch) < 2 || len(candidateMatch) < 2 {
return nil, fmt.Errorf("failed to parse Installed or Candidate versions from apt output. check package name")
}
return &pkgcommon.PackageVersion{
Installed: strings.TrimSpace(installedMatch[1]),
Candidate: strings.TrimSpace(candidateMatch[1]),
Match: installedMatch[1] == candidateMatch[1],
}, nil
return packages, nil
}
func SearchPackages(pkgs []string, version string) (string, []string) {
baseCommand := "dpkg-query"
baseArgs := []string{"-W", "-f='${Package}\t${Architecture}\t${db:Status-Status}\t${Version}\t${Installed-Size}\t${Binary:summary}\n'"}
baseArgs = append(baseArgs, pkgs...)
return baseCommand, baseArgs
}

View File

@ -1,4 +1,4 @@
package pkgcommon
package packagemanagercommon
// PackageManagerOption defines a functional option for configuring a PackageManager.
type PackageManagerOption func(interface{})
@ -15,3 +15,9 @@ type PackageVersion struct {
Match bool
Message string
}
type Package struct {
Name string `yaml:"name"`
Version string `yaml:"version,omitempty"`
VersionCheck PackageVersion
}

View File

@ -5,7 +5,7 @@ import (
"regexp"
"strings"
"git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/pkgcommon"
packagemanagercommon "git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/common"
)
// DnfManager implements PackageManager for systems using YUM.
@ -26,21 +26,21 @@ func NewDnfManager() *DnfManager {
}
// Configure applies functional options to customize the package manager.
func (y *DnfManager) Configure(options ...pkgcommon.PackageManagerOption) {
func (y *DnfManager) Configure(options ...packagemanagercommon.PackageManagerOption) {
for _, opt := range options {
opt(y)
}
}
// Install returns the command and arguments for installing a package.
func (y *DnfManager) Install(pkg, version string, args []string) (string, []string) {
func (y *DnfManager) Install(pkgs []packagemanagercommon.Package, args []string) (string, []string) {
baseCmd := y.prependAuthCommand("dnf")
baseArgs := []string{"install", "-y"}
if version != "" {
baseArgs = append(baseArgs, fmt.Sprintf("%s-%s", pkg, version))
} else {
baseArgs = append(baseArgs, pkg)
for _, p := range pkgs {
baseArgs = append(baseArgs, p.Name)
}
if args != nil {
baseArgs = append(baseArgs, args...)
}
@ -48,9 +48,13 @@ func (y *DnfManager) Install(pkg, version string, args []string) (string, []stri
}
// Remove returns the command and arguments for removing a package.
func (y *DnfManager) Remove(pkg string, args []string) (string, []string) {
func (y *DnfManager) Remove(pkgs []packagemanagercommon.Package, args []string) (string, []string) {
baseCmd := y.prependAuthCommand("dnf")
baseArgs := []string{"remove", "-y", pkg}
baseArgs := []string{"remove", "-y"}
for _, p := range pkgs {
baseArgs = append(baseArgs, p.Name)
}
if args != nil {
baseArgs = append(baseArgs, args...)
}
@ -58,38 +62,41 @@ func (y *DnfManager) Remove(pkg string, args []string) (string, []string) {
}
// Upgrade returns the command and arguments for upgrading a specific package.
func (y *DnfManager) Upgrade(pkg, version string) (string, []string) {
func (y *DnfManager) Upgrade(pkgs []packagemanagercommon.Package) (string, []string) {
baseCmd := y.prependAuthCommand("dnf")
baseArgs := []string{"update", "-y"}
if version != "" {
baseArgs = append(baseArgs, fmt.Sprintf("%s-%s", pkg, version))
} else {
baseArgs = append(baseArgs, pkg)
for _, p := range pkgs {
baseArgs = append(baseArgs, p.Name)
}
return baseCmd, baseArgs
}
// UpgradeAll returns the command and arguments for upgrading all packages.
func (y *DnfManager) UpgradeAll() (string, []string) {
baseCmd := y.prependAuthCommand("dnf")
baseArgs := []string{"update", "-y"}
baseArgs := []string{"upgrade", "-y"}
return baseCmd, baseArgs
}
// CheckVersion returns the command and arguments for checking the info of a specific package.
func (d *DnfManager) CheckVersion(pkg, version string) (string, []string) {
func (d *DnfManager) CheckVersion(pkgs []packagemanagercommon.Package) (string, []string) {
baseCmd := d.prependAuthCommand("dnf")
baseArgs := []string{"info", pkg}
baseArgs := []string{"info"}
for _, p := range pkgs {
baseArgs = append(baseArgs, p.Name)
}
return baseCmd, baseArgs
}
// Parse parses the dnf info output to extract Installed and Candidate versions.
func (d DnfManager) Parse(output string) (*pkgcommon.PackageVersion, error) {
func (d DnfManager) ParseRemotePackageManagerVersionOutput(output string) ([]packagemanagercommon.Package, []error) {
// Check for error message in the output
if strings.Contains(output, "No matching packages to list") {
return nil, fmt.Errorf("error: package not listed")
return nil, []error{fmt.Errorf("error: package not listed")}
}
// Define regular expressions to capture installed and available versions
@ -111,13 +118,10 @@ func (d DnfManager) Parse(output string) (*pkgcommon.PackageVersion, error) {
}
if installedVersion == "" && candidateVersion == "" {
return nil, fmt.Errorf("failed to parse versions from dnf output")
return nil, []error{fmt.Errorf("failed to parse versions from dnf output")}
}
return &pkgcommon.PackageVersion{
Installed: installedVersion,
Candidate: candidateVersion,
}, nil
return nil, nil
}
// prependAuthCommand prepends the authentication command if UseAuth is true.

View File

@ -4,26 +4,26 @@ import (
"fmt"
"git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/apt"
packagemanagercommon "git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/common"
"git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/dnf"
"git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/pkgcommon"
"git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/yum"
)
// PackageManager is an interface used to define common package commands. This shall be implemented by every package.
type PackageManager interface {
Install(pkg, version string, args []string) (string, []string)
Remove(pkg string, args []string) (string, []string)
Upgrade(pkg, version string) (string, []string) // Upgrade a specific package
Install(pkgs []packagemanagercommon.Package, args []string) (string, []string)
Remove(pkgs []packagemanagercommon.Package, args []string) (string, []string)
Upgrade(pkgs []packagemanagercommon.Package) (string, []string) // Upgrade a specific package
UpgradeAll() (string, []string)
CheckVersion(pkg, version string) (string, []string)
Parse(output string) (*pkgcommon.PackageVersion, error)
CheckVersion(pkgs []packagemanagercommon.Package) (string, []string)
ParseRemotePackageManagerVersionOutput(output string) ([]packagemanagercommon.Package, []error)
// Configure applies functional options to customize the package manager.
Configure(options ...pkgcommon.PackageManagerOption)
Configure(options ...packagemanagercommon.PackageManagerOption)
}
// PackageManagerFactory returns the appropriate PackageManager based on the package tool.
// Takes variable number of options.
func PackageManagerFactory(managerType string, options ...pkgcommon.PackageManagerOption) (PackageManager, error) {
func PackageManagerFactory(managerType string, options ...packagemanagercommon.PackageManagerOption) (PackageManager, error) {
var manager PackageManager
switch managerType {
@ -43,7 +43,7 @@ func PackageManagerFactory(managerType string, options ...pkgcommon.PackageManag
}
// WithAuth enables authentication and sets the authentication command.
func WithAuth(authCommand string) pkgcommon.PackageManagerOption {
func WithAuth(authCommand string) packagemanagercommon.PackageManagerOption {
return func(manager interface{}) {
if configurable, ok := manager.(interface {
SetUseAuth(bool)
@ -56,7 +56,7 @@ func WithAuth(authCommand string) pkgcommon.PackageManagerOption {
}
// WithoutAuth disables authentication.
func WithoutAuth() pkgcommon.PackageManagerOption {
func WithoutAuth() packagemanagercommon.PackageManagerOption {
return func(manager interface{}) {
if configurable, ok := manager.(interface {
SetUseAuth(bool)
@ -68,8 +68,8 @@ func WithoutAuth() pkgcommon.PackageManagerOption {
// ConfigurablePackageManager defines methods for setting configuration options.
type ConfigurablePackageManager interface {
pkgcommon.PackageParser
packagemanagercommon.PackageParser
SetUseAuth(useAuth bool)
SetAuthCommand(authCommand string)
SetPackageParser(parser pkgcommon.PackageParser)
SetPackageParser(parser packagemanagercommon.PackageParser)
}

View File

@ -3,8 +3,9 @@ package yum
import (
"fmt"
"regexp"
"strings"
"git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/pkgcommon"
packagemanagercommon "git.andrewnw.xyz/CyberShell/backy/pkg/pkgman/common"
)
// YumManager implements PackageManager for systems using YUM.
@ -25,21 +26,20 @@ func NewYumManager() *YumManager {
}
// Configure applies functional options to customize the package manager.
func (y *YumManager) Configure(options ...pkgcommon.PackageManagerOption) {
func (y *YumManager) Configure(options ...packagemanagercommon.PackageManagerOption) {
for _, opt := range options {
opt(y)
}
}
// Install returns the command and arguments for installing a package.
func (y *YumManager) Install(pkg, version string, args []string) (string, []string) {
func (y *YumManager) Install(pkgs []packagemanagercommon.Package, args []string) (string, []string) {
baseCmd := y.prependAuthCommand("yum")
baseArgs := []string{"install", "-y"}
if version != "" {
baseArgs = append(baseArgs, fmt.Sprintf("%s-%s", pkg, version))
} else {
baseArgs = append(baseArgs, pkg)
for _, p := range pkgs {
baseArgs = append(baseArgs, p.Name)
}
if args != nil {
baseArgs = append(baseArgs, args...)
}
@ -47,9 +47,13 @@ func (y *YumManager) Install(pkg, version string, args []string) (string, []stri
}
// Remove returns the command and arguments for removing a package.
func (y *YumManager) Remove(pkg string, args []string) (string, []string) {
func (y *YumManager) Remove(pkgs []packagemanagercommon.Package, args []string) (string, []string) {
baseCmd := y.prependAuthCommand("yum")
baseArgs := []string{"remove", "-y", pkg}
baseArgs := []string{"remove", "-y"}
for _, p := range pkgs {
baseArgs = append(baseArgs, p.Name)
}
if args != nil {
baseArgs = append(baseArgs, args...)
}
@ -57,14 +61,13 @@ func (y *YumManager) Remove(pkg string, args []string) (string, []string) {
}
// Upgrade returns the command and arguments for upgrading a specific package.
func (y *YumManager) Upgrade(pkg, version string) (string, []string) {
func (y *YumManager) Upgrade(pkgs []packagemanagercommon.Package) (string, []string) {
baseCmd := y.prependAuthCommand("yum")
baseArgs := []string{"update", "-y"}
if version != "" {
baseArgs = append(baseArgs, fmt.Sprintf("%s-%s", pkg, version))
} else {
baseArgs = append(baseArgs, pkg)
for _, p := range pkgs {
baseArgs = append(baseArgs, p.Name)
}
return baseCmd, baseArgs
}
@ -76,17 +79,27 @@ func (y *YumManager) UpgradeAll() (string, []string) {
}
// CheckVersion returns the command and arguments for checking the info of a specific package.
func (y *YumManager) CheckVersion(pkg, version string) (string, []string) {
func (y *YumManager) CheckVersion(pkgs []packagemanagercommon.Package) (string, []string) {
baseCmd := y.prependAuthCommand("yum")
baseArgs := []string{"info", pkg}
baseArgs := []string{"info"}
for _, p := range pkgs {
baseArgs = append(baseArgs, p.Name)
}
return baseCmd, baseArgs
}
// Parse parses the dnf info output to extract Installed and Candidate versions.
func (y YumManager) Parse(output string) (*pkgcommon.PackageVersion, error) {
reInstalled := regexp.MustCompile(`(?m)^Installed Packages\s*Name\s*:\s*\S+\s*Version\s*:\s*([^\s]+)\s*Release\s*:\s*([^\s]+)`)
reAvailable := regexp.MustCompile(`(?m)^Available Packages\s*Name\s*:\s*\S+\s*Version\s*:\s*([^\s]+)\s*Release\s*:\s*([^\s]+)`)
func (y YumManager) ParseRemotePackageManagerVersionOutput(output string) ([]packagemanagercommon.Package, []error) {
// Check for error message in the output
if strings.Contains(output, "No matching packages to list") {
return nil, []error{fmt.Errorf("error: package not listed")}
}
// Define regular expressions to capture installed and available versions
reInstalled := regexp.MustCompile(`(?m)^Installed packages\s*Name\s*:\s*\S+\s*Epoch\s*:\s*\S+\s*Version\s*:\s*([^\s]+)\s*Release\s*:\s*([^\s]+)`)
reAvailable := regexp.MustCompile(`(?m)^Available packages\s*Name\s*:\s*\S+\s*Epoch\s*:\s*\S+\s*Version\s*:\s*([^\s]+)\s*Release\s*:\s*([^\s]+)`)
installedMatch := reInstalled.FindStringSubmatch(output)
candidateMatch := reAvailable.FindStringSubmatch(output)
@ -103,13 +116,10 @@ func (y YumManager) Parse(output string) (*pkgcommon.PackageVersion, error) {
}
if installedVersion == "" && candidateVersion == "" {
return nil, fmt.Errorf("failed to parse versions from dnf output")
return nil, []error{fmt.Errorf("failed to parse versions from dnf output")}
}
return &pkgcommon.PackageVersion{
Installed: installedVersion,
Candidate: candidateVersion,
}, nil
return nil, nil
}
// prependAuthCommand prepends the authentication command if UseAuth is true.