minio/cmd/update-main.go

286 lines
7.4 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, 2016, 2017 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"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/fatih/color"
"github.com/minio/cli"
)
// Check for new software updates.
var updateCmd = cli.Command{
Name: "update",
Usage: "Check for a new software update.",
Action: mainUpdate,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "quiet",
Usage: "Disable any update messages.",
},
},
CustomHelpTemplate: `Name:
{{.HelpName}} - {{.Usage}}
USAGE:
{{.HelpName}}{{if .VisibleFlags}} [FLAGS]{{end}}
{{if .VisibleFlags}}
FLAGS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}
EXIT STATUS:
0 - You are already running the most recent version.
1 - New update is available.
-1 - Error in getting update information.
EXAMPLES:
1. Check if there is a new update available:
$ {{.HelpName}}
`,
}
const (
minioReleaseTagTimeLayout = "2006-01-02T15-04-05Z"
minioReleaseURL = "https://dl.minio.io/server/minio/release/" + runtime.GOOS + "-" + runtime.GOARCH + "/"
)
func getCurrentReleaseTime(minioVersion, minioBinaryPath string) (releaseTime time.Time, err error) {
if releaseTime, err = time.Parse(time.RFC3339, minioVersion); err == nil {
return releaseTime, err
}
if !filepath.IsAbs(minioBinaryPath) {
// Make sure to look for the absolute path of the binary.
minioBinaryPath, err = exec.LookPath(minioBinaryPath)
if err != nil {
return releaseTime, err
}
}
// Looks like version is minio non-standard, we use minio binary's ModTime as release time.
fi, err := osStat(minioBinaryPath)
if err != nil {
err = fmt.Errorf("Unable to get ModTime of %s. %s", minioBinaryPath, err)
} else {
releaseTime = fi.ModTime().UTC()
}
return releaseTime, err
}
// GetCurrentReleaseTime - returns this process's release time. If it is official minio version,
// parsed version is returned else minio binary's mod time is returned.
func GetCurrentReleaseTime() (releaseTime time.Time, err error) {
return getCurrentReleaseTime(Version, os.Args[0])
}
func isDocker(cgroupFile string) (bool, error) {
cgroup, err := ioutil.ReadFile(cgroupFile)
if os.IsNotExist(err) {
err = nil
}
return bytes.Contains(cgroup, []byte("docker")), err
}
// IsDocker - returns if the environment is docker or not.
func IsDocker() bool {
found, err := isDocker("/proc/self/cgroup")
fatalIf(err, "Error in docker check.")
return found
}
func isSourceBuild(minioVersion string) bool {
_, err := time.Parse(time.RFC3339, minioVersion)
return err != nil
}
// IsSourceBuild - returns if this binary is made from source or not.
func IsSourceBuild() bool {
return isSourceBuild(Version)
}
// DO NOT CHANGE USER AGENT STYLE.
// The style should be
// Minio (<OS>; <ARCH>[; docker][; source]) Minio/<VERSION> Minio/<RELEASE-TAG> Minio/<COMMIT-ID>
//
// For any change here should be discussed by openning an issue at https://github.com/minio/minio/issues.
func getUserAgent(mode string) string {
userAgent := "Minio (" + runtime.GOOS + "; " + runtime.GOARCH
if mode != "" {
userAgent += "; " + mode
}
if IsDocker() {
userAgent += "; docker"
}
if IsSourceBuild() {
userAgent += "; source"
}
userAgent += ") " + " Minio/" + Version + " Minio/" + ReleaseTag + " Minio/" + CommitID
return userAgent
}
func downloadReleaseData(releaseChecksumURL string, timeout time.Duration, mode string) (data string, err error) {
req, err := http.NewRequest("GET", releaseChecksumURL, nil)
if err != nil {
return data, err
}
req.Header.Set("User-Agent", getUserAgent(mode))
client := &http.Client{
Timeout: timeout,
Transport: &http.Transport{
// need to close connection after usage.
DisableKeepAlives: true,
},
}
resp, err := client.Do(req)
if err != nil {
return data, err
}
if resp == nil {
return data, fmt.Errorf("No response from server to download URL %s", releaseChecksumURL)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return data, fmt.Errorf("Error downloading URL %s. Response: %v", releaseChecksumURL, resp.Status)
}
dataBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return data, fmt.Errorf("Error reading response. %s", err)
}
data = string(dataBytes)
return data, err
}
// DownloadReleaseData - downloads release data from minio official server.
func DownloadReleaseData(timeout time.Duration, mode string) (data string, err error) {
data, err = downloadReleaseData(minioReleaseURL+"minio.shasum", timeout, mode)
if err == nil {
return data, nil
}
return downloadReleaseData(minioReleaseURL+"minio.sha256sum", timeout, mode)
}
func parseReleaseData(data string) (releaseTime time.Time, err error) {
fields := strings.Fields(data)
if len(fields) != 2 {
err = fmt.Errorf("Unknown release data `%s`", data)
return releaseTime, err
}
releaseInfo := fields[1]
if fields = strings.Split(releaseInfo, "."); len(fields) != 3 {
err = fmt.Errorf("Unknown release information `%s`", releaseInfo)
return releaseTime, err
}
if !(fields[0] == "minio" && fields[1] == "RELEASE") {
err = fmt.Errorf("Unknown release '%s'", releaseInfo)
return releaseTime, err
}
releaseTime, err = time.Parse(minioReleaseTagTimeLayout, fields[2])
if err != nil {
err = fmt.Errorf("Unknown release time format. %s", err)
}
return releaseTime, err
}
func getLatestReleaseTime(timeout time.Duration, mode string) (releaseTime time.Time, err error) {
data, err := DownloadReleaseData(timeout, mode)
if err != nil {
return releaseTime, err
}
return parseReleaseData(data)
}
func getDownloadURL() (downloadURL string) {
if IsDocker() {
return "docker pull minio/minio"
}
if runtime.GOOS == "windows" {
return minioReleaseURL + "minio.exe"
}
return minioReleaseURL + "minio"
}
func getUpdateInfo(timeout time.Duration, mode string) (older time.Duration, downloadURL string, err error) {
currentReleaseTime, err := GetCurrentReleaseTime()
if err != nil {
return older, downloadURL, err
}
latestReleaseTime, err := getLatestReleaseTime(timeout, mode)
if err != nil {
return older, downloadURL, err
}
if latestReleaseTime.After(currentReleaseTime) {
older = latestReleaseTime.Sub(currentReleaseTime)
downloadURL = getDownloadURL()
}
return older, downloadURL, nil
}
func mainUpdate(ctx *cli.Context) {
if len(ctx.Args()) != 0 {
cli.ShowCommandHelpAndExit(ctx, "update", -1)
}
quiet := ctx.Bool("quiet") || ctx.GlobalBool("quiet")
if quiet {
log.EnableQuiet()
}
minioMode := ""
older, downloadURL, err := getUpdateInfo(10*time.Second, minioMode)
if err != nil {
log.Println(err)
os.Exit(-1)
}
if older != time.Duration(0) {
log.Println(colorizeUpdateMessage(downloadURL, older))
os.Exit(1)
}
colorSprintf := color.New(color.FgGreen, color.Bold).SprintfFunc()
log.Println(colorSprintf("You are already running the most recent version of minio."))
os.Exit(0)
}