kvm/internal/ota/state.go

220 lines
6.8 KiB
Go

package ota
import (
"net/http"
"sync"
"time"
"github.com/Masterminds/semver/v3"
"github.com/rs/zerolog"
)
var (
availableComponents = []string{"app", "system"}
defaultComponents = map[string]string{
"app": "",
"system": "",
}
)
// UpdateMetadata represents the metadata of an update
type UpdateMetadata struct {
AppVersion string `json:"appVersion"`
AppURL string `json:"appUrl"`
AppHash string `json:"appHash"`
SystemVersion string `json:"systemVersion"`
SystemURL string `json:"systemUrl"`
SystemHash string `json:"systemHash"`
}
// LocalMetadata represents the local metadata of the system
type LocalMetadata struct {
AppVersion string `json:"appVersion"`
SystemVersion string `json:"systemVersion"`
}
// UpdateStatus represents the current update status
type UpdateStatus struct {
Local *LocalMetadata `json:"local"`
Remote *UpdateMetadata `json:"remote"`
SystemUpdateAvailable bool `json:"systemUpdateAvailable"`
AppUpdateAvailable bool `json:"appUpdateAvailable"`
// for backwards compatibility
Error string `json:"error,omitempty"`
}
// PostRebootAction represents the action to be taken after a reboot
// It is used to redirect the user to a specific page after a reboot
type PostRebootAction struct {
HealthCheck string `json:"healthCheck"` // The health check URL to call after the reboot
RedirectTo string `json:"redirectTo"` // The URL to redirect to after the reboot
}
// componentUpdateStatus represents the status of a component update
type componentUpdateStatus struct {
pending bool
available bool
availableReason string // why the component is available or not available
version string
localVersion string
url string
hash string
downloadProgress float32
downloadFinishedAt time.Time
verificationProgress float32
verifiedAt time.Time
updateProgress float32
updatedAt time.Time
dependsOn []string
}
func (c *componentUpdateStatus) getZerologLogger(l *zerolog.Logger) *zerolog.Logger {
logger := l.With().
Bool("pending", c.pending).
Bool("available", c.available).
Str("availableReason", c.availableReason).
Str("version", c.version).
Str("localVersion", c.localVersion).
Str("url", c.url).
Str("hash", c.hash).
Float32("downloadProgress", c.downloadProgress).
Time("downloadFinishedAt", c.downloadFinishedAt).
Float32("verificationProgress", c.verificationProgress).
Time("verifiedAt", c.verifiedAt).
Float32("updateProgress", c.updateProgress).
Time("updatedAt", c.updatedAt).
Strs("dependsOn", c.dependsOn).
Logger()
return &logger
}
// HwRebootFunc is a function that reboots the hardware
type HwRebootFunc func(force bool, postRebootAction *PostRebootAction, delay time.Duration) error
// ResetConfigFunc is a function that resets the config
type ResetConfigFunc func() error
// GetHTTPClientFunc is a function that returns the HTTP client
type GetHTTPClientFunc func() *http.Client
// OnStateUpdateFunc is a function that updates the state of the OTA
type OnStateUpdateFunc func(state *RPCState)
// OnProgressUpdateFunc is a function that updates the progress of the OTA
type OnProgressUpdateFunc func(progress float32)
// GetLocalVersionFunc is a function that returns the local version of the system and app
type GetLocalVersionFunc func() (systemVersion *semver.Version, appVersion *semver.Version, err error)
// State represents the current OTA state for the UI
type State struct {
releaseAPIEndpoint string
l *zerolog.Logger
mu sync.Mutex
updating bool
error string
metadataFetchedAt time.Time
rebootNeeded bool
componentUpdateStatuses map[string]componentUpdateStatus
client GetHTTPClientFunc
reboot HwRebootFunc
getLocalVersion GetLocalVersionFunc
onStateUpdate OnStateUpdateFunc
resetConfig ResetConfigFunc
}
func toUpdateStatus(appUpdate *componentUpdateStatus, systemUpdate *componentUpdateStatus, error string) *UpdateStatus {
return &UpdateStatus{
Local: &LocalMetadata{
AppVersion: appUpdate.localVersion,
SystemVersion: systemUpdate.localVersion,
},
Remote: &UpdateMetadata{
AppVersion: appUpdate.version,
AppURL: appUpdate.url,
AppHash: appUpdate.hash,
SystemVersion: systemUpdate.version,
SystemURL: systemUpdate.url,
SystemHash: systemUpdate.hash,
},
SystemUpdateAvailable: systemUpdate.available,
AppUpdateAvailable: appUpdate.available,
Error: error,
}
}
// ToUpdateStatus converts the State to the UpdateStatus
func (s *State) ToUpdateStatus() *UpdateStatus {
appUpdate, ok := s.componentUpdateStatuses["app"]
if !ok {
return nil
}
systemUpdate, ok := s.componentUpdateStatuses["system"]
if !ok {
return nil
}
return toUpdateStatus(&appUpdate, &systemUpdate, s.error)
}
// IsUpdatePending returns true if an update is pending
func (s *State) IsUpdatePending() bool {
return s.updating
}
// Options represents the options for the OTA state
type Options struct {
Logger *zerolog.Logger
GetHTTPClient GetHTTPClientFunc
GetLocalVersion GetLocalVersionFunc
OnStateUpdate OnStateUpdateFunc
OnProgressUpdate OnProgressUpdateFunc
HwReboot HwRebootFunc
ReleaseAPIEndpoint string
ResetConfig ResetConfigFunc
SkipConfirmSystem bool
}
// NewState creates a new OTA state
func NewState(opts Options) *State {
components := make(map[string]componentUpdateStatus)
for _, component := range availableComponents {
components[component] = componentUpdateStatus{}
}
s := &State{
l: opts.Logger,
client: opts.GetHTTPClient,
reboot: opts.HwReboot,
onStateUpdate: opts.OnStateUpdate,
getLocalVersion: opts.GetLocalVersion,
componentUpdateStatuses: components,
releaseAPIEndpoint: opts.ReleaseAPIEndpoint,
resetConfig: opts.ResetConfig,
}
if !opts.SkipConfirmSystem {
go s.confirmCurrentSystem()
}
return s
}
// appUpdateStatus.url = remoteMetadata.AppURL
// appUpdateStatus.hash = remoteMetadata.AppHash
// appUpdateStatus.version = remoteMetadata.AppVersion
// systemUpdateStatus.url = remoteMetadata.SystemURL
// systemUpdateStatus.hash = remoteMetadata.SystemHash
// systemUpdateStatus.version = remoteMetadata.SystemVersion
// // Get remote versions
// systemVersionRemote, err := semver.NewVersion(remoteMetadata.SystemVersion)
//
// if err != nil {
// err = fmt.Errorf("error parsing remote system version: %w", err)
// return err
// }
//
// systemUpdateStatus.available = systemVersionRemote.GreaterThan(systemVersionLocal)