Merge pull request #1681 from calmh/major-upgrade

Allow major upgrades [v0.10]
This commit is contained in:
Audrius Butkevicius
2015-04-22 14:11:18 +01:00
10 changed files with 3966 additions and 27 deletions
+5 -4
View File
@@ -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)
+2 -2
View File
@@ -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
+6
View File
@@ -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",
+35
View File
@@ -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>&emsp;
<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>&emsp;<span translate>Upgrade</span></button>
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span>&emsp;<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
File diff suppressed because it is too large Load Diff
+46 -12
View File
@@ -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
+49 -5
View File
@@ -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")
}
}
+1 -1
View File
@@ -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
} }