mirror of
https://github.com/netbirdio/netbird.git
synced 2024-11-25 09:33:24 +01:00
185 lines
3.7 KiB
Go
185 lines
3.7 KiB
Go
|
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
|
||
|
}
|