minio/cmd/update-main.go

282 lines
7.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Minio Cloud Storage, (C) 2015 Minio, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cmd
import (
"bytes"
"errors"
"io/ioutil"
"net/http"
"os"
"runtime"
"strings"
"time"
"github.com/fatih/color"
"github.com/minio/cli"
"github.com/minio/mc/pkg/console"
)
// Check for new software updates.
var updateCmd = cli.Command{
Name: "update",
Usage: "Check for a new software update.",
Action: mainUpdate,
Flags: globalFlags,
CustomHelpTemplate: `Name:
minio {{.Name}} - {{.Usage}}
USAGE:
minio {{.Name}} [FLAGS]
FLAGS:
{{range .Flags}}{{.}}
{{end}}
EXAMPLES:
1. Check for any new official release.
$ minio {{.Name}}
`,
}
// update URL endpoints.
const (
minioUpdateStableURL = "https://dl.minio.io/server/minio/release"
)
// updateMessage container to hold update messages.
type updateMessage struct {
Update bool `json:"update"`
Download string `json:"downloadURL"`
NewerThan time.Duration `json:"newerThan"`
}
// String colorized update message.
func (u updateMessage) String() string {
if !u.Update {
updateMessage := color.New(color.FgGreen, color.Bold).SprintfFunc()
return updateMessage("You are already running the most recent version of minio.")
}
msg := colorizeUpdateMessage(u.Download, u.NewerThan)
return msg
}
func parseReleaseData(data string) (time.Time, error) {
releaseStr := strings.Fields(data)
if len(releaseStr) < 2 {
return time.Time{}, errors.New("Update data malformed")
}
releaseDate := releaseStr[1]
releaseDateSplits := strings.SplitN(releaseDate, ".", 3)
if len(releaseDateSplits) < 3 {
return time.Time{}, (errors.New("Update data malformed"))
}
if releaseDateSplits[0] != "minio" {
return time.Time{}, (errors.New("Update data malformed, missing minio tag"))
}
// "OFFICIAL" tag is still kept for backward compatibility.
// We should remove this for the next release.
if releaseDateSplits[1] != "RELEASE" && releaseDateSplits[1] != "OFFICIAL" {
return time.Time{}, (errors.New("Update data malformed, missing RELEASE tag"))
}
dateSplits := strings.SplitN(releaseDateSplits[2], "T", 2)
if len(dateSplits) < 2 {
return time.Time{}, (errors.New("Update data malformed, not in modified RFC3359 form"))
}
dateSplits[1] = strings.Replace(dateSplits[1], "-", ":", -1)
date := strings.Join(dateSplits, "T")
parsedDate, err := time.Parse(time.RFC3339, date)
if err != nil {
return time.Time{}, err
}
return parsedDate, nil
}
// User Agent should always following the below style.
// Please open an issue to discuss any new changes here.
//
// Minio (OS; ARCH) APP/VER APP/VER
var (
userAgentSuffix = "Minio/" + Version + " " + "Minio/" + ReleaseTag + " " + "Minio/" + CommitID
)
// Check if the operating system is a docker container.
func isDocker() bool {
cgroup, err := ioutil.ReadFile("/proc/self/cgroup")
if err != nil && os.IsNotExist(err) {
return false
}
fatalIf(err, "Unable to read `cgroup` file.")
return bytes.Contains(cgroup, []byte("docker"))
}
// Check if the minio server binary was built with source.
func isSourceBuild() bool {
return Version == "DEVELOPMENT.GOGET"
}
// Fetch the current version of the Minio server binary.
func getCurrentMinioVersion() (current time.Time, err error) {
// For development builds we check for binary modTime
// to validate against latest minio server release.
if Version != "DEVELOPMENT.GOGET" {
// Parse current minio version into RFC3339.
current, err = time.Parse(time.RFC3339, Version)
if err != nil {
return time.Time{}, err
}
return current, nil
} // else {
// For all development builds through `go get`.
// fall back to looking for version of the build
// date of the binary itself.
var fi os.FileInfo
fi, err = os.Stat(os.Args[0])
if err != nil {
return time.Time{}, err
}
return fi.ModTime(), nil
}
// verify updates for releases.
func getReleaseUpdate(updateURL string, duration time.Duration) (updateMsg updateMessage, errMsg string, err error) {
// Construct a new update url.
newUpdateURLPrefix := updateURL + "/" + runtime.GOOS + "-" + runtime.GOARCH
newUpdateURL := newUpdateURLPrefix + "/minio.shasum"
// Get the downloadURL.
var downloadURL string
if isDocker() {
downloadURL = "docker pull minio/minio"
} else {
switch runtime.GOOS {
case "windows":
// For windows.
downloadURL = newUpdateURLPrefix + "/minio.exe"
default:
// For all other operating systems.
downloadURL = newUpdateURLPrefix + "/minio"
}
}
// Initialize update message.
updateMsg = updateMessage{
Download: downloadURL,
}
// Instantiate a new client with 3 sec timeout.
client := &http.Client{
Timeout: duration,
}
current, err := getCurrentMinioVersion()
if err != nil {
errMsg = "Unable to fetch the current version of Minio server."
return
}
// Initialize new request.
req, err := http.NewRequest("GET", newUpdateURL, nil)
if err != nil {
return
}
userAgentPrefix := func() string {
prefix := "Minio (" + runtime.GOOS + "; " + runtime.GOARCH
// if its a source build.
if isSourceBuild() {
if isDocker() {
prefix = prefix + "; " + "docker; source) "
} else {
prefix = prefix + "; " + "source) "
}
return prefix
} // else { not source.
if isDocker() {
prefix = prefix + "; " + "docker) "
} else {
prefix = prefix + ") "
}
return prefix
}()
// Set user agent.
req.Header.Set("User-Agent", userAgentPrefix+" "+userAgentSuffix)
// Fetch new update.
resp, err := client.Do(req)
if err != nil {
return
}
// Verify if we have a valid http response i.e http.StatusOK.
if resp != nil {
if resp.StatusCode != http.StatusOK {
errMsg = "Failed to retrieve update notice."
err = errors.New("http status : " + resp.Status)
return
}
}
// Read the response body.
updateBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
errMsg = "Failed to retrieve update notice. Please try again later."
return
}
errMsg = "Failed to retrieve update notice. Please try again later. Please report this issue at https://github.com/minio/minio/issues"
// Parse the date if its valid.
latest, err := parseReleaseData(string(updateBody))
if err != nil {
return
}
// Verify if the date is not zero.
if latest.IsZero() {
err = errors.New("Release date cannot be zero. Please report this issue at https://github.com/minio/minio/issues")
return
}
// Is the update latest?.
if latest.After(current) {
updateMsg.Update = true
updateMsg.NewerThan = latest.Sub(current)
}
// Return update message.
return updateMsg, "", nil
}
// main entry point for update command.
func mainUpdate(ctx *cli.Context) {
// Set global quiet flag.
if ctx.Bool("quiet") || ctx.GlobalBool("quiet") {
return
}
// Check for update.
var updateMsg updateMessage
var errMsg string
var err error
var secs = time.Second * 3
updateMsg, errMsg, err = getReleaseUpdate(minioUpdateStableURL, secs)
fatalIf(err, errMsg)
console.Println(updateMsg)
}