mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-18 08:16:39 +00:00
Feature/update check (#1232)
Periodically fetch the latest available version, and the UI will shows a new menu for the download link. It checks both the daemon version and the UI version.
This commit is contained in:
184
version/update.go
Normal file
184
version/update.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
goversion "github.com/hashicorp/go-version"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
fetchPeriod = 30 * time.Minute
|
||||
)
|
||||
|
||||
var (
|
||||
versionURL = "https://pkgs.netbird.io/releases/latest/version"
|
||||
)
|
||||
|
||||
// Update fetch the version info periodically and notify the onUpdateListener in case the UI version or the
|
||||
// daemon version are deprecated
|
||||
type Update struct {
|
||||
uiVersion *goversion.Version
|
||||
daemonVersion *goversion.Version
|
||||
latestAvailable *goversion.Version
|
||||
versionsLock sync.Mutex
|
||||
|
||||
fetchTicker *time.Ticker
|
||||
fetchDone chan struct{}
|
||||
|
||||
onUpdateListener func()
|
||||
listenerLock sync.Mutex
|
||||
}
|
||||
|
||||
// NewUpdate instantiate Update and start to fetch the new version information
|
||||
func NewUpdate() *Update {
|
||||
currentVersion, err := goversion.NewVersion(version)
|
||||
if err != nil {
|
||||
currentVersion, _ = goversion.NewVersion("0.0.0")
|
||||
}
|
||||
|
||||
latestAvailable, _ := goversion.NewVersion("0.0.0")
|
||||
|
||||
u := &Update{
|
||||
latestAvailable: latestAvailable,
|
||||
uiVersion: currentVersion,
|
||||
fetchTicker: time.NewTicker(fetchPeriod),
|
||||
fetchDone: make(chan struct{}),
|
||||
}
|
||||
go u.startFetcher()
|
||||
return u
|
||||
}
|
||||
|
||||
// StopWatch stop the version info fetch loop
|
||||
func (u *Update) StopWatch() {
|
||||
u.fetchTicker.Stop()
|
||||
|
||||
select {
|
||||
case u.fetchDone <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// SetDaemonVersion update the currently running daemon version. If new version is available it will trigger
|
||||
// the onUpdateListener
|
||||
func (u *Update) SetDaemonVersion(newVersion string) bool {
|
||||
daemonVersion, err := goversion.NewVersion(newVersion)
|
||||
if err != nil {
|
||||
daemonVersion, _ = goversion.NewVersion("0.0.0")
|
||||
}
|
||||
|
||||
u.versionsLock.Lock()
|
||||
if u.daemonVersion != nil && u.daemonVersion.Equal(daemonVersion) {
|
||||
u.versionsLock.Unlock()
|
||||
return false
|
||||
}
|
||||
|
||||
u.daemonVersion = daemonVersion
|
||||
u.versionsLock.Unlock()
|
||||
return u.checkUpdate()
|
||||
}
|
||||
|
||||
// SetOnUpdateListener set new update listener
|
||||
func (u *Update) SetOnUpdateListener(updateFn func()) {
|
||||
u.listenerLock.Lock()
|
||||
defer u.listenerLock.Unlock()
|
||||
|
||||
u.onUpdateListener = updateFn
|
||||
if u.isUpdateAvailable() {
|
||||
u.onUpdateListener()
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Update) startFetcher() {
|
||||
changed := u.fetchVersion()
|
||||
if changed {
|
||||
u.checkUpdate()
|
||||
}
|
||||
|
||||
select {
|
||||
case <-u.fetchDone:
|
||||
return
|
||||
case <-u.fetchTicker.C:
|
||||
changed := u.fetchVersion()
|
||||
if changed {
|
||||
u.checkUpdate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Update) fetchVersion() bool {
|
||||
resp, err := http.Get(versionURL)
|
||||
if err != nil {
|
||||
log.Errorf("failed to fetch version info: %s", err)
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
log.Errorf("invalid status code: %d", resp.StatusCode)
|
||||
return false
|
||||
}
|
||||
|
||||
if resp.ContentLength > 100 {
|
||||
log.Errorf("too large response: %d", resp.ContentLength)
|
||||
return false
|
||||
}
|
||||
|
||||
content, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Errorf("failed to read content: %s", err)
|
||||
return false
|
||||
}
|
||||
|
||||
latestAvailable, err := goversion.NewVersion(string(content))
|
||||
if err != nil {
|
||||
log.Errorf("failed to parse the version string: %s", err)
|
||||
return false
|
||||
}
|
||||
|
||||
u.versionsLock.Lock()
|
||||
defer u.versionsLock.Unlock()
|
||||
|
||||
if u.latestAvailable.Equal(latestAvailable) {
|
||||
return false
|
||||
}
|
||||
u.latestAvailable = latestAvailable
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (u *Update) checkUpdate() bool {
|
||||
if !u.isUpdateAvailable() {
|
||||
return false
|
||||
}
|
||||
|
||||
u.listenerLock.Lock()
|
||||
defer u.listenerLock.Unlock()
|
||||
if u.onUpdateListener == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
go u.onUpdateListener()
|
||||
return true
|
||||
}
|
||||
|
||||
func (u *Update) isUpdateAvailable() bool {
|
||||
u.versionsLock.Lock()
|
||||
defer u.versionsLock.Unlock()
|
||||
|
||||
if u.latestAvailable.GreaterThan(u.uiVersion) {
|
||||
return true
|
||||
}
|
||||
|
||||
if u.daemonVersion == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if u.latestAvailable.GreaterThan(u.daemonVersion) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user