Commit e1fd6968 authored by Addshore's avatar Addshore 🏄
Browse files

Updates from releases.wikimedia.org

Has not been fully tested yet, as there is no real release to grab
at this point.
However, this also doesn't alter the default current behaviour,
so should be safe to merge anyway..

Change-Id: If92526a73f588e81b8a37c1cc684ca6d8973cea3
parent 72290c2e
......@@ -81,10 +81,19 @@ var rootCmd = &cobra.Command{
func wizardDevMode() {
c := config.LoadFromDisk()
fmt.Println("\nYou need to choose a development environment mode in order to continue:")
fmt.Println(" - '" + config.ConfigDevModeMwdd + "' will provide advanced CLI tooling around a new mediawiki-docker-dev inspired development environment.")
fmt.Println(" - '" + config.DevModeMwdd + "' will provide advanced CLI tooling around a new mediawiki-docker-dev inspired development environment.")
fmt.Println("\nAs the only environment available currently, it will be set as your default dev environment (alias 'dev')")
c.DevMode = config.ConfigDevModeMwdd
c.DevMode = config.DevModeMwdd
c.WriteToDisk()
}
func wizardUpdateChannel() {
c := config.LoadFromDisk()
fmt.Println("\nYou need to choose an update channel in order to continue:")
fmt.Println(" - '" + config.UpdateChannelDev + "' is the only current release channel, so will be set now.")
c.UpdateChannel = config.UpdateChannelDev
c.WriteToDisk()
}
......@@ -97,31 +106,36 @@ func Execute(GitCommitIn string, GitBranchIn string, GitStateIn string, GitSumma
BuildDate = BuildDateIn
Version = VersionIn
canUpdate, release := updater.CanUpdateDaily(Version, GitSummary, false)
if canUpdate {
colorReset := "\033[0m"
colorYellow := "\033[33m"
colorWhite := "\033[37m"
colorCyan := "\033[36m"
fmt.Printf(
"\n"+colorYellow+"A new update is availbile\n"+colorCyan+"%s(%s) "+colorWhite+"-> "+colorCyan+"%s"+colorReset+"\n\n",
Version, GitSummary, release.Version.String(),
)
}
// check for dev alias
// Check and set needed config values
c := config.LoadFromDisk()
if c.DevMode != config.ConfigDevModeMwdd {
if !config.DevModeValues.Contains(c.DevMode) {
wizardDevMode()
c = config.LoadFromDisk()
}
if !config.UpdateChannelValues.Contains(c.UpdateChannel) {
wizardUpdateChannel()
c = config.LoadFromDisk()
}
// mwdd mode
if c.DevMode == config.ConfigDevModeMwdd {
if c.DevMode == config.DevModeMwdd {
mwddCmd.Aliases = []string{"dev"}
mwddCmd.Short += "\t(alias: dev)"
}
// Check if any updates are ready for us
canUpdate, nextVersionString := updater.CanUpdateDaily(Version, GitSummary, false)
if canUpdate {
colorReset := "\033[0m"
colorYellow := "\033[33m"
colorWhite := "\033[37m"
colorCyan := "\033[36m"
fmt.Printf(
"\n"+colorYellow+"A new update is availbile\n"+colorCyan+"%s(%s) "+colorWhite+"-> "+colorCyan+"%s"+colorReset+"\n\n",
Version, GitSummary, nextVersionString,
)
}
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
......
......@@ -20,6 +20,7 @@ import (
"fmt"
"os"
"gerrit.wikimedia.org/r/mediawiki/tools/cli/internal/config"
"gerrit.wikimedia.org/r/mediawiki/tools/cli/internal/updater"
"github.com/manifoldco/promptui"
"github.com/spf13/cobra"
......@@ -43,15 +44,17 @@ var updateCmd = &cobra.Command{
Use: "update",
Short: "Checks for and performs updates",
Run: func(cmd *cobra.Command, args []string) {
c := config.LoadFromDisk()
fmt.Println("You are on the " + c.UpdateChannel + " channel.")
canUpdate, nextRelease := updater.CanUpdate(Version, GitSummary, Verbosity >= 2)
if !canUpdate || nextRelease == nil {
fmt.Println("No update available")
canUpdate, toUpdateToOrMessage := updater.CanUpdate(Version, GitSummary, Verbosity >= 2)
if !canUpdate {
fmt.Println(toUpdateToOrMessage)
os.Exit(0)
}
fmt.Println("New update found: " + nextRelease.Version.String())
fmt.Println(nextRelease.AssetURL)
fmt.Println("New update found: " + toUpdateToOrMessage)
updatePrompt := promptui.Prompt{
Label: " Do you want to update?",
......@@ -59,7 +62,8 @@ var updateCmd = &cobra.Command{
}
_, err := updatePrompt.Run()
if err == nil {
updateSuccess, updateMessage := updater.UpdateTo(*nextRelease, Verbosity >= 2)
// Note: technically there is a small race condition here, and we might update to a newer version if it was release between stages
updateSuccess, updateMessage := updater.Update(Version, GitSummary, Verbosity >= 2)
fmt.Println(updateMessage)
if !updateSuccess {
os.Exit(1)
......
/*Package config for interacting with the cli config
Copyright © 2020 Addshore
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package config
/*DevModeValues allowed values for DevMode*/
var DevModeValues = AllowedOptions([]string{DevModeMwdd})
/*DevModeMwdd value for DevMode that will use the docker/mediawiki-docker-dev command set*/
const DevModeMwdd string = "docker"
/*UpdateChannelValues allowed values for UpdateChannel*/
var UpdateChannelValues = AllowedOptions([]string{UpdateChannelDev, UpdateChannelStable})
/*UpdateChannelDev value for UpdateChannel that will pull updates from addshore's dev builds*/
const UpdateChannelDev string = "dev-addshore"
/*UpdateChannelStable value for UpdateChannel that will pull updates from wikimedia's stable builds*/
const UpdateChannelStable string = "stable-wikimedia"
/*Config representation of a cli config*/
type Config struct {
DevMode string `json:"dev_mode"`
UpdateChannel string `json:"update_channel"`
}
/*AllowedOptions representation of allowed options for a config value*/
type AllowedOptions []string
/*Contains do the allowed options contain this value*/
func (cao AllowedOptions) Contains(value string) bool {
for _, v := range cao {
if v == value {
return true
}
}
return false
}
......@@ -27,14 +27,6 @@ import (
"strings"
)
/*ConfigDevModeMwdd ...*/
const ConfigDevModeMwdd string = "docker"
/*Config representation of a cli config*/
type Config struct {
DevMode string `json:"dev_mode"`
}
func configPath() string {
return mwcliDirectory() + string(os.PathSeparator) + "config.json"
}
......
/*Package updater is used to update the cli
Copyright © 2020 Addshore
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package updater
import (
"fmt"
"log"
"os"
"strings"
"github.com/blang/semver"
"github.com/rhysd/go-github-selfupdate/selfupdate"
)
/*CanUpdateFromAddshore ...*/
func CanUpdateFromAddshore(currentVersion string, gitSummary string, verboseOutput bool) (bool, *selfupdate.Release) {
if verboseOutput {
selfupdate.EnableLog()
}
// TODO when builds are on wm.o then allow for a "dev" or "stable" update option and checks
v, err := semver.Parse(strings.Trim(gitSummary, "v"))
if err != nil {
if verboseOutput {
log.Println("Could not parse git summary version, maybe you are not using a real release?")
}
return false, nil
}
rel, ok, err := selfupdate.DetectLatest("addshore/mwcli")
if err != nil {
if verboseOutput {
log.Println("Some unknown error occurred")
}
return false, rel
}
if !ok {
if verboseOutput {
log.Println("No release detected. Current version is considered up-to-date")
}
return false, rel
}
if v.Equals(rel.Version) {
if verboseOutput {
log.Println("Current version", v, "is the latest. Update is not needed")
}
return false, rel
}
if verboseOutput {
log.Println("Update available", rel.Version)
}
return true, rel
}
/*UpdateFromAddshoreTo ...*/
func UpdateFromAddshoreTo(release selfupdate.Release, verboseOutput bool) (success bool, message string) {
if verboseOutput {
selfupdate.EnableLog()
}
cmdPath, err := os.Executable()
if err != nil {
return false, "Failed to grab local executable location"
}
err = selfupdate.UpdateTo(release.AssetURL, cmdPath)
if err != nil {
return false, "Binary update failed" + err.Error()
}
return true, "Successfully updated to version" + release.Version.String() + "\nRelease note:\n" + release.ReleaseNotes
}
/*UpdateFromAddshore ...*/
func UpdateFromAddshore(currentVersion string, gitSummary string, verboseOutput bool) (success bool, message string) {
canUpdate, nextRelease := CanUpdateFromAddshore(currentVersion, gitSummary, verboseOutput)
if !canUpdate || nextRelease == nil {
return false, "Nothing to update to"
}
updateSuccess, updateMessage := UpdateFromAddshoreTo(*nextRelease, verboseOutput)
fmt.Println(updateMessage)
if !updateSuccess {
os.Exit(1)
}
return updateSuccess, updateMessage
}
/*Package updater is used to update the cli
Copyright © 2020 Addshore
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package updater
import (
"io/ioutil"
"log"
"os"
"os/user"
"path/filepath"
"time"
)
/*CanUpdateDaily will check for updates at most once a day*/
func CanUpdateDaily(currentVersion string, gitSummary string, verboseOutput bool) (bool, string) {
now := time.Now().UTC()
if now.Sub(lastCheckedTime()).Hours() < 24 {
if verboseOutput {
log.Println("Already checked for updates in the last 24 hours")
}
return false, ""
}
setCheckedTime(now)
return CanUpdate(currentVersion, gitSummary, verboseOutput)
}
func lastCheckedTime() time.Time {
if _, err := os.Stat(lastUpdateFilePath()); os.IsNotExist(err) {
return time.Now().UTC().Add(-24 * time.Hour * 7)
}
content, err := ioutil.ReadFile(lastUpdateFilePath())
if err != nil {
log.Fatal(err)
}
t, err := time.Parse(time.RFC3339, string(content))
if err != nil {
log.Fatal(err)
}
return t
}
func setCheckedTime(toSet time.Time) {
ensureDirectoryForFileOnDisk(lastUpdateFilePath())
err := ioutil.WriteFile(lastUpdateFilePath(), []byte(toSet.Format(time.RFC3339)), 0700)
if err != nil {
log.Fatal(err)
}
}
func lastUpdateFilePath() string {
currentUser, _ := user.Current()
return currentUser.HomeDir + string(os.PathSeparator) + ".mwcli/.lastUpdateCheck"
}
func ensureDirectoryForFileOnDisk(file string) {
// TODO factor this method out (also used in mwdd.files)
ensureDirectoryOnDisk(filepath.Dir(file))
}
func ensureDirectoryOnDisk(dirPath string) {
// TODO factor this method out (also used in mwdd.files)
if _, err := os.Stat(dirPath); err != nil {
os.MkdirAll(dirPath, 0755)
}
}
......@@ -18,128 +18,35 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package updater
import (
"io/ioutil"
"log"
"os"
"os/user"
"path/filepath"
"strings"
"time"
"github.com/blang/semver"
"github.com/rhysd/go-github-selfupdate/selfupdate"
"gerrit.wikimedia.org/r/mediawiki/tools/cli/internal/config"
)
/*CanUpdateDaily will check for updates at most once a day*/
func CanUpdateDaily(currentVersion string, gitSummary string, verboseOutput bool) (bool, *selfupdate.Release) {
now := time.Now().UTC()
if(now.Sub(lastCheckedTime()).Hours() < 24 ) {
if(verboseOutput){
log.Println("Already checked for updates in the last 24 hours")
/*CanUpdate will check for updates*/
func CanUpdate(currentVersion string, gitSummary string, verboseOutput bool) (bool, string) {
c := config.LoadFromDisk()
if c.UpdateChannel == config.UpdateChannelDev {
canUpdate, release := CanUpdateFromAddshore(currentVersion, gitSummary, verboseOutput)
if canUpdate {
return canUpdate, release.Version.String()
}
return false, nil
// When canUpdate is false, we dont have a release to get the version string of
return canUpdate, "Can't currently update"
}
setCheckedTime(now)
return CanUpdate(currentVersion, gitSummary, verboseOutput)
}
func lastCheckedTime() time.Time {
if _, err := os.Stat(lastUpdateFilePath()); os.IsNotExist(err) {
return time.Now().UTC().Add(-24*time.Hour*7)
}
content, err := ioutil.ReadFile(lastUpdateFilePath())
if err != nil {
log.Fatal(err)
if c.UpdateChannel == config.UpdateChannelStable {
return CanUpdateFromWikimedia(currentVersion, gitSummary, verboseOutput)
}
t, err := time.Parse(time.RFC3339, string(content))
if err != nil {
log.Fatal(err)
}
return t
}
func setCheckedTime( toSet time.Time ) {
ensureDirectoryForFileOnDisk(lastUpdateFilePath())
err := ioutil.WriteFile(lastUpdateFilePath(), []byte(toSet.Format(time.RFC3339)), 0700)
if err != nil {
log.Fatal(err)
}
panic("Unexpected update channel")
}
func lastUpdateFilePath() string {
currentUser, _ := user.Current()
return currentUser.HomeDir + string(os.PathSeparator) + ".mwcli/.lastUpdateCheck"
}
func ensureDirectoryForFileOnDisk(file string) {
// TODO factor this method out (also used in mwdd.files)
ensureDirectoryOnDisk(filepath.Dir(file))
}
func ensureDirectoryOnDisk(dirPath string) {
// TODO factor this method out (also used in mwdd.files)
if _, err := os.Stat(dirPath); err != nil {
os.MkdirAll(dirPath, 0755)
/*Update perform the latest update*/
func Update(currentVersion string, gitSummary string, verboseOutput bool) (bool, string) {
c := config.LoadFromDisk()
if c.UpdateChannel == config.UpdateChannelDev {
return UpdateFromAddshore(currentVersion, gitSummary, verboseOutput)
}
}
/*CanUpdate ...*/
func CanUpdate(currentVersion string, gitSummary string, verboseOutput bool) (bool, *selfupdate.Release) {
if(verboseOutput){
selfupdate.EnableLog()
if c.UpdateChannel == config.UpdateChannelStable {
// TODO implement me
panic("Not yet implemented")
}
// TODO when builds are on wm.o then allow for a "dev" or "stable" update option and checks
v, err := semver.Parse(strings.Trim(gitSummary,"v"))
if err != nil {
if(verboseOutput){
log.Println("Could not parse git summary version, maybe you are not using a real release?")
}
return false, nil
}
rel, ok, err := selfupdate.DetectLatest("addshore/mwcli")
if err != nil {
if(verboseOutput){
log.Println("Some unknown error occurred")
}
return false, rel
}
if !ok {
if(verboseOutput){
log.Println("No release detected. Current version is considered up-to-date")
}
return false, rel
}
if v.Equals(rel.Version) {
if(verboseOutput){
log.Println("Current version", v, "is the latest. Update is not needed")
}
return false, rel
}
if(verboseOutput){
log.Println("Update available", rel.Version)
}
return true, rel
panic("Unexpected update channel")
}
/*UpdateTo ...*/
func UpdateTo(release selfupdate.Release, verboseOutput bool) (success bool, message string) {
if(verboseOutput){
selfupdate.EnableLog()
}
cmdPath, err := os.Executable()
if err != nil {
return false, "Failed to grab local executable location"
}
err = selfupdate.UpdateTo(release.AssetURL, cmdPath)
if err != nil {
return false, "Binary update failed" + err.Error()
}
return true, "Successfully updated to version" + release.Version.String() + "\nRelease note:\n" + release.ReleaseNotes
}
\ No newline at end of file
/*Package updater is used to update the cli
Copyright © 2020 Addshore
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package updater
import (
"io/ioutil"
"net/http"
"os"
"runtime"
"strings"
"github.com/blang/semver"
"github.com/rhysd/go-github-selfupdate/selfupdate"
)
/*CanUpdateFromWikimedia ...*/
func CanUpdateFromWikimedia(currentVersion string, gitSummary string, verboseOutput bool) (bool, string) {
if verboseOutput {
selfupdate.EnableLog()
}
latestRelease := latestWikimediaRelease()
if latestRelease == "404" {
return false, "No Wikimedia releases yet"
}
newVersion, newErr := semver.Parse(strings.Trim(latestRelease, "v"))
currentVerion, currentErr := semver.Parse(strings.Trim(gitSummary, "v"))
if newErr != nil {
return false, "Could not remote release version?"
}
if currentErr != nil {
return false, "Could not parse current git summary version '" + gitSummary + "', maybe you are not using a real release? Next release would be " + newVersion.String()
}
return currentVerion.Compare(newVersion) == -1, newVersion.String()
}
func latestWikimediaRelease() string {
url := "https://releases.wikimedia.org/mwcli/latest.txt"
client := http.Client{}
resp, err := client.Get(url)
if err != nil {
panic("Something went wrong retrieving " + url)
}
defer resp.Body.Close()
content, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic("Something went wrong reading " + url)
}
latestContent := strings.TrimSpace(string(content))
if strings.Contains(latestContent, "404") {
return "404"
}
return latestContent
}
/*UpdateFromWikimedia ...*/
func UpdateFromWikimedia(currentVersion string, gitSummary string, verboseOutput bool) (success bool, message string) {
if verboseOutput {
selfupdate.EnableLog()
}
canUpdate, newVersionOrMessage := CanUpdateFromWikimedia(currentVersion, gitSummary, verboseOutput)
if !canUpdate {
return false, "No update found: " + newVersionOrMessage
}
assetURL := "https://releases.wikimedia.org/mwcli/" + newVersionOrMessage + "/mw_v" + newVersionOrMessage + "_" + runtime.GOOS + "_" + runtime.GOARCH
cmdPath, err := os.Executable()
if err != nil {
return false, "Failed to grab local executable location"
}