Merge pull request #1681 from calmh/major-upgrade
Allow major upgrades [v0.10]
This commit is contained in:
@@ -618,7 +618,7 @@ func restGetUpgrade(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Error(w, upgrade.ErrUpgradeUnsupported.Error(), 500)
|
http.Error(w, upgrade.ErrUpgradeUnsupported.Error(), 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rel, err := upgrade.LatestRelease(strings.Contains(Version, "-beta"))
|
rel, err := upgrade.LatestRelease(Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), 500)
|
http.Error(w, err.Error(), 500)
|
||||||
return
|
return
|
||||||
@@ -626,7 +626,8 @@ func restGetUpgrade(w http.ResponseWriter, r *http.Request) {
|
|||||||
res := make(map[string]interface{})
|
res := make(map[string]interface{})
|
||||||
res["running"] = Version
|
res["running"] = Version
|
||||||
res["latest"] = rel.Tag
|
res["latest"] = rel.Tag
|
||||||
res["newer"] = upgrade.CompareVersions(rel.Tag, Version) == 1
|
res["newer"] = upgrade.CompareVersions(rel.Tag, Version) == upgrade.Newer
|
||||||
|
res["majorNewer"] = upgrade.CompareVersions(rel.Tag, Version) == upgrade.MajorNewer
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
json.NewEncoder(w).Encode(res)
|
json.NewEncoder(w).Encode(res)
|
||||||
@@ -660,14 +661,14 @@ func restGetLang(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func restPostUpgrade(w http.ResponseWriter, r *http.Request) {
|
func restPostUpgrade(w http.ResponseWriter, r *http.Request) {
|
||||||
rel, err := upgrade.LatestRelease(strings.Contains(Version, "-beta"))
|
rel, err := upgrade.LatestRelease(Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Warnln("getting latest release:", err)
|
l.Warnln("getting latest release:", err)
|
||||||
http.Error(w, err.Error(), 500)
|
http.Error(w, err.Error(), 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if upgrade.CompareVersions(rel.Tag, Version) == 1 {
|
if upgrade.CompareVersions(rel.Tag, Version) > upgrade.Equal {
|
||||||
err = upgrade.To(rel)
|
err = upgrade.To(rel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Warnln("upgrading:", err)
|
l.Warnln("upgrading:", err)
|
||||||
|
|||||||
@@ -324,7 +324,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if doUpgrade || doUpgradeCheck {
|
if doUpgrade || doUpgradeCheck {
|
||||||
rel, err := upgrade.LatestRelease(IsBeta)
|
rel, err := upgrade.LatestRelease(Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Fatalln("Upgrade:", err) // exits 1
|
l.Fatalln("Upgrade:", err) // exits 1
|
||||||
}
|
}
|
||||||
@@ -1057,7 +1057,7 @@ func autoUpgrade() {
|
|||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
}
|
}
|
||||||
|
|
||||||
rel, err := upgrade.LatestRelease(IsBeta)
|
rel, err := upgrade.LatestRelease(Version)
|
||||||
if err == upgrade.ErrUpgradeUnsupported {
|
if err == upgrade.ErrUpgradeUnsupported {
|
||||||
events.Default.Unsubscribe(sub)
|
events.Default.Unsubscribe(sub)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"A new major version may not be compatible with previous versions.": "A new major version may not be compatible with previous versions.",
|
||||||
"API Key": "API Key",
|
"API Key": "API Key",
|
||||||
"About": "About",
|
"About": "About",
|
||||||
"Add": "Add",
|
"Add": "Add",
|
||||||
@@ -72,6 +73,7 @@
|
|||||||
"Latest Release": "Latest Release",
|
"Latest Release": "Latest Release",
|
||||||
"Local Discovery": "Local Discovery",
|
"Local Discovery": "Local Discovery",
|
||||||
"Local State": "Local State",
|
"Local State": "Local State",
|
||||||
|
"Major Upgrade": "Major Upgrade",
|
||||||
"Maximum Age": "Maximum Age",
|
"Maximum Age": "Maximum Age",
|
||||||
"Metadata Only": "Metadata Only",
|
"Metadata Only": "Metadata Only",
|
||||||
"Move to top of queue": "Move to top of queue",
|
"Move to top of queue": "Move to top of queue",
|
||||||
@@ -92,11 +94,13 @@
|
|||||||
"Override Changes": "Override Changes",
|
"Override Changes": "Override Changes",
|
||||||
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for",
|
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for",
|
||||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Path where versions should be stored (leave empty for the default .stversions folder in the folder).",
|
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Path where versions should be stored (leave empty for the default .stversions folder in the folder).",
|
||||||
|
"Please consult the release notes before performing a major upgrade.": "Please consult the release notes before performing a major upgrade.",
|
||||||
"Please wait": "Please wait",
|
"Please wait": "Please wait",
|
||||||
"Preview": "Preview",
|
"Preview": "Preview",
|
||||||
"Preview Usage Report": "Preview Usage Report",
|
"Preview Usage Report": "Preview Usage Report",
|
||||||
"Quick guide to supported patterns": "Quick guide to supported patterns",
|
"Quick guide to supported patterns": "Quick guide to supported patterns",
|
||||||
"RAM Utilization": "RAM Utilization",
|
"RAM Utilization": "RAM Utilization",
|
||||||
|
"Release Notes": "Release Notes",
|
||||||
"Rescan": "Rescan",
|
"Rescan": "Rescan",
|
||||||
"Rescan All": "Rescan All",
|
"Rescan All": "Rescan All",
|
||||||
"Rescan Interval": "Rescan Interval",
|
"Rescan Interval": "Rescan Interval",
|
||||||
@@ -155,10 +159,12 @@
|
|||||||
"The number of versions must be a number and cannot be blank.": "The number of versions must be a number and cannot be blank.",
|
"The number of versions must be a number and cannot be blank.": "The number of versions must be a number and cannot be blank.",
|
||||||
"The rescan interval must be a non-negative number of seconds.": "The rescan interval must be a non-negative number of seconds.",
|
"The rescan interval must be a non-negative number of seconds.": "The rescan interval must be a non-negative number of seconds.",
|
||||||
"The rescan interval must be at least 5 seconds.": "The rescan interval must be at least 5 seconds.",
|
"The rescan interval must be at least 5 seconds.": "The rescan interval must be at least 5 seconds.",
|
||||||
|
"This is a major version upgrade.": "This is a major version upgrade.",
|
||||||
"Unknown": "Unknown",
|
"Unknown": "Unknown",
|
||||||
"Unshared": "Unshared",
|
"Unshared": "Unshared",
|
||||||
"Unused": "Unused",
|
"Unused": "Unused",
|
||||||
"Up to Date": "Up to Date",
|
"Up to Date": "Up to Date",
|
||||||
|
"Upgrade": "Upgrade",
|
||||||
"Upgrade To {%version%}": "Upgrade To {{version}}",
|
"Upgrade To {%version%}": "Upgrade To {{version}}",
|
||||||
"Upgrading": "Upgrading",
|
"Upgrading": "Upgrading",
|
||||||
"Upload Rate": "Upload Rate",
|
"Upload Rate": "Upload Rate",
|
||||||
|
|||||||
@@ -38,6 +38,12 @@
|
|||||||
<span translate translate-value-version="{{upgradeInfo.latest}}">Upgrade To {%version%}</span>
|
<span translate translate-value-version="{{upgradeInfo.latest}}">Upgrade To {%version%}</span>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
<li ng-if="upgradeInfo && upgradeInfo.majorNewer">
|
||||||
|
<button type="button" class="btn navbar-btn btn-danger btn-sm" href="" ng-click="upgradeMajor()">
|
||||||
|
<span class="glyphicon glyphicon-chevron-up"></span> 
|
||||||
|
<span translate translate-value-version="{{upgradeInfo.latest}}">Upgrade To {%version%}</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><span class="glyphicon glyphicon-cog" aria-label="Edit"></span></a>
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><span class="glyphicon glyphicon-cog" aria-label="Edit"></span></a>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
@@ -460,6 +466,35 @@
|
|||||||
<img ng-if="myID" class="center-block img-thumbnail" src="qr/?text={{myID}}"/>
|
<img ng-if="myID" class="center-block img-thumbnail" src="qr/?text={{myID}}"/>
|
||||||
</modal>
|
</modal>
|
||||||
|
|
||||||
|
<!-- Major upgrade modal -->
|
||||||
|
|
||||||
|
<div id="majorUpgrade" class="modal fade" tabindex="-1" data-backdrop="true" data-keyboard="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header alert alert-danger">
|
||||||
|
<h4 class="modal-title">
|
||||||
|
<span ng-if="icon" class="glyphicon glyphicon-chevron-up"></span>
|
||||||
|
<span translate>Major Upgrade</span>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>
|
||||||
|
<span translate>This is a major version upgrade.</span>
|
||||||
|
<span translate>A new major version may not be compatible with previous versions.</span>
|
||||||
|
<span translate>Please consult the release notes before performing a major upgrade.</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href="https://github.com/syncthing/syncthing/releases/latest" target="_blank" translate>Release Notes</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-primary btn-sm" ng-click="upgrade()"><span class="glyphicon glyphicon-ok"></span> <span translate>Upgrade</span></button>
|
||||||
|
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span> <span translate>Close</span></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Device editor modal -->
|
<!-- Device editor modal -->
|
||||||
|
|
||||||
<div id="editDevice" class="modal fade" tabindex="-1">
|
<div id="editDevice" class="modal fade" tabindex="-1">
|
||||||
|
|||||||
@@ -672,6 +672,7 @@ angular.module('syncthing.core')
|
|||||||
|
|
||||||
$scope.upgrade = function () {
|
$scope.upgrade = function () {
|
||||||
restarting = true;
|
restarting = true;
|
||||||
|
$('#majorUpgrade').modal('hide');
|
||||||
$('#upgrading').modal();
|
$('#upgrading').modal();
|
||||||
$http.post(urlbase + '/upgrade').success(function () {
|
$http.post(urlbase + '/upgrade').success(function () {
|
||||||
$('#restarting').modal();
|
$('#restarting').modal();
|
||||||
@@ -681,6 +682,10 @@ angular.module('syncthing.core')
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.upgradeMajor = function () {
|
||||||
|
$('#majorUpgrade').modal();
|
||||||
|
};
|
||||||
|
|
||||||
$scope.shutdown = function () {
|
$scope.shutdown = function () {
|
||||||
restarting = true;
|
restarting = true;
|
||||||
$http.post(urlbase + '/shutdown').success(function () {
|
$http.post(urlbase + '/shutdown').success(function () {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
+3814
File diff suppressed because it is too large
Load Diff
@@ -23,36 +23,70 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Returns the latest release, including prereleases or not depending on the argument
|
// Returns the latest releases, including prereleases or not depending on the argument
|
||||||
func LatestRelease(prerelease bool) (Release, error) {
|
func LatestGithubReleases(version string) ([]Release, error) {
|
||||||
resp, err := http.Get("https://api.github.com/repos/syncthing/syncthing/releases?per_page=10")
|
resp, err := http.Get("https://api.github.com/repos/syncthing/syncthing/releases?per_page=30")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Release{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if resp.StatusCode > 299 {
|
if resp.StatusCode > 299 {
|
||||||
return Release{}, fmt.Errorf("API call returned HTTP error: %s", resp.Status)
|
return nil, fmt.Errorf("API call returned HTTP error: %s", resp.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
var rels []Release
|
var rels []Release
|
||||||
json.NewDecoder(resp.Body).Decode(&rels)
|
json.NewDecoder(resp.Body).Decode(&rels)
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
|
|
||||||
|
return rels, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type SortByRelease []Release
|
||||||
|
|
||||||
|
func (s SortByRelease) Len() int {
|
||||||
|
return len(s)
|
||||||
|
}
|
||||||
|
func (s SortByRelease) Swap(i, j int) {
|
||||||
|
s[i], s[j] = s[j], s[i]
|
||||||
|
}
|
||||||
|
func (s SortByRelease) Less(i, j int) bool {
|
||||||
|
return CompareVersions(s[i].Tag, s[j].Tag) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func LatestRelease(version string) (Release, error) {
|
||||||
|
rels, _ := LatestGithubReleases(version)
|
||||||
|
return SelectLatestRelease(version, rels)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SelectLatestRelease(version string, rels []Release) (Release, error) {
|
||||||
if len(rels) == 0 {
|
if len(rels) == 0 {
|
||||||
return Release{}, ErrVersionUnknown
|
return Release{}, ErrVersionUnknown
|
||||||
}
|
}
|
||||||
|
|
||||||
if prerelease {
|
sort.Sort(SortByRelease(rels))
|
||||||
// We are a beta version. Use the latest.
|
// Check for a beta build
|
||||||
return rels[0], nil
|
beta := strings.Contains(version, "-beta")
|
||||||
}
|
|
||||||
|
|
||||||
// We are a regular release. Only consider non-prerelease versions for upgrade.
|
|
||||||
for _, rel := range rels {
|
for _, rel := range rels {
|
||||||
if !rel.Prerelease {
|
if rel.Prerelease && !beta {
|
||||||
return rel, nil
|
continue
|
||||||
|
}
|
||||||
|
for _, asset := range rel.Assets {
|
||||||
|
assetName := path.Base(asset.Name)
|
||||||
|
// Check for the architecture
|
||||||
|
expectedRelease := releaseName(rel.Tag)
|
||||||
|
if debug {
|
||||||
|
l.Debugf("expected release asset %q", expectedRelease)
|
||||||
|
}
|
||||||
|
if debug {
|
||||||
|
l.Debugln("considering release", assetName)
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(assetName, expectedRelease) {
|
||||||
|
return rel, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Release{}, ErrVersionUnknown
|
return Release{}, ErrVersionUnknown
|
||||||
|
|||||||
@@ -4,11 +4,17 @@
|
|||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
// +build !noupgrade
|
||||||
|
|
||||||
package upgrade
|
package upgrade
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
var testcases = []struct {
|
var versions = []struct {
|
||||||
a, b string
|
a, b string
|
||||||
r Relation
|
r Relation
|
||||||
}{
|
}{
|
||||||
@@ -27,6 +33,7 @@ var testcases = []struct {
|
|||||||
{"0.10.0", "0.2.0", MajorNewer},
|
{"0.10.0", "0.2.0", MajorNewer},
|
||||||
{"30.10.0", "4.9.0", MajorNewer},
|
{"30.10.0", "4.9.0", MajorNewer},
|
||||||
{"0.9.0-beta7", "0.9.0-beta6", Newer},
|
{"0.9.0-beta7", "0.9.0-beta6", Newer},
|
||||||
|
{"0.9.0-beta7", "1.0.0-alpha", MajorOlder},
|
||||||
{"1.0.0-alpha", "1.0.0-alpha.1", Older},
|
{"1.0.0-alpha", "1.0.0-alpha.1", Older},
|
||||||
{"1.0.0-alpha.1", "1.0.0-alpha.beta", Older},
|
{"1.0.0-alpha.1", "1.0.0-alpha.beta", Older},
|
||||||
{"1.0.0-alpha.beta", "1.0.0-beta", Older},
|
{"1.0.0-alpha.beta", "1.0.0-beta", Older},
|
||||||
@@ -44,9 +51,46 @@ var testcases = []struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCompareVersions(t *testing.T) {
|
func TestCompareVersions(t *testing.T) {
|
||||||
for _, tc := range testcases {
|
for _, v := range versions {
|
||||||
if r := CompareVersions(tc.a, tc.b); r != tc.r {
|
if r := CompareVersions(v.a, v.b); r != v.r {
|
||||||
t.Errorf("compareVersions(%q, %q): %d != %d", tc.a, tc.b, r, tc.r)
|
t.Errorf("compareVersions(%q, %q): %d != %d", v.a, v.b, r, v.r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var upgrades = map[string]string{
|
||||||
|
"v0.10.21": "v0.10.30",
|
||||||
|
"v0.10.29": "v0.10.30",
|
||||||
|
"v0.10.31": "v0.10.30",
|
||||||
|
"v0.10.0-alpha": "v0.10.30",
|
||||||
|
"v0.10.0-beta": "v0.11.0-beta0",
|
||||||
|
"v0.11.0-beta0+40-g53cb66e-dirty": "v0.11.0-beta0",
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGithubRelease(t *testing.T) {
|
||||||
|
fd, err := os.Open("testdata/github-releases.json")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Missing github-release test data")
|
||||||
|
}
|
||||||
|
defer fd.Close()
|
||||||
|
|
||||||
|
var rels []Release
|
||||||
|
json.NewDecoder(fd).Decode(&rels)
|
||||||
|
|
||||||
|
for old, target := range upgrades {
|
||||||
|
upgrade, err := SelectLatestRelease(old, rels)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Error retrieving latest version", err)
|
||||||
|
}
|
||||||
|
if upgrade.Tag != target {
|
||||||
|
t.Errorf("Invalid upgrade release: %v -> %v, but got %v", old, target, upgrade.Tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorRelease(t *testing.T) {
|
||||||
|
_, err := SelectLatestRelease("v0.11.0-beta", nil)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("Should return an error when no release were available")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,6 +16,6 @@ func upgradeToURL(binary, url string) error {
|
|||||||
return ErrUpgradeUnsupported
|
return ErrUpgradeUnsupported
|
||||||
}
|
}
|
||||||
|
|
||||||
func LatestRelease(prerelease bool) (Release, error) {
|
func LatestRelease(version string) (Release, error) {
|
||||||
return Release{}, ErrUpgradeUnsupported
|
return Release{}, ErrUpgradeUnsupported
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user