[WIP] Add update notifications

This commit is contained in:
Vladimir Stoilov
2024-09-26 10:43:47 +03:00
parent 7cf20b2cf8
commit c9631daa3e

View File

@@ -15,20 +15,11 @@ import (
const ( const (
updateTaskRepeatDuration = 1 * time.Hour updateTaskRepeatDuration = 1 * time.Hour
updateAvailableNotificationID = "updates:update-available" updateAvailableNotificationID = "updates:update-available"
updateFailedNotificationID = "updates:update-failed"
// ResourceUpdateEvent is emitted every time the // ResourceUpdateEvent is emitted every time the
// updater successfully performed a resource update. // updater successfully performed a resource update.
// ResourceUpdateEvent is emitted even if no new
// versions are available. Subscribers are expected
// to check if new versions of their resources are
// available by checking File.UpgradeAvailable().
ResourceUpdateEvent = "resource update" ResourceUpdateEvent = "resource update"
// VersionUpdateEvent is emitted every time a new
// version of a monitored resource is selected.
// During module initialization VersionUpdateEvent
// is also emitted.
VersionUpdateEvent = "active version update"
) )
// UserAgent is an HTTP User-Agent that is used to add // UserAgent is an HTTP User-Agent that is used to add
@@ -57,7 +48,6 @@ type Updates struct {
upgraderWorkerMgr *mgr.WorkerMgr upgraderWorkerMgr *mgr.WorkerMgr
EventResourcesUpdated *mgr.EventMgr[struct{}] EventResourcesUpdated *mgr.EventMgr[struct{}]
EventVersionsUpdated *mgr.EventMgr[struct{}]
registry Registry registry Registry
downloader Downloader downloader Downloader
@@ -76,7 +66,6 @@ func New(instance instance, name string, index UpdateIndex) (*Updates, error) {
states: m.NewStateMgr(), states: m.NewStateMgr(),
EventResourcesUpdated: mgr.NewEventMgr[struct{}](ResourceUpdateEvent, m), EventResourcesUpdated: mgr.NewEventMgr[struct{}](ResourceUpdateEvent, m),
EventVersionsUpdated: mgr.NewEventMgr[struct{}](VersionUpdateEvent, m),
autoApply: index.AutoApply, autoApply: index.AutoApply,
needsRestart: index.NeedsRestart, needsRestart: index.NeedsRestart,
@@ -84,9 +73,8 @@ func New(instance instance, name string, index UpdateIndex) (*Updates, error) {
instance: instance, instance: instance,
} }
// Events // Workers
module.updateCheckWorkerMgr = m.NewWorkerMgr("update checker", module.checkForUpdates, nil) module.updateCheckWorkerMgr = m.NewWorkerMgr("update checker", module.checkForUpdates, nil).Repeat(updateTaskRepeatDuration)
module.updateCheckWorkerMgr.Repeat(updateTaskRepeatDuration)
module.upgraderWorkerMgr = m.NewWorkerMgr("upgrader", module.applyUpdates, nil) module.upgraderWorkerMgr = m.NewWorkerMgr("upgrader", module.applyUpdates, nil)
var err error var err error
@@ -101,17 +89,18 @@ func New(instance instance, name string, index UpdateIndex) (*Updates, error) {
} }
func (u *Updates) checkForUpdates(wc *mgr.WorkerCtx) error { func (u *Updates) checkForUpdates(wc *mgr.WorkerCtx) error {
// Download the index file.
err := u.downloader.downloadIndexFile(wc.Ctx()) err := u.downloader.downloadIndexFile(wc.Ctx())
if err != nil { if err != nil {
return fmt.Errorf("failed to download index file: %w", err) return fmt.Errorf("failed to download index file: %w", err)
} }
// Check if there is a new version.
defer u.EventResourcesUpdated.Submit(struct{}{})
if u.downloader.version.LessThanOrEqual(u.registry.version) { if u.downloader.version.LessThanOrEqual(u.registry.version) {
log.Infof("updates: check compete: no new updates") log.Infof("updates: check compete: no new updates")
return nil return nil
} }
// Download the new version.
downloadBundle := u.downloader.bundle downloadBundle := u.downloader.bundle
log.Infof("updates: check complete: downloading new version: %s %s", downloadBundle.Name, downloadBundle.Version) log.Infof("updates: check complete: downloading new version: %s %s", downloadBundle.Name, downloadBundle.Version)
err = u.downloader.copyMatchingFilesFromCurrent(u.registry.files) err = u.downloader.copyMatchingFilesFromCurrent(u.registry.files)
@@ -123,9 +112,11 @@ func (u *Updates) checkForUpdates(wc *mgr.WorkerCtx) error {
log.Errorf("updates: failed to download update: %s", err) log.Errorf("updates: failed to download update: %s", err)
} else { } else {
if u.autoApply { if u.autoApply {
// Trigger upgrade.
u.upgraderWorkerMgr.Go() u.upgraderWorkerMgr.Go()
} else { } else {
notifications.NotifyPrompt(updateAvailableNotificationID, "Update available", "Apply update and restart?", notifications.Action{ // Notify the user with option to trigger upgrade.
notifications.NotifyPrompt(updateAvailableNotificationID, "New update is available.", fmt.Sprintf("%s %s", downloadBundle.Name, downloadBundle.Version), notifications.Action{
ID: "apply", ID: "apply",
Text: "Apply", Text: "Apply",
Type: notifications.ActionTypeWebhook, Type: notifications.ActionTypeWebhook,
@@ -142,16 +133,26 @@ func (u *Updates) checkForUpdates(wc *mgr.WorkerCtx) error {
func (u *Updates) applyUpdates(_ *mgr.WorkerCtx) error { func (u *Updates) applyUpdates(_ *mgr.WorkerCtx) error {
currentBundle := u.registry.bundle currentBundle := u.registry.bundle
downloadBundle := u.downloader.bundle downloadBundle := u.downloader.bundle
if u.downloader.version.LessThanOrEqual(u.registry.version) {
// No new version, silently return.
return nil
}
log.Infof("update: starting update: %s %s -> %s", currentBundle.Name, currentBundle.Version, downloadBundle.Version) log.Infof("update: starting update: %s %s -> %s", currentBundle.Name, currentBundle.Version, downloadBundle.Version)
err := u.registry.performRecoverableUpgrade(u.downloader.dir, u.downloader.indexFile) err := u.registry.performRecoverableUpgrade(u.downloader.dir, u.downloader.indexFile)
if err != nil { if err != nil {
// TODO(vladimir): Send notification to UI // Notify the user that update failed.
log.Errorf("updates: failed to apply updates: %s", err) notifications.NotifyPrompt(updateFailedNotificationID, "Failed to apply update.", err.Error())
} else if u.needsRestart { return fmt.Errorf("updates: failed to apply updates: %w", err)
// TODO(vladimir): Prompt user to restart? }
u.instance.Restart()
if u.needsRestart {
// Perform restart.
u.instance.Restart()
} else {
// Update completed and no restart was needed. Submit an event.
u.EventResourcesUpdated.Submit(struct{}{})
} }
u.EventResourcesUpdated.Submit(struct{}{})
return nil return nil
} }