diff --git a/cmd/imageOutdated.go b/cmd/imageOutdated.go index 2e3e9bb..43b90a3 100644 --- a/cmd/imageOutdated.go +++ b/cmd/imageOutdated.go @@ -4,15 +4,27 @@ import ( "log/slog" "github.com/spf13/cobra" - "github.com/ublue-os/uupd/drv" + "github.com/ublue-os/uupd/drv/generic" + "github.com/ublue-os/uupd/drv/system" ) func ImageOutdated(cmd *cobra.Command, args []string) { - systemUpdater, err := drv.SystemUpdater{}.New(drv.UpdaterInitConfiguration{}) + initConfiguration := generic.UpdaterInitConfiguration{}.New() + initConfiguration.Ci = false + initConfiguration.DryRun = false + initConfiguration.Verbose = false + + mainSystemDriver, _, _, err := system.InitializeSystemDriver(*initConfiguration) if err != nil { - slog.Error("Failed getting system driver", slog.Any("error", err)) + slog.Error("Failed") return } - println(systemUpdater.Outdated) + systemOutdated, err := mainSystemDriver.Outdated() + + if err != nil { + slog.Error("Failed checking if system is out of date") + } + + println(systemOutdated) } diff --git a/cmd/root.go b/cmd/root.go index f8e476d..5a7fa43 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -9,10 +9,8 @@ import ( "path" "path/filepath" - "github.com/jedib0t/go-pretty/v6/text" "github.com/spf13/cobra" appLogging "github.com/ublue-os/uupd/pkg/logging" - "golang.org/x/term" ) func assertRoot(cmd *cobra.Command, args []string) { @@ -118,19 +116,4 @@ func init() { rootCmd.PersistentFlags().StringVar(&fLogFile, "log-file", "-", "File where user-facing logs will be written to") rootCmd.PersistentFlags().StringVar(&fLogLevel, "log-level", "info", "Log level for user-facing logs") rootCmd.PersistentFlags().BoolVar(&fNoLogging, "quiet", false, "Make logs quiet") - - interactiveProgress := true - if fLogFile != "-" { - interactiveProgress = false - } - isTerminal := term.IsTerminal(int(os.Stdout.Fd())) - if !isTerminal { - interactiveProgress = false - } - if !text.ANSICodesSupported { - interactiveProgress = false - text.DisableColors() - } - - rootCmd.Flags().BoolP("no-progress", "p", !interactiveProgress, "Do not show progress bars") } diff --git a/cmd/update.go b/cmd/update.go index 84c13f3..2f22aa7 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -5,27 +5,35 @@ import ( "log/slog" "os" - "github.com/jedib0t/go-pretty/v6/progress" "github.com/spf13/cobra" "github.com/ublue-os/uupd/checks" - "github.com/ublue-os/uupd/drv" + "github.com/ublue-os/uupd/drv/brew" + "github.com/ublue-os/uupd/drv/distrobox" + "github.com/ublue-os/uupd/drv/flatpak" + drv "github.com/ublue-os/uupd/drv/generic" + "github.com/ublue-os/uupd/drv/system" + "github.com/ublue-os/uupd/pkg/filelock" "github.com/ublue-os/uupd/pkg/percent" "github.com/ublue-os/uupd/pkg/session" ) func Update(cmd *cobra.Command, args []string) { - lock, err := filelock.AcquireLock() + lockfile, err := filelock.OpenLockfile(filelock.GetDefaultLockfile()) if err != nil { - slog.Error(fmt.Sprintf("%v, is uupd already running?", err)) + slog.Error("Failed creating and opening lockfile. Is uupd already running?", slog.Any("error", err)) return } - defer func() { - err := filelock.ReleaseLock(lock) + defer func(lockfile *os.File) { + err := filelock.ReleaseLock(lockfile) if err != nil { - slog.Error("Failed releasing lock") + slog.Error("Failed releasing lock", slog.Any("error", err)) } - }() + }(lockfile) + if err := filelock.AcquireLock(lockfile, filelock.TimeoutConfig{Tries: 5}); err != nil { + slog.Error(fmt.Sprintf("%v, is uupd already running?", err)) + return + } hwCheck, err := cmd.Flags().GetBool("hw-check") if err != nil { @@ -64,86 +72,40 @@ func Update(cmd *cobra.Command, args []string) { initConfiguration.DryRun = dryRun initConfiguration.Verbose = verboseRun - brewUpdater, err := drv.BrewUpdater{}.New(*initConfiguration) + brewUpdater, err := brew.BrewUpdater{}.New(*initConfiguration) brewUpdater.Config.Enabled = err == nil - flatpakUpdater, err := drv.FlatpakUpdater{}.New(*initConfiguration) + flatpakUpdater, err := flatpak.FlatpakUpdater{}.New(*initConfiguration) flatpakUpdater.Config.Enabled = err == nil flatpakUpdater.SetUsers(users) - distroboxUpdater, err := drv.DistroboxUpdater{}.New(*initConfiguration) + distroboxUpdater, err := distrobox.DistroboxUpdater{}.New(*initConfiguration) distroboxUpdater.Config.Enabled = err == nil distroboxUpdater.SetUsers(users) - var enableUpd bool = true - - rpmOstreeUpdater, err := drv.RpmOstreeUpdater{}.New(*initConfiguration) - if err != nil { - enableUpd = false - } - - systemUpdater, err := drv.SystemUpdater{}.New(*initConfiguration) - if err != nil { - enableUpd = false - } - - isBootc, err := drv.BootcCompatible(systemUpdater.BinaryPath) - if err != nil { - isBootc = false - } - - if !isBootc { - slog.Debug("Using rpm-ostree fallback as system driver") - } - - systemUpdater.Config.Enabled = isBootc && enableUpd - rpmOstreeUpdater.Config.Enabled = !isBootc && enableUpd + mainSystemDriver, mainSystemDriverConfig, _, _ := system.InitializeSystemDriver(*initConfiguration) - // The system driver to be applied needs to have the correct "enabled" value since it will NOT update from here onwards. - var mainSystemDriver drv.SystemUpdateDriver = &systemUpdater - if !isBootc { - mainSystemDriver = &rpmOstreeUpdater - } - - enableUpd, err = mainSystemDriver.Check() + enableUpd, err := mainSystemDriver.Check() if err != nil { slog.Error("Failed checking for updates") } + mainSystemDriverConfig.Enabled = mainSystemDriverConfig.Enabled && enableUpd - slog.Debug("System Updater module status", slog.Bool("enabled", enableUpd)) + slog.Debug("System Updater module status", slog.Bool("enabled", mainSystemDriverConfig.Enabled)) totalSteps := brewUpdater.Steps() + flatpakUpdater.Steps() + distroboxUpdater.Steps() - if enableUpd { + if mainSystemDriverConfig.Enabled { totalSteps += mainSystemDriver.Steps() } - pw := percent.NewProgressWriter() - pw.SetNumTrackersExpected(1) - pw.SetAutoStop(false) - progressEnabled, err := cmd.Flags().GetBool("no-progress") - if err != nil { - slog.Error("Failed to get no-progress flag", "error", err) - return - } - // Move this to its actual boolean value (~no-progress) - progressEnabled = !progressEnabled - - if progressEnabled { - go pw.Render() - percent.ResetOscProgress() - } + // FIXME: check if is interactive + percent.ResetOscProgress() // -1 because 0 index - tracker := percent.NewIncrementTracker(&progress.Tracker{Message: "Updating", Units: progress.UnitsDefault, Total: int64(totalSteps - 1)}, totalSteps-1) - pw.AppendTracker(tracker.Tracker) + tracker := &percent.Incrementer{MaxIncrements: totalSteps - 1} - var trackerConfig = &drv.TrackerConfiguration{ - Tracker: tracker, - Writer: &pw, - Progress: progressEnabled, - } - flatpakUpdater.Tracker = trackerConfig - distroboxUpdater.Tracker = trackerConfig + flatpakUpdater.Tracker = tracker + distroboxUpdater.Tracker = tracker var outputs = []drv.CommandOutput{} @@ -155,7 +117,7 @@ func Update(cmd *cobra.Command, args []string) { if systemOutdated { const OUTDATED_WARNING = "There hasn't been an update in over a month. Consider rebooting or running updates manually" - err := session.Notify("System Warning", OUTDATED_WARNING) + err := session.Notify(users, "System Warning", OUTDATED_WARNING) if err != nil { slog.Error("Failed showing warning notification") } @@ -165,9 +127,9 @@ func Update(cmd *cobra.Command, args []string) { // This section is ugly but we cant really do much about it. // Using interfaces doesn't preserve the "Config" struct state and I dont know any other way to make this work without cursed workarounds. - if enableUpd { - slog.Debug(fmt.Sprintf("%s module", systemUpdater.Config.Title), slog.String("module_name", systemUpdater.Config.Title), slog.Any("module_configuration", systemUpdater.Config)) - percent.ChangeTrackerMessageFancy(pw, tracker, progressEnabled, percent.TrackerMessage{Title: systemUpdater.Config.Title, Description: systemUpdater.Config.Description}) + if mainSystemDriverConfig.Enabled { + slog.Debug(fmt.Sprintf("%s module", mainSystemDriverConfig.Title), slog.String("module_name", mainSystemDriverConfig.Title), slog.Any("module_configuration", mainSystemDriverConfig)) + percent.ReportStatusChange(tracker, percent.TrackerMessage{Title: mainSystemDriverConfig.Title, Description: mainSystemDriverConfig.Description}) var out *[]drv.CommandOutput out, err = mainSystemDriver.Update() outputs = append(outputs, *out...) @@ -176,7 +138,7 @@ func Update(cmd *cobra.Command, args []string) { if brewUpdater.Config.Enabled { slog.Debug(fmt.Sprintf("%s module", brewUpdater.Config.Title), slog.String("module_name", brewUpdater.Config.Title), slog.Any("module_configuration", brewUpdater.Config)) - percent.ChangeTrackerMessageFancy(pw, tracker, progressEnabled, percent.TrackerMessage{Title: brewUpdater.Config.Title, Description: brewUpdater.Config.Description}) + percent.ReportStatusChange(tracker, percent.TrackerMessage{Title: brewUpdater.Config.Title, Description: brewUpdater.Config.Description}) var out *[]drv.CommandOutput out, err = brewUpdater.Update() outputs = append(outputs, *out...) @@ -185,7 +147,6 @@ func Update(cmd *cobra.Command, args []string) { if flatpakUpdater.Config.Enabled { slog.Debug(fmt.Sprintf("%s module", flatpakUpdater.Config.Title), slog.String("module_name", flatpakUpdater.Config.Title), slog.Any("module_configuration", flatpakUpdater.Config)) - percent.ChangeTrackerMessageFancy(pw, tracker, progressEnabled, percent.TrackerMessage{Title: flatpakUpdater.Config.Title, Description: flatpakUpdater.Config.Description}) var out *[]drv.CommandOutput out, err = flatpakUpdater.Update() outputs = append(outputs, *out...) @@ -194,17 +155,14 @@ func Update(cmd *cobra.Command, args []string) { if distroboxUpdater.Config.Enabled { slog.Debug(fmt.Sprintf("%s module", distroboxUpdater.Config.Title), slog.String("module_name", distroboxUpdater.Config.Title), slog.Any("module_configuration", distroboxUpdater.Config)) - percent.ChangeTrackerMessageFancy(pw, tracker, progressEnabled, percent.TrackerMessage{Title: distroboxUpdater.Config.Title, Description: distroboxUpdater.Config.Description}) var out *[]drv.CommandOutput out, err = distroboxUpdater.Update() outputs = append(outputs, *out...) tracker.IncrementSection(err) } - if progressEnabled { - pw.Stop() - percent.ResetOscProgress() - } + // FIXME: detect interactive session + percent.ResetOscProgress() if verboseRun { slog.Info("Verbose run requested") diff --git a/cmd/updateCheck.go b/cmd/updateCheck.go index b8950b4..a2d4f68 100644 --- a/cmd/updateCheck.go +++ b/cmd/updateCheck.go @@ -4,42 +4,20 @@ import ( "log/slog" "github.com/spf13/cobra" - "github.com/ublue-os/uupd/drv" + "github.com/ublue-os/uupd/drv/generic" + "github.com/ublue-os/uupd/drv/system" ) func UpdateCheck(cmd *cobra.Command, args []string) { - var enableUpd bool = true + initConfiguration := generic.UpdaterInitConfiguration{}.New() + initConfiguration.Ci = false + initConfiguration.DryRun = false + initConfiguration.Verbose = false - initConfiguration := drv.UpdaterInitConfiguration{}.New() - rpmOstreeUpdater, err := drv.RpmOstreeUpdater{}.New(*initConfiguration) + mainSystemDriver, _, _, err := system.InitializeSystemDriver(*initConfiguration) if err != nil { - enableUpd = false - } - - systemUpdater, err := drv.SystemUpdater{}.New(*initConfiguration) - if err != nil { - enableUpd = false - } - - isBootc, err := drv.BootcCompatible(systemUpdater.BinaryPath) - if err != nil { - isBootc = false - } - - if !isBootc { - slog.Debug("Using rpm-ostree fallback as system driver") - } - - systemUpdater.Config.Enabled = isBootc && enableUpd - rpmOstreeUpdater.Config.Enabled = !isBootc && enableUpd - - var mainSystemDriver drv.SystemUpdateDriver - if !isBootc { - slog.Debug("Using the rpm-ostree driver") - mainSystemDriver = &rpmOstreeUpdater - } else { - slog.Debug("Using the bootc driver") - mainSystemDriver = &systemUpdater + slog.Error("Failed") + return } updateAvailable, err := mainSystemDriver.Check() diff --git a/drv/brew.go b/drv/brew/brew.go similarity index 62% rename from drv/brew.go rename to drv/brew/brew.go index f934d91..0fc0e93 100644 --- a/drv/brew.go +++ b/drv/brew/brew.go @@ -1,4 +1,4 @@ -package drv +package brew import ( "fmt" @@ -7,6 +7,7 @@ import ( "strings" "syscall" + . "github.com/ublue-os/uupd/drv/generic" "github.com/ublue-os/uupd/pkg/session" ) @@ -45,19 +46,20 @@ func (up BrewUpdater) Update() (*[]CommandOutput, error) { } cli := []string{up.BrewPath, "update"} - out, err := session.RunUID(up.Config.logger, slog.LevelDebug, up.BaseUser, cli, up.Config.Environment) + out, err := session.RunUID(up.Config.Logger, slog.LevelDebug, up.BaseUser, cli, up.Config.Environment) tmpout := CommandOutput{}.New(out, err) tmpout.Context = "Brew Update" tmpout.Cli = cli tmpout.Failure = err != nil - if err != nil { - tmpout.SetFailureContext("Brew update") + if failure := err != nil; failure { + tmpout.Failure = failure + tmpout.Context = "Brew Update" final_output = append(final_output, *tmpout) return &final_output, err } cli = []string{up.BrewPath, "upgrade"} - out, err = session.RunUID(up.Config.logger, slog.LevelDebug, up.BaseUser, cli, up.Config.Environment) + out, err = session.RunUID(up.Config.Logger, slog.LevelDebug, up.BaseUser, cli, up.Config.Environment) tmpout = CommandOutput{}.New(out, err) tmpout.Context = "Brew Upgrade" tmpout.Cli = cli @@ -84,32 +86,12 @@ func (up BrewUpdater) New(config UpdaterInitConfiguration) (BrewUpdater, error) DryRun: config.DryRun, Environment: config.Environment, } - up.Config.logger = config.Logger.With(slog.String("module", strings.ToLower(up.Config.Title))) + up.Config.Logger = config.Logger.With(slog.String("module", strings.ToLower(up.Config.Title))) - brewPrefix, exists := up.Config.Environment["HOMEBREW_PREFIX"] - if !exists || brewPrefix == "" { - up.BrewPrefix = "/home/linuxbrew/.linuxbrew" - } else { - up.BrewPrefix = brewPrefix - } - brewRepo, exists := up.Config.Environment["HOMEBREW_REPOSITORY"] - if !exists || brewRepo == "" { - up.BrewRepo = fmt.Sprintf("%s/Homebrew", up.BrewPrefix) - } else { - up.BrewRepo = brewRepo - } - brewCellar, exists := up.Config.Environment["HOMEBREW_CELLAR"] - if !exists || brewCellar == "" { - up.BrewCellar = fmt.Sprintf("%s/Cellar", up.BrewPrefix) - } else { - up.BrewCellar = brewCellar - } - brewPath, exists := up.Config.Environment["HOMEBREW_PATH"] - if !exists || brewPath == "" { - up.BrewPath = fmt.Sprintf("%s/bin/brew", up.BrewPrefix) - } else { - up.BrewPath = brewPath - } + up.BrewPrefix = EnvOrFallback(up.Config.Environment, "HOMEBREW_PREFIX", "/home/linuxbrew/.linuxbrew") + up.BrewRepo = EnvOrFallback(up.Config.Environment, "HOMEBREW_REPOSITORY", fmt.Sprintf("%s/Homebrew", up.BrewPrefix)) + up.BrewCellar = EnvOrFallback(up.Config.Environment, "HOMEBREW_CELLAR", fmt.Sprintf("%s/Cellar", up.BrewPrefix)) + up.BrewPath = EnvOrFallback(up.Config.Environment, "HOMEBREW_PATH", fmt.Sprintf("%s/bin/brew", up.BrewPrefix)) if up.Config.DryRun { return up, nil @@ -123,11 +105,3 @@ func (up BrewUpdater) New(config UpdaterInitConfiguration) (BrewUpdater, error) return up, nil } - -func (up *BrewUpdater) Logger() *slog.Logger { - return up.Config.logger -} - -func (up *BrewUpdater) SetLogger(logger *slog.Logger) { - up.Config.logger = logger -} diff --git a/drv/brew/brew_test.go b/drv/brew/brew_test.go new file mode 100644 index 0000000..d622250 --- /dev/null +++ b/drv/brew/brew_test.go @@ -0,0 +1,35 @@ +package brew_test + +import ( + "testing" + + "github.com/ublue-os/uupd/drv/brew" + "github.com/ublue-os/uupd/drv/generic" + appLogging "github.com/ublue-os/uupd/pkg/logging" +) + +func InitBaseConfig() brew.BrewUpdater { + var initConfiguration = generic.UpdaterInitConfiguration{ + DryRun: false, + Ci: false, + Verbose: false, + Environment: nil, + Logger: appLogging.NewMuteLogger(), + } + driv, _ := brew.BrewUpdater{}.New(initConfiguration) + return driv +} + +func TestProperSteps(t *testing.T) { + brewUpdater := InitBaseConfig() + brewUpdater.Config.Enabled = false + + if brewUpdater.Steps() != 0 { + t.Fatalf("Expected no steps when module is disabled") + } + + brewUpdater.Config.Enabled = true + if brewUpdater.Steps() == 0 { + t.Fatalf("Expected steps to be added") + } +} diff --git a/drv/distrobox.go b/drv/distrobox/distrobox.go similarity index 55% rename from drv/distrobox.go rename to drv/distrobox/distrobox.go index 5db751c..70a99fa 100644 --- a/drv/distrobox.go +++ b/drv/distrobox/distrobox.go @@ -1,16 +1,17 @@ -package drv +package distrobox import ( "log/slog" "strings" + . "github.com/ublue-os/uupd/drv/generic" "github.com/ublue-os/uupd/pkg/percent" "github.com/ublue-os/uupd/pkg/session" ) type DistroboxUpdater struct { Config DriverConfiguration - Tracker *TrackerConfiguration + Tracker *percent.Incrementer binaryPath string users []session.User usersEnabled bool @@ -38,16 +39,11 @@ func (up DistroboxUpdater) New(config UpdaterInitConfiguration) (DistroboxUpdate DryRun: config.DryRun, Environment: config.Environment, } - up.Config.logger = config.Logger.With(slog.String("module", strings.ToLower(up.Config.Title))) + up.Config.Logger = config.Logger.With(slog.String("module", strings.ToLower(up.Config.Title))) up.usersEnabled = false up.Tracker = nil - binaryPath, exists := up.Config.Environment["UUPD_DISTROBOX_BINARY"] - if !exists || binaryPath == "" { - up.binaryPath = "/usr/bin/distrobox" - } else { - up.binaryPath = binaryPath - } + up.binaryPath = EnvOrFallback(up.Config.Environment, "UUPD_DISTROBOX_BINARY", "/usr/bin/distrobox") return up, nil } @@ -65,20 +61,20 @@ func (up DistroboxUpdater) Update() (*[]CommandOutput, error) { var finalOutput = []CommandOutput{} if up.Config.DryRun { - percent.ChangeTrackerMessageFancy(*up.Tracker.Writer, up.Tracker.Tracker, up.Tracker.Progress, percent.TrackerMessage{Title: up.Config.Title, Description: up.Config.Description}) - up.Tracker.Tracker.IncrementSection(nil) + percent.ReportStatusChange(up.Tracker, percent.TrackerMessage{Title: up.Config.Title, Description: up.Config.Description}) + up.Tracker.IncrementSection(nil) var err error = nil for _, user := range up.users { - up.Tracker.Tracker.IncrementSection(err) - percent.ChangeTrackerMessageFancy(*up.Tracker.Writer, up.Tracker.Tracker, up.Tracker.Progress, percent.TrackerMessage{Title: up.Config.Title, Description: *up.Config.UserDescription + " " + user.Name}) + up.Tracker.IncrementSection(err) + percent.ReportStatusChange(up.Tracker, percent.TrackerMessage{Title: up.Config.Title, Description: *up.Config.UserDescription + " " + user.Name}) } return &finalOutput, nil } - percent.ChangeTrackerMessageFancy(*up.Tracker.Writer, up.Tracker.Tracker, up.Tracker.Progress, percent.TrackerMessage{Title: up.Config.Title, Description: up.Config.Description}) + percent.ReportStatusChange(up.Tracker, percent.TrackerMessage{Title: up.Config.Title, Description: up.Config.Description}) cli := []string{up.binaryPath, "upgrade", "-a"} - out, err := session.RunUID(up.Config.logger, slog.LevelDebug, 0, cli, nil) + out, err := session.RunUID(up.Config.Logger, slog.LevelDebug, 0, cli, nil) tmpout := CommandOutput{}.New(out, err) tmpout.Context = up.Config.Description tmpout.Cli = cli @@ -87,11 +83,11 @@ func (up DistroboxUpdater) Update() (*[]CommandOutput, error) { err = nil for _, user := range up.users { - up.Tracker.Tracker.IncrementSection(err) + up.Tracker.IncrementSection(err) context := *up.Config.UserDescription + " " + user.Name - percent.ChangeTrackerMessageFancy(*up.Tracker.Writer, up.Tracker.Tracker, up.Tracker.Progress, percent.TrackerMessage{Title: up.Config.Title, Description: *up.Config.UserDescription + " " + user.Name}) + percent.ReportStatusChange(up.Tracker, percent.TrackerMessage{Title: up.Config.Title, Description: *up.Config.UserDescription + " " + user.Name}) cli := []string{up.binaryPath, "upgrade", "-a"} - out, err := session.RunUID(up.Config.logger, slog.LevelDebug, user.UID, cli, nil) + out, err := session.RunUID(up.Config.Logger, slog.LevelDebug, user.UID, cli, nil) tmpout = CommandOutput{}.New(out, err) tmpout.Context = context tmpout.Cli = cli @@ -100,11 +96,3 @@ func (up DistroboxUpdater) Update() (*[]CommandOutput, error) { } return &finalOutput, nil } - -func (up *DistroboxUpdater) Logger() *slog.Logger { - return up.Config.logger -} - -func (up *DistroboxUpdater) SetLogger(logger *slog.Logger) { - up.Config.logger = logger -} diff --git a/drv/distrobox/distrobox_test.go b/drv/distrobox/distrobox_test.go new file mode 100644 index 0000000..77d3b27 --- /dev/null +++ b/drv/distrobox/distrobox_test.go @@ -0,0 +1,56 @@ +package distrobox_test + +import ( + "log" + "testing" + + "github.com/ublue-os/uupd/drv/distrobox" + "github.com/ublue-os/uupd/drv/generic" + appLogging "github.com/ublue-os/uupd/pkg/logging" + "github.com/ublue-os/uupd/pkg/session" +) + +func InitBaseConfig() distrobox.DistroboxUpdater { + var initConfiguration = generic.UpdaterInitConfiguration{ + DryRun: false, + Ci: false, + Verbose: false, + Environment: nil, + Logger: appLogging.NewMuteLogger(), + } + driv, _ := distrobox.DistroboxUpdater{}.New(initConfiguration) + return driv +} + +func TestProperSteps(t *testing.T) { + updater := InitBaseConfig() + updater.Config.Enabled = false + + if updater.Steps() != 0 { + t.Fatalf("Expected no steps when module is disabled") + } + + updater.Config.Enabled = true + if updater.Steps() == 0 { + t.Fatalf("Expected steps to be added") + } +} + +func TestProperUserSteps(t *testing.T) { + updater := InitBaseConfig() + + mockUser := []session.User{ + {UID: 0, Name: "root"}, + {UID: 1, Name: "roote"}, + {UID: 2, Name: "rooto"}, + } + updater.SetUsers(mockUser) + + if reported := updater.Steps(); reported != 1+len(mockUser) { + log.Fatalf("Incorrect number of steps for users: %d", reported) + } + updater.Config.Enabled = false + if reported := updater.Steps(); reported != 0 { + log.Fatalf("Incorrect number of steps for users: %d", reported) + } +} diff --git a/drv/flatpak.go b/drv/flatpak/flatpak.go similarity index 57% rename from drv/flatpak.go rename to drv/flatpak/flatpak.go index 1c42e1c..7cbfd79 100644 --- a/drv/flatpak.go +++ b/drv/flatpak/flatpak.go @@ -1,17 +1,18 @@ -package drv +package flatpak import ( "log/slog" "os/exec" "strings" + . "github.com/ublue-os/uupd/drv/generic" "github.com/ublue-os/uupd/pkg/percent" "github.com/ublue-os/uupd/pkg/session" ) type FlatpakUpdater struct { Config DriverConfiguration - Tracker *TrackerConfiguration + Tracker *percent.Incrementer binaryPath string users []session.User usersEnabled bool @@ -39,16 +40,11 @@ func (up FlatpakUpdater) New(config UpdaterInitConfiguration) (FlatpakUpdater, e DryRun: config.DryRun, Environment: config.Environment, } - up.Config.logger = config.Logger.With(slog.String("module", strings.ToLower(up.Config.Title))) + up.Config.Logger = config.Logger.With(slog.String("module", strings.ToLower(up.Config.Title))) up.usersEnabled = false up.Tracker = nil - binaryPath, exists := up.Config.Environment["UUPD_FLATPAK_BINARY"] - if !exists || binaryPath == "" { - up.binaryPath = "/usr/bin/flatpak" - } else { - up.binaryPath = binaryPath - } + up.binaryPath = EnvOrFallback(up.Config.Environment, "UUPD_FLATPAK_BINARY", "/usr/bin/flatpak") return up, nil } @@ -66,21 +62,21 @@ func (up FlatpakUpdater) Update() (*[]CommandOutput, error) { var finalOutput = []CommandOutput{} if up.Config.DryRun { - percent.ChangeTrackerMessageFancy(*up.Tracker.Writer, up.Tracker.Tracker, up.Tracker.Progress, percent.TrackerMessage{Title: up.Config.Title, Description: up.Config.Description}) - up.Tracker.Tracker.IncrementSection(nil) + percent.ReportStatusChange(up.Tracker, percent.TrackerMessage{Title: up.Config.Title, Description: up.Config.Description}) + up.Tracker.IncrementSection(nil) var err error = nil for _, user := range up.users { - up.Tracker.Tracker.IncrementSection(err) - percent.ChangeTrackerMessageFancy(*up.Tracker.Writer, up.Tracker.Tracker, up.Tracker.Progress, percent.TrackerMessage{Title: up.Config.Title, Description: *up.Config.UserDescription + " " + user.Name}) + up.Tracker.IncrementSection(err) + percent.ReportStatusChange(up.Tracker, percent.TrackerMessage{Title: up.Config.Title, Description: *up.Config.UserDescription + " " + user.Name}) } return &finalOutput, nil } - percent.ChangeTrackerMessageFancy(*up.Tracker.Writer, up.Tracker.Tracker, up.Tracker.Progress, percent.TrackerMessage{Title: up.Config.Title, Description: up.Config.Description}) + percent.ReportStatusChange(up.Tracker, percent.TrackerMessage{Title: up.Config.Title, Description: up.Config.Description}) cli := []string{up.binaryPath, "update", "-y", "--noninteractive"} flatpakCmd := exec.Command(cli[0], cli[1:]...) - out, err := session.RunLog(up.Config.logger, slog.LevelDebug, flatpakCmd) + out, err := session.RunLog(up.Config.Logger, slog.LevelDebug, flatpakCmd) tmpout := CommandOutput{}.New(out, err) tmpout.Context = up.Config.Description tmpout.Cli = cli @@ -89,11 +85,11 @@ func (up FlatpakUpdater) Update() (*[]CommandOutput, error) { err = nil for _, user := range up.users { - up.Tracker.Tracker.IncrementSection(err) + up.Tracker.IncrementSection(err) context := *up.Config.UserDescription + " " + user.Name - percent.ChangeTrackerMessageFancy(*up.Tracker.Writer, up.Tracker.Tracker, up.Tracker.Progress, percent.TrackerMessage{Title: up.Config.Title, Description: context}) + percent.ReportStatusChange(up.Tracker, percent.TrackerMessage{Title: up.Config.Title, Description: context}) cli := []string{up.binaryPath, "update", "-y"} - out, err := session.RunUID(up.Config.logger, slog.LevelDebug, user.UID, cli, nil) + out, err := session.RunUID(up.Config.Logger, slog.LevelDebug, user.UID, cli, nil) tmpout = CommandOutput{}.New(out, err) tmpout.Context = context tmpout.Cli = cli @@ -102,11 +98,3 @@ func (up FlatpakUpdater) Update() (*[]CommandOutput, error) { } return &finalOutput, nil } - -func (up *FlatpakUpdater) Logger() *slog.Logger { - return up.Config.logger -} - -func (up *FlatpakUpdater) SetLogger(logger *slog.Logger) { - up.Config.logger = logger -} diff --git a/drv/flatpak/flatpak_test.go b/drv/flatpak/flatpak_test.go new file mode 100644 index 0000000..6974b6f --- /dev/null +++ b/drv/flatpak/flatpak_test.go @@ -0,0 +1,56 @@ +package flatpak_test + +import ( + "log" + "testing" + + "github.com/ublue-os/uupd/drv/flatpak" + "github.com/ublue-os/uupd/drv/generic" + appLogging "github.com/ublue-os/uupd/pkg/logging" + "github.com/ublue-os/uupd/pkg/session" +) + +func InitBaseConfig() flatpak.FlatpakUpdater { + var initConfiguration = generic.UpdaterInitConfiguration{ + DryRun: false, + Ci: false, + Verbose: false, + Environment: nil, + Logger: appLogging.NewMuteLogger(), + } + driv, _ := flatpak.FlatpakUpdater{}.New(initConfiguration) + return driv +} + +func TestProperSteps(t *testing.T) { + updater := InitBaseConfig() + updater.Config.Enabled = false + + if updater.Steps() != 0 { + t.Fatalf("Expected no steps when module is disabled") + } + + updater.Config.Enabled = true + if updater.Steps() == 0 { + t.Fatalf("Expected steps to be added") + } +} + +func TestProperUserSteps(t *testing.T) { + updater := InitBaseConfig() + + mockUser := []session.User{ + {UID: 0, Name: "root"}, + {UID: 1, Name: "roote"}, + {UID: 2, Name: "rooto"}, + } + updater.SetUsers(mockUser) + + if reported := updater.Steps(); reported != 1+len(mockUser) { + log.Fatalf("Incorrect number of steps for users: %d", reported) + } + updater.Config.Enabled = false + if reported := updater.Steps(); reported != 0 { + log.Fatalf("Incorrect number of steps for users: %d", reported) + } +} diff --git a/drv/generic.go b/drv/generic/generic.go similarity index 67% rename from drv/generic.go rename to drv/generic/generic.go index ae0e4c7..512847c 100644 --- a/drv/generic.go +++ b/drv/generic/generic.go @@ -1,12 +1,10 @@ -package drv +package generic import ( "log/slog" "os" "strings" - "github.com/jedib0t/go-pretty/v6/progress" - "github.com/ublue-os/uupd/pkg/percent" "github.com/ublue-os/uupd/pkg/session" ) @@ -20,11 +18,19 @@ type UpdaterInitConfiguration struct { Logger *slog.Logger } -func GetEnvironment(data []string, getkeyval func(item string) (key, val string)) map[string]string { +func EnvOrFallback(environment EnvironmentMap, key string, fallback string) string { + validCase, exists := environment[key] + if exists && validCase != "" { + return validCase + } + return fallback +} + +func GetEnvironment(data []string) map[string]string { items := make(map[string]string) for _, item := range data { - key, val := getkeyval(item) - items[key] = val + splits := strings.Split(item, "=") + items[splits[0]] = splits[1] } return items } @@ -32,12 +38,7 @@ func GetEnvironment(data []string, getkeyval func(item string) (key, val string) func (up UpdaterInitConfiguration) New() *UpdaterInitConfiguration { up.DryRun = false up.Ci = false - up.Environment = GetEnvironment(os.Environ(), func(item string) (key, val string) { - splits := strings.Split(item, "=") - key = splits[0] - val = splits[1] - return - }) + up.Environment = GetEnvironment(os.Environ()) up.Logger = slog.Default() return &up @@ -60,11 +61,6 @@ func (output CommandOutput) New(out []byte, err error) *CommandOutput { } } -func (out *CommandOutput) SetFailureContext(context string) { - out.Failure = true - out.Context = context -} - type DriverConfiguration struct { Title string Description string @@ -72,16 +68,10 @@ type DriverConfiguration struct { MultiUser bool DryRun bool Environment EnvironmentMap `json:"-"` - logger *slog.Logger `json:"-"` + Logger *slog.Logger `json:"-"` UserDescription *string } -type TrackerConfiguration struct { - Tracker *percent.IncrementTracker - Writer *progress.Writer - Progress bool -} - type UpdateDriver interface { Steps() int Check() (bool, error) diff --git a/drv/generic/generic_test.go b/drv/generic/generic_test.go new file mode 100644 index 0000000..e07b3d2 --- /dev/null +++ b/drv/generic/generic_test.go @@ -0,0 +1,40 @@ +package generic_test + +import ( + "log" + "testing" + + "github.com/ublue-os/uupd/drv/generic" +) + +func TestFallBack(t *testing.T) { + var environment generic.EnvironmentMap = generic.EnvironmentMap{ + "TEST_FALLBACK_GOOD": "true", + } + if value := generic.EnvOrFallback(environment, "TEST_FALLBACK_GOOD", "FALSE"); value != "true" { + t.Fatalf("Getting the proper value fails, %s", value) + } + if value := generic.EnvOrFallback(environment, "TEST_FALLBACK_BAD", "FALSE"); value != "FALSE" { + t.Fatalf("Getting the fallback fails, %s", value) + } +} + +func TestProperEnvironment(t *testing.T) { + valuesExpected := map[string]string{ + "SIGMA": "true", + "CHUD": "false", + "AMOGUS": "sus", + "NOTHING": "", + } + + for key, value := range valuesExpected { + testmap := generic.GetEnvironment([]string{key + "=" + value}) + valuegot, exists := testmap[key] + if !exists { + log.Fatalf("Could not get environment variable at all: %s", key) + } + if valuegot != value { + log.Fatalf("Value gotten from variable was not expected: Got %s, Expected %s", valuegot, value) + } + } +} diff --git a/drv/rpm-ostree.go b/drv/rpmostree/rpmostree.go similarity index 66% rename from drv/rpm-ostree.go rename to drv/rpmostree/rpmostree.go index 23cb540..637b786 100644 --- a/drv/rpm-ostree.go +++ b/drv/rpmostree/rpmostree.go @@ -1,15 +1,17 @@ -package drv +package rpmostree // Temporary: WILL get removed at some point. // FIXME: Remove this on Spring 2025 when we all move to dnf5 and bootc ideally import ( "encoding/json" - "github.com/ublue-os/uupd/pkg/session" "log/slog" "os/exec" "strings" "time" + + . "github.com/ublue-os/uupd/drv/generic" + "github.com/ublue-os/uupd/pkg/session" ) type rpmOstreeStatus struct { @@ -23,15 +25,19 @@ type RpmOstreeUpdater struct { BinaryPath string } +// Checks if it is at least a month old considering how that works +func IsOutdatedOneMonthTimestamp(current time.Time, target time.Time) bool { + return target.Before(current.AddDate(0, -1, 0)) +} + func (up RpmOstreeUpdater) Outdated() (bool, error) { if up.Config.DryRun { return false, nil } - oneMonthAgo := time.Now().AddDate(0, -1, 0) var timestamp time.Time cmd := exec.Command(up.BinaryPath, "status", "--json", "--booted") - out, err := session.RunLog(up.Config.logger, slog.LevelDebug, cmd) + out, err := session.RunLog(up.Config.Logger, slog.LevelDebug, cmd) if err != nil { return false, err } @@ -42,7 +48,7 @@ func (up RpmOstreeUpdater) Outdated() (bool, error) { } timestamp = time.Unix(status.Deployments[0].Timestamp, 0).UTC() - return timestamp.Before(oneMonthAgo), nil + return IsOutdatedOneMonthTimestamp(time.Now(), timestamp), nil } func (up RpmOstreeUpdater) Update() (*[]CommandOutput, error) { @@ -50,7 +56,7 @@ func (up RpmOstreeUpdater) Update() (*[]CommandOutput, error) { var cmd *exec.Cmd binaryPath := up.BinaryPath cli := []string{binaryPath, "upgrade"} - out, err := session.RunLog(up.Config.logger, slog.LevelDebug, cmd) + out, err := session.RunLog(up.Config.Logger, slog.LevelDebug, cmd) tmpout := CommandOutput{}.New(out, err) tmpout.Cli = cli tmpout.Failure = err != nil @@ -66,20 +72,6 @@ func (up RpmOstreeUpdater) Steps() int { return 0 } -func BootcCompatible(binaryPath string) (bool, error) { - cmd := exec.Command(binaryPath, "status", "--format=json") - out, err := cmd.CombinedOutput() - if err != nil { - return false, nil - } - var status bootcStatus - err = json.Unmarshal(out, &status) - if err != nil { - return false, nil - } - return !(status.Status.Booted.Incompatible || status.Status.Staged.Incompatible), nil -} - func (up RpmOstreeUpdater) New(config UpdaterInitConfiguration) (RpmOstreeUpdater, error) { up.Config = DriverConfiguration{ Title: "System", @@ -88,17 +80,8 @@ func (up RpmOstreeUpdater) New(config UpdaterInitConfiguration) (RpmOstreeUpdate DryRun: config.DryRun, Environment: config.Environment, } - up.Config.logger = config.Logger.With(slog.String("module", strings.ToLower(up.Config.Title))) - if up.Config.DryRun { - return up, nil - } - - binaryPath, exists := up.Config.Environment["UUPD_RPMOSTREE_BINARY"] - if !exists || binaryPath == "" { - up.BinaryPath = "/usr/bin/rpm-ostree" - } else { - up.BinaryPath = binaryPath - } + up.Config.Logger = config.Logger.With(slog.String("module", strings.ToLower(up.Config.Title))) + up.BinaryPath = EnvOrFallback(up.Config.Environment, "UUPD_RPMOSTREE_BINARY", "/usr/bin/rpm-ostree") return up, nil } @@ -117,14 +100,6 @@ func (up RpmOstreeUpdater) Check() (bool, error) { } updateNecessary := strings.Contains(string(out), "AvailableUpdate") - up.Config.logger.Debug("Executed update check", slog.String("output", string(out)), slog.Bool("update", updateNecessary)) + up.Config.Logger.Debug("Executed update check", slog.String("output", string(out)), slog.Bool("update", updateNecessary)) return updateNecessary, nil } - -func (up *RpmOstreeUpdater) Logger() *slog.Logger { - return up.Config.logger -} - -func (up *RpmOstreeUpdater) SetLogger(logger *slog.Logger) { - up.Config.logger = logger -} diff --git a/drv/rpmostree/rpmostree_test.go b/drv/rpmostree/rpmostree_test.go new file mode 100644 index 0000000..56c1522 --- /dev/null +++ b/drv/rpmostree/rpmostree_test.go @@ -0,0 +1,35 @@ +package rpmostree_test + +import ( + "testing" + + "github.com/ublue-os/uupd/drv/generic" + "github.com/ublue-os/uupd/drv/rpmostree" + appLogging "github.com/ublue-os/uupd/pkg/logging" +) + +func InitBaseConfig() rpmostree.RpmOstreeUpdater { + var initConfiguration = generic.UpdaterInitConfiguration{ + DryRun: false, + Ci: false, + Verbose: false, + Environment: nil, + Logger: appLogging.NewMuteLogger(), + } + driv, _ := rpmostree.RpmOstreeUpdater{}.New(initConfiguration) + return driv +} + +func TestProperSteps(t *testing.T) { + rpmostreeUpdater := InitBaseConfig() + rpmostreeUpdater.Config.Enabled = false + + if rpmostreeUpdater.Steps() != 0 { + t.Fatalf("Expected no steps when module is disabled") + } + + rpmostreeUpdater.Config.Enabled = true + if rpmostreeUpdater.Steps() == 0 { + t.Fatalf("Expected steps to be added") + } +} diff --git a/drv/system.go b/drv/system/system.go similarity index 52% rename from drv/system.go rename to drv/system/system.go index 3cf6cc6..55649c3 100644 --- a/drv/system.go +++ b/drv/system/system.go @@ -1,4 +1,4 @@ -package drv +package system import ( "encoding/json" @@ -7,6 +7,8 @@ import ( "strings" "time" + . "github.com/ublue-os/uupd/drv/generic" + "github.com/ublue-os/uupd/drv/rpmostree" "github.com/ublue-os/uupd/pkg/session" ) @@ -34,8 +36,6 @@ type SystemUpdateDriver interface { Outdated() (bool, error) Check() (bool, error) Update() (*[]CommandOutput, error) - Logger() *slog.Logger - SetLogger(value *slog.Logger) } type SystemUpdater struct { @@ -47,23 +47,24 @@ func (up SystemUpdater) Outdated() (bool, error) { if up.Config.DryRun { return false, nil } - oneMonthAgo := time.Now().AddDate(0, -1, 0) - var timestamp time.Time + cmd := exec.Command(up.BinaryPath, "status", "--format=json") out, err := cmd.CombinedOutput() if err != nil { return false, err } + var status bootcStatus err = json.Unmarshal(out, &status) if err != nil { return false, err } - timestamp, err = time.Parse(time.RFC3339Nano, status.Status.Booted.Image.Timestamp) + + timestamp, err := time.Parse(time.RFC3339Nano, status.Status.Booted.Image.Timestamp) if err != nil { return false, nil } - return timestamp.Before(oneMonthAgo), nil + return rpmostree.IsOutdatedOneMonthTimestamp(time.Now(), timestamp), nil } func (up SystemUpdater) Update() (*[]CommandOutput, error) { @@ -71,13 +72,12 @@ func (up SystemUpdater) Update() (*[]CommandOutput, error) { var cmd *exec.Cmd binaryPath := up.BinaryPath cli := []string{binaryPath, "upgrade", "--quiet"} - up.Config.logger.Debug("Executing update", slog.Any("cli", cli)) + up.Config.Logger.Debug("Executing update", slog.Any("cli", cli)) cmd = exec.Command(cli[0], cli[1:]...) - out, err := session.RunLog(up.Config.logger, slog.LevelDebug, cmd) + out, err := session.RunLog(up.Config.Logger, slog.LevelDebug, cmd) tmpout := CommandOutput{}.New(out, err) - if err != nil { - tmpout.SetFailureContext("System update") - } + tmpout.Failure = err != nil + tmpout.Context = "System Update" finalOutput = append(finalOutput, *tmpout) return &finalOutput, err } @@ -97,17 +97,8 @@ func (up SystemUpdater) New(config UpdaterInitConfiguration) (SystemUpdater, err DryRun: config.DryRun, Environment: config.Environment, } - up.Config.logger = config.Logger.With(slog.String("module", strings.ToLower(up.Config.Title))) - if up.Config.DryRun { - return up, nil - } - - bootcBinaryPath, exists := up.Config.Environment["UUPD_BOOTC_BINARY"] - if !exists || bootcBinaryPath == "" { - up.BinaryPath = "/usr/bin/bootc" - } else { - up.BinaryPath = bootcBinaryPath - } + up.Config.Logger = config.Logger.With(slog.String("module", strings.ToLower(up.Config.Title))) + up.BinaryPath = EnvOrFallback(config.Environment, "UUPD_BOOTC_BINARY", "/usr/bin/bootc") return up, nil } @@ -122,15 +113,61 @@ func (up SystemUpdater) Check() (bool, error) { if err != nil { return true, err } + updateNecessary := !strings.Contains(string(out), "No changes in:") - up.Config.logger.Debug("Executed update check", slog.String("output", string(out)), slog.Bool("update", updateNecessary)) + up.Config.Logger.Debug("Executed update check", slog.String("output", string(out)), slog.Bool("update", updateNecessary)) return updateNecessary, nil } -func (up *SystemUpdater) Logger() *slog.Logger { - return up.Config.logger +func BootcCompatible(binaryPath string) (bool, error) { + cmd := exec.Command(binaryPath, "status", "--format=json") + out, err := cmd.CombinedOutput() + if err != nil { + return false, nil + } + var status bootcStatus + err = json.Unmarshal(out, &status) + if err != nil { + return false, nil + } + return !(status.Status.Booted.Incompatible || status.Status.Staged.Incompatible), nil } -func (up *SystemUpdater) SetLogger(logger *slog.Logger) { - up.Config.logger = logger +func InitializeSystemDriver(initConfiguration UpdaterInitConfiguration) (SystemUpdateDriver, DriverConfiguration, bool, error) { + var enableUpd bool = true + + rpmOstreeUpdater, err := rpmostree.RpmOstreeUpdater{}.New(initConfiguration) + if err != nil { + enableUpd = false + } + + systemUpdater, err := SystemUpdater{}.New(initConfiguration) + if err != nil { + enableUpd = false + } + + isBootc, err := BootcCompatible(systemUpdater.BinaryPath) + if err != nil { + isBootc = false + } + + if !isBootc { + slog.Debug("Using rpm-ostree fallback as system driver") + } + + // The system driver to be applied needs to have the correct "enabled" value since it will NOT update from here onwards. + systemUpdater.Config.Enabled = systemUpdater.Config.Enabled && isBootc && enableUpd + rpmOstreeUpdater.Config.Enabled = rpmOstreeUpdater.Config.Enabled && !isBootc && enableUpd + + var finalConfig DriverConfiguration + var mainSystemDriver SystemUpdateDriver + if isBootc { + mainSystemDriver = &systemUpdater + finalConfig = systemUpdater.Config + } else { + mainSystemDriver = &rpmOstreeUpdater + finalConfig = systemUpdater.Config + } + + return mainSystemDriver, finalConfig, isBootc, err } diff --git a/drv/system/system_test.go b/drv/system/system_test.go new file mode 100644 index 0000000..ce162e0 --- /dev/null +++ b/drv/system/system_test.go @@ -0,0 +1,35 @@ +package system_test + +import ( + "testing" + + "github.com/ublue-os/uupd/drv/generic" + "github.com/ublue-os/uupd/drv/system" + appLogging "github.com/ublue-os/uupd/pkg/logging" +) + +func InitBaseConfig() system.SystemUpdater { + var initConfiguration = generic.UpdaterInitConfiguration{ + DryRun: false, + Ci: false, + Verbose: false, + Environment: nil, + Logger: appLogging.NewMuteLogger(), + } + driv, _ := system.SystemUpdater{}.New(initConfiguration) + return driv +} + +func TestProperSteps(t *testing.T) { + systemUpdater := InitBaseConfig() + systemUpdater.Config.Enabled = false + + if systemUpdater.Steps() != 0 { + t.Fatalf("Expected no steps when module is disabled") + } + + systemUpdater.Config.Enabled = true + if systemUpdater.Steps() == 0 { + t.Fatalf("Expected steps to be added") + } +} diff --git a/go.mod b/go.mod index cc7f8bf..0128381 100644 --- a/go.mod +++ b/go.mod @@ -4,10 +4,8 @@ go 1.22.9 require ( github.com/godbus/dbus/v5 v5.1.0 - github.com/jedib0t/go-pretty/v6 v6.6.3 github.com/shirou/gopsutil/v4 v4.24.10 github.com/spf13/cobra v1.8.1 - golang.org/x/term v0.26.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -16,9 +14,7 @@ require ( github.com/go-ole/go-ole v1.2.6 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect - github.com/rivo/uniseg v0.2.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect diff --git a/go.sum b/go.sum index e589ba7..a12d58d 100644 --- a/go.sum +++ b/go.sum @@ -12,18 +12,12 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jedib0t/go-pretty/v6 v6.6.3 h1:nGqgS0tgIO1Hto47HSaaK4ac/I/Bu7usmdD3qvs0WvM= -github.com/jedib0t/go-pretty/v6 v6.6.3/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shirou/gopsutil/v4 v4.24.10 h1:7VOzPtfw/5YDU+jLEoBwXwxJbQetULywoSV4RYY7HkM= github.com/shirou/gopsutil/v4 v4.24.10/go.mod h1:s4D/wg+ag4rG0WO7AiTj2BeYCRhym0vM7DHbZRxnIT8= @@ -45,8 +39,6 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= -golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/justfile b/justfile index 1d16408..f66e4f4 100644 --- a/justfile +++ b/justfile @@ -76,3 +76,21 @@ lint: release: goreleaser + +test directory="": + #!/usr/bin/env bash + if [ "{{directory}}" != "" ] ; then + go test -v -cover ./{{directory}}/... + else + go test -v -cover ./... + fi + +test-coverage directory="": + #!/usr/bin/env bash + t="/tmp/go-cover.$$.tmp" + + if [ "{{directory}}" != "" ] ; then + go test -v -coverprofile=$t ./{{directory}}/... $@ && go tool cover -html=$t && unlink $t + else + go test -v -coverprofile=$t ./... $@ && go tool cover -html=$t && unlink $t + fi diff --git a/pkg/filelock/filelock.go b/pkg/filelock/filelock.go index 88d6147..3300a53 100644 --- a/pkg/filelock/filelock.go +++ b/pkg/filelock/filelock.go @@ -1,20 +1,14 @@ package filelock import ( - "fmt" - "io" + "errors" "os" "syscall" "time" ) -const fileLockPath string = "/run/uupd.lock" - func IsFileLocked(file *os.File) bool { - lock := syscall.Flock_t{ - Type: syscall.F_WRLCK, - Whence: io.SeekStart, - } + lock := syscall.Flock_t{} err := syscall.FcntlFlock(file.Fd(), syscall.F_GETLK, &lock) if err != nil { return false @@ -22,34 +16,43 @@ func IsFileLocked(file *os.File) bool { return lock.Type != syscall.F_UNLCK } -func AcquireLock() (*os.File, error) { - file, err := os.OpenFile(fileLockPath, os.O_RDONLY|os.O_CREATE|os.O_TRUNC, 0600) - if err != nil { - return nil, err - } +func GetDefaultLockfile() string { + return "/run/uupd.lock" +} - timeout := 5.0 - startTime := time.Now() - var lockFile *os.File +func OpenLockfile(filepath string) (*os.File, error) { + return os.OpenFile(filepath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0666) +} + +type TimeoutConfig struct { + Tries int +} - for time.Since(startTime).Seconds() < timeout { - err = syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB) - if err == nil { - lockFile = file +func AcquireLock(file *os.File, timeout TimeoutConfig) error { + maxTries := timeout.Tries + tries := 0 + fileLocked := false + + for tries < maxTries { + err := syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB) + if fileLocked = err == nil; fileLocked { break } + tries += 1 time.Sleep(1 * time.Second) } - if lockFile == nil { - file.Close() - return nil, fmt.Errorf("Could not acquire lock at %s", fileLockPath) + if !fileLocked { + return errors.New("Could not acquire lock to file") } - return lockFile, nil + return nil } func ReleaseLock(file *os.File) error { + if err := syscall.Flock(int(file.Fd()), syscall.LOCK_UN); err != nil { + return err + } return syscall.Close(int(file.Fd())) } diff --git a/pkg/filelock/filelock_test.go b/pkg/filelock/filelock_test.go new file mode 100644 index 0000000..86d5373 --- /dev/null +++ b/pkg/filelock/filelock_test.go @@ -0,0 +1,48 @@ +package filelock_test + +import ( + "testing" + + "github.com/ublue-os/uupd/pkg/filelock" +) + +const LockForTest = "/tmp/uupd-sigmatest.lock" + +var defaultTimeout = filelock.TimeoutConfig{1} + +func TestFullLock(t *testing.T) { + file, err := filelock.OpenLockfile(LockForTest) + if err != nil { + t.Fatalf("Failed even opening the file, %v", err) + } + defer file.Close() + err = filelock.AcquireLock(file, defaultTimeout) + if err != nil { + t.Fatalf("Failed acquiring lock file, %v", err) + } + err = filelock.ReleaseLock(file) + if err != nil { + t.Fatal("Failed releasing lock file") + } +} + +func TestLockAcquired(t *testing.T) { + file1, err := filelock.OpenLockfile(LockForTest) + if err != nil { + t.Fatalf("Failed even opening the file, %v", err) + } + defer file1.Close() + err = filelock.AcquireLock(file1, defaultTimeout) + if err != nil { + t.Fatalf("Failed acquiring lock file, %v", err) + } + file2, err := filelock.OpenLockfile(LockForTest) + if err != nil { + t.Fatalf("Failed even opening the file, %v", err) + } + defer file2.Close() + err = filelock.AcquireLock(file2, defaultTimeout) + if err == nil { + t.Fatalf("Expected failing to lock file, %v", err) + } +} diff --git a/pkg/logging/userHandler.go b/pkg/logging/userHandler.go index 1452a6b..e828218 100644 --- a/pkg/logging/userHandler.go +++ b/pkg/logging/userHandler.go @@ -96,7 +96,13 @@ func (h *UserHandler) Handle(ctx context.Context, r slog.Record) error { colorize(lightGray, r.Time.Format(timeFormat)), level, colorize(white, r.Message), - colorize(darkGray, "\n"+trimmedBytes), + colorize(darkGray, + func() string { + if len(trimmedBytes) > 0 && trimmedBytes != "{}" { + return colorize(darkGray, "\n"+trimmedBytes) + } + return "" + }()), ) return nil diff --git a/pkg/percent/colorpicker.go b/pkg/percent/colorpicker.go deleted file mode 100644 index 32043e4..0000000 --- a/pkg/percent/colorpicker.go +++ /dev/null @@ -1,70 +0,0 @@ -package percent - -import ( - "math" - - . "github.com/jedib0t/go-pretty/v6/text" -) - -// Accent color portal return as of xdg-desktop-portal-gnome 47.1 -type Accent struct { - Type string `json:"type"` - Data []struct { - Type string `json:"type"` - Data [3]float64 `json:"data"` - } `json:"data"` -} - -// Colors taken straight from GNOME 47 accent colors using this command: -// busctl --user call org.freedesktop.portal.Desktop /org/freedesktop/portal/desktop org.freedesktop.portal.Settings ReadOne 'ss' 'org.freedesktop.appearance' 'accent-color' -// This is as close as we can map the colors as possible afaik - Pink and Magenta DO look a like, and thats kind of a problem -var colorMap = map[Color][3]float64{ - FgHiBlack: {0, 0, 0}, - FgHiBlue: {0.207843, 0.517647, 0.894118}, - FgHiCyan: {0.129412, 0.564706, 0.643137}, - FgHiGreen: {0.227451, 0.580392, 0.290196}, - FgHiYellow: {0.784314, 0.533333, 0}, - FgHiRed: {0.901961, 0.176471, 0.258824}, - FgHiMagenta: {0.568627, 0.254902, 0.67451}, - FgHiWhite: {0.435294, 0.513726, 0.588235}, -} - -// Calculates the Euclidean distance between two colors -func colorDistance(c1, c2 [3]float64) float64 { - return math.Sqrt( - math.Pow(c1[0]-c2[0], 2) + - math.Pow(c1[1]-c2[1], 2) + - math.Pow(c1[2]-c2[2], 2), - ) -} - -func findClosestColor(rgb [3]float64) (Color, Color) { - var closestColor Color - minDistance := math.MaxFloat64 - - for color, predefinedRGB := range colorMap { - distance := colorDistance(rgb, predefinedRGB) - if distance < minDistance { - minDistance = distance - closestColor = color - } - } - - nonHiColor, isHiColor := hiToNonHiMap[closestColor] - if isHiColor { - return closestColor, nonHiColor - } - - return closestColor, closestColor -} - -var hiToNonHiMap = map[Color]Color{ - FgHiBlack: FgBlack, - FgHiRed: FgRed, - FgHiGreen: FgGreen, - FgHiYellow: FgYellow, - FgHiBlue: FgBlue, - FgHiMagenta: FgMagenta, - FgHiCyan: FgCyan, - FgHiWhite: FgWhite, -} diff --git a/pkg/percent/incrementer.go b/pkg/percent/incrementer.go new file mode 100644 index 0000000..725c7d4 --- /dev/null +++ b/pkg/percent/incrementer.go @@ -0,0 +1,17 @@ +package percent + +type Incrementer struct { + DoneIncrements int + MaxIncrements int +} + +func (it *Incrementer) IncrementSection(err error) { + if int64(it.DoneIncrements)+int64(1) > int64(it.MaxIncrements) { + return + } + it.DoneIncrements += 1 +} + +func (it *Incrementer) CurrentStep() int { + return it.DoneIncrements +} diff --git a/pkg/percent/incrementer_test.go b/pkg/percent/incrementer_test.go new file mode 100644 index 0000000..0d1422b --- /dev/null +++ b/pkg/percent/incrementer_test.go @@ -0,0 +1,43 @@ +package percent_test + +import ( + "math" + "testing" + + "github.com/ublue-os/uupd/pkg/percent" +) + +func InitIncrementer(max int) percent.Incrementer { + return percent.Incrementer{MaxIncrements: max} +} + +func TestOverflow(t *testing.T) { + max := 3 + tracker := InitIncrementer(max) + + iter := 0 + for iter < max { + tracker.IncrementSection(nil) + iter++ + } + // +1 so that it overflows + tracker.IncrementSection(nil) + + if tracker.CurrentStep() > max { + t.Fatalf("Incremented with overflow. Expected: %d, Got: %d", max, tracker.CurrentStep()) + } +} + +func TestProperIncrement(t *testing.T) { + num := math.MaxInt8 + tracker := InitIncrementer(num) + + iter := 0 + for iter < num { + if tracker.CurrentStep() != iter { + t.Fatalf("Misstep increment. Expected: %d, Got: %d", iter, tracker.CurrentStep()) + } + tracker.IncrementSection(nil) + iter++ + } +} diff --git a/pkg/percent/progressmanager.go b/pkg/percent/progressmanager.go index d85aee2..a14f26a 100644 --- a/pkg/percent/progressmanager.go +++ b/pkg/percent/progressmanager.go @@ -1,148 +1,21 @@ package percent import ( - "encoding/json" - "fmt" "log/slog" - "math" - "os" - "strconv" - "time" - - "github.com/jedib0t/go-pretty/v6/progress" - "github.com/jedib0t/go-pretty/v6/text" - "github.com/ublue-os/uupd/pkg/session" ) -type Incrementer struct { - doneIncrements int - MaxIncrements int -} - -type IncrementTracker struct { - Tracker *progress.Tracker - incrementer *Incrementer -} - -var CuteColors = progress.StyleColors{ - Message: text.Colors{text.FgWhite}, - Error: text.Colors{text.FgRed}, - Percent: text.Colors{text.FgHiBlue}, - Pinned: text.Colors{text.BgHiBlack, text.FgWhite, text.Bold}, - Stats: text.Colors{text.FgHiBlack}, - Time: text.Colors{text.FgBlue}, - Tracker: text.Colors{text.FgHiBlue}, - Value: text.Colors{text.FgBlue}, - Speed: text.Colors{text.FgBlue}, -} - -func NewProgressWriter() progress.Writer { - pw := progress.NewWriter() - - pw.SetTrackerLength(25) - pw.Style().Visibility.TrackerOverall = true - pw.Style().Visibility.Time = true - pw.Style().Visibility.Tracker = true - pw.Style().Visibility.Value = true - pw.SetMessageLength(32) - pw.SetSortBy(progress.SortByPercentDsc) - pw.SetStyle(progress.StyleBlocks) - pw.SetTrackerPosition(progress.PositionRight) - pw.SetUpdateFrequency(time.Millisecond * 100) - pw.Style().Options.PercentFormat = "%4.1f%%" - - pw.Style().Colors = CuteColors - - var targetUser int - baseUser, exists := os.LookupEnv("SUDO_UID") - if !exists || baseUser == "" { - targetUser = 0 - } else { - var err error - targetUser, err = strconv.Atoi(baseUser) - if err != nil { - slog.Error("Failed parsing provided user as UID", slog.String("user_value", baseUser)) - return pw - } - } - - if targetUser != 0 { - var accentColorSet progress.StyleColors - // Get accent color: https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Settings.html - cli := []string{"busctl", "--user", "--json=short", "call", "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop", "org.freedesktop.portal.Settings", "ReadOne", "ss", "org.freedesktop.appearance", "accent-color"} - out, err := session.RunUID(nil, slog.LevelDebug, targetUser, cli, nil) - if err != nil { - return pw - } - var accent Accent - err = json.Unmarshal(out, &accent) - if err != nil { - return pw - } - - raw_color := accent.Data[0].Data - - highlightColor, lowColor := findClosestColor(raw_color) - - validHighlightColor := text.Colors{highlightColor} - validLowColor := text.Colors{lowColor} - - accentColorSet.Percent = validHighlightColor - accentColorSet.Tracker = validHighlightColor - accentColorSet.Time = validLowColor - accentColorSet.Value = validLowColor - accentColorSet.Speed = validLowColor - pw.Style().Colors = accentColorSet - } - return pw -} - -func NewIncrementTracker(tracker *progress.Tracker, max_increments int) *IncrementTracker { - return &IncrementTracker{ - Tracker: tracker, - incrementer: &Incrementer{MaxIncrements: max_increments}, - } -} - type TrackerMessage struct { Title string Description string } -func ChangeTrackerMessageFancy(writer progress.Writer, tracker *IncrementTracker, progress bool, message TrackerMessage) { - if !progress { - slog.Info("Updating", - slog.String("title", message.Title), - slog.String("description", message.Description), - slog.Int64("progress", tracker.Tracker.Value()), - slog.Int64("total", tracker.Tracker.Total), - ) - return - } - percentage := math.Round((float64(tracker.Tracker.Value()) / float64(tracker.Tracker.Total)) * 100) - fmt.Printf("\033]9;4;1;%d\a", int(percentage)) - finalMessage := fmt.Sprintf("Updating %s (%s)", message.Description, message.Title) - writer.SetMessageLength(len(finalMessage)) - tracker.Tracker.UpdateMessage(finalMessage) -} - -func (it *IncrementTracker) IncrementSection(err error) { - var increment_step float64 - if it.incrementer.doneIncrements == 0 { - increment_step = 1 - } else { - increment_step = float64(it.Tracker.Total / int64(it.incrementer.MaxIncrements)) - } - if err == nil { - it.Tracker.Increment(int64(increment_step)) - } else { - it.Tracker.IncrementWithError(int64(increment_step)) - } - it.incrementer.doneIncrements++ -} - -func (it *IncrementTracker) CurrentStep() int { - return it.incrementer.doneIncrements +func ReportStatusChange(tracker *Incrementer, message TrackerMessage) { + slog.Info("Updating", + slog.String("title", message.Title), + slog.String("description", message.Description), + slog.Int("progress", tracker.CurrentStep()), + slog.Int("total", tracker.MaxIncrements), + ) } func ResetOscProgress() { diff --git a/pkg/session/session.go b/pkg/session/session.go index bbc5c1f..12fe655 100644 --- a/pkg/session/session.go +++ b/pkg/session/session.go @@ -2,6 +2,7 @@ package session import ( "bufio" + "context" "fmt" "io" "log/slog" @@ -25,14 +26,19 @@ func RunLog(logger *slog.Logger, level slog.Level, command *exec.Cmd) ([]byte, e stdout, _ := command.StdoutPipe() stderr, _ := command.StderrPipe() multiReader := io.MultiReader(stdout, stderr) + actuallogger := slog.Default() - command.Start() + if err := command.Start(); err != nil { + actuallogger.Warn("Error occoured starting external command", slog.Any("error", err)) + } scanner := bufio.NewScanner(multiReader) scanner.Split(bufio.ScanLines) for scanner.Scan() { - logger.With(slog.Bool("subcommand", true)).Log(nil, level, scanner.Text()) + actuallogger.Log(context.TODO(), level, scanner.Text()) + } + if err := command.Wait(); err != nil { + actuallogger.Warn("Error occoured while waiting for external command", slog.Any("error", err)) } - command.Wait() return scanner.Bytes(), scanner.Err() } @@ -56,6 +62,23 @@ func RunUID(logger *slog.Logger, level slog.Level, uid int, command []string, en return RunLog(logger, level, cmd) } +func ParseUserFromVariant(uidVariant dbus.Variant, nameVariant dbus.Variant) (User, error) { + uid, ok := uidVariant.Value().(uint32) + if !ok { + return User{}, fmt.Errorf("invalid UID type, expected uint32") + } + + name, ok := nameVariant.Value().(string) + if !ok { + return User{}, fmt.Errorf("invalid Name type, expected string") + } + + return User{ + UID: int(uid), + Name: name, + }, nil +} + func ListUsers() ([]User, error) { conn, err := dbus.SystemBus() if err != nil { @@ -72,38 +95,20 @@ func ListUsers() ([]User, error) { var users []User for _, data := range resp { - if len(data) < 2 { - return []User{}, fmt.Errorf("Malformed dbus response") - } - uidVariant := data[0] - nameVariant := data[1] - - uid, ok := uidVariant.Value().(uint32) - if !ok { - return []User{}, fmt.Errorf("invalid UID type, expected uint32") + parsed, err := ParseUserFromVariant(data[0], data[1]) + if err != nil { + return nil, err } - name, ok := nameVariant.Value().(string) - if !ok { - return []User{}, fmt.Errorf("invalid Name type, expected string") - } - - users = append(users, User{ - UID: int(uid), - Name: name, - }) + users = append(users, parsed) } return users, nil } -func Notify(summary string, body string) error { - users, err := ListUsers() - if err != nil { - return err - } +func Notify(users []User, summary string, body string) error { for _, user := range users { // we don't care if these exit - _, _ = RunUID(slog.Default(), slog.LevelDebug, user.UID, []string{"/usr/bin/notify-send", "--app-name", "uupd", summary, body}, nil) + _, _ = RunUID(nil, slog.LevelDebug, user.UID, []string{"/usr/bin/notify-send", "--app-name", "uupd", summary, body}, nil) } return nil } diff --git a/pkg/session/session_test.go b/pkg/session/session_test.go new file mode 100644 index 0000000..68935c8 --- /dev/null +++ b/pkg/session/session_test.go @@ -0,0 +1,75 @@ +package session_test + +import ( + "fmt" + "math" + "testing" + + "github.com/godbus/dbus/v5" + "github.com/ublue-os/uupd/pkg/session" +) + +func TestUserParsingInvalidUID(t *testing.T) { + t.Parallel() + testVariants := []dbus.Variant{ + dbus.MakeVariant(0.3), + dbus.MakeVariant(-1), + dbus.MakeVariant(math.MaxInt), + dbus.MakeVariant(math.MinInt), + } + + userName := dbus.MakeVariant("root") + for _, uidVariant := range testVariants { + t.Run(fmt.Sprintf("variant: %v", uidVariant.Value()), func(t *testing.T) { + t.Parallel() + _, err := session.ParseUserFromVariant(uidVariant, userName) + if err == nil { + t.Fatalf("Parser accepted invalid input: %v %v", uidVariant, userName) + } + }) + } +} + +func TestUserParsingInvalidName(t *testing.T) { + t.Parallel() + testVariants := []dbus.Variant{ + dbus.MakeVariant(0.3), + dbus.MakeVariant(-1), + dbus.MakeVariant(math.MaxInt), + dbus.MakeVariant(math.MinInt), + } + + uidVariant := dbus.MakeVariant(uint32(0)) + for _, nameVariant := range testVariants { + t.Run(fmt.Sprintf("variant: %v", uidVariant.Value()), func(t *testing.T) { + t.Parallel() + _, err := session.ParseUserFromVariant(uidVariant, nameVariant) + if err == nil { + t.Fatalf("Parser accepted invalid input: %v", err) + } + }) + } +} + +func TestUserParsingValidUser(t *testing.T) { + t.Parallel() + testVariants := []struct { + UidVariant dbus.Variant + NameVariant dbus.Variant + }{ + {dbus.MakeVariant(uint32(0)), dbus.MakeVariant("root")}, + {dbus.MakeVariant(uint32(10)), dbus.MakeVariant("bob")}, + {dbus.MakeVariant(uint32(20)), dbus.MakeVariant("beatryz")}, + {dbus.MakeVariant(uint32(math.MaxUint16)), dbus.MakeVariant("zorg_the_destroyer")}, + } + + for _, variant := range testVariants { + t.Run(fmt.Sprintf("variant: %v", variant.NameVariant.Value()), func(t *testing.T) { + t.Parallel() + _, err := session.ParseUserFromVariant(variant.UidVariant, variant.NameVariant) + if err != nil { + t.Fatalf("Parser rejected valid input: %v", err) + } + }) + } +} diff --git a/uupd.service b/uupd.service index 6bbf5a0..aac4cba 100644 --- a/uupd.service +++ b/uupd.service @@ -3,4 +3,4 @@ Description=Universal Blue Update Oneshot Service [Service] Type=oneshot -ExecStart=/usr/bin/uupd -c +ExecStart=/usr/bin/uupd --log-level debug --json --hw-check diff --git a/uupd.spec b/uupd.spec index af02be7..946bf4f 100644 --- a/uupd.spec +++ b/uupd.spec @@ -14,9 +14,9 @@ License: Apache-2.0 BuildRequires: golang BuildRequires: systemd-rpm-macros -Requires: bootc -Requires: distrobox -Requires: flatpak +Recommends: bootc +Recommends: distrobox +Recommends: flatpak Requires: libnotify Requires: systemd Provides: %{name} = %{version} @@ -39,8 +39,7 @@ install -Dpm 644 %{name}.timer %{buildroot}%{_unitdir}/%{name}.timer install -Dpm 644 %{name}.rules %{buildroot}%{_sysconfdir}/polkit-1/rules.d/%{name}.rules %check -# go test should be here if you have tests, e.g. -# go test -v ./... +go test -v ./... %post %systemd_post %{name}.timer