Merge pull request #1261 from harshavardhana/update-message

minio: Server upon start displays a message if update is available.
master
Anand Babu (AB) Periasamy 9 years ago
commit 5bd47861d6
  1. 23
      commands.go
  2. 18
      flags.go
  3. 31
      globals.go
  4. 43
      main.go
  5. 7
      minio-main.go
  6. 38
      notifier.go
  7. 99
      update-main.go

@ -18,13 +18,30 @@ package main
import "github.com/minio/cli" import "github.com/minio/cli"
// Collection of minio commands currently supported are // Collection of minio commands currently supported are.
var commands = []cli.Command{} var commands = []cli.Command{}
// Collection of minio commands currently supported in a trie tree // Collection of minio commands currently supported in a trie tree.
var commandsTree = newTrie() var commandsTree = newTrie()
// registerCommand registers a cli command // Collection of minio flags currently supported.
var globalFlags = []cli.Flag{
cli.BoolFlag{
Name: "quiet, q",
Usage: "Suppress chatty console output.",
},
cli.BoolFlag{
Name: "debug",
Usage: "Enable debugging output.",
},
cli.StringFlag{
Name: "config-dir, C",
Value: mustGetConfigPath(),
Usage: "Path to configuration folder.",
},
}
// registerCommand registers a cli command.
func registerCommand(command cli.Command) { func registerCommand(command cli.Command) {
commands = append(commands, command) commands = append(commands, command)
commandsTree.Insert(command.Name) commandsTree.Insert(command.Name)

@ -15,21 +15,3 @@
*/ */
package main package main
import "github.com/minio/cli"
// Collection of minio flags currently supported
var flags = []cli.Flag{}
var (
configFolderFlag = cli.StringFlag{
Name: "config-folder, C",
Value: mustGetConfigPath(),
Usage: "Path to configuration folder.",
}
)
// registerFlag registers a cli flag
func registerFlag(flag cli.Flag) {
flags = append(flags, flag)
}

@ -16,7 +16,11 @@
package main package main
import "github.com/fatih/color" import (
"github.com/fatih/color"
"github.com/minio/cli"
"github.com/minio/mc/pkg/console"
)
// Global constants for Minio. // Global constants for Minio.
const ( const (
@ -33,9 +37,34 @@ const (
globalMinioConfigFile = "config.json" globalMinioConfigFile = "config.json"
) )
var (
globalQuiet = false // Quiet flag set via command line
globalDebug = false // Debug flag set via command line
// Add new global flags here.
)
// global colors. // global colors.
var ( var (
colorMagenta = color.New(color.FgMagenta, color.Bold).SprintfFunc() colorMagenta = color.New(color.FgMagenta, color.Bold).SprintfFunc()
colorWhite = color.New(color.FgWhite, color.Bold).SprintfFunc() colorWhite = color.New(color.FgWhite, color.Bold).SprintfFunc()
colorGreen = color.New(color.FgGreen, color.Bold).SprintfFunc() colorGreen = color.New(color.FgGreen, color.Bold).SprintfFunc()
) )
// Set global states. NOTE: It is deliberately kept monolithic to
// ensure we dont miss out any flags.
func setGlobals(quiet, debug bool) {
globalQuiet = quiet
globalDebug = debug
// Enable debug messages if requested.
if globalDebug == true {
console.DebugPrint = true
}
}
// Set global states. NOTE: It is deliberately kept monolithic to
// ensure we dont miss out any flags.
func setGlobalsFromContext(ctx *cli.Context) {
quiet := ctx.Bool("quiet") || ctx.GlobalBool("quiet")
debug := ctx.Bool("debug") || ctx.GlobalBool("debug")
setGlobals(quiet, debug)
}

@ -27,6 +27,17 @@ import (
"github.com/minio/cli" "github.com/minio/cli"
"github.com/minio/mc/pkg/console" "github.com/minio/mc/pkg/console"
"github.com/minio/minio/pkg/probe" "github.com/minio/minio/pkg/probe"
"github.com/olekukonko/ts"
)
var (
// global flags for minio.
minioFlags = []cli.Flag{
cli.BoolFlag{
Name: "help, h",
Usage: "Show help.",
},
}
) )
// Help template for minio. // Help template for minio.
@ -136,16 +147,13 @@ func registerApp() *cli.App {
registerCommand(versionCmd) registerCommand(versionCmd)
registerCommand(updateCmd) registerCommand(updateCmd)
// Register all flags.
registerFlag(configFolderFlag)
// Set up app. // Set up app.
app := cli.NewApp() app := cli.NewApp()
app.Name = "Minio" app.Name = "Minio"
app.Author = "Minio.io" app.Author = "Minio.io"
app.Usage = "Distributed Object Storage Server for Micro Services." app.Usage = "Distributed Object Storage Server for Micro Services."
app.Description = `Micro services environment provisions one Minio server per application instance. Scalability is achieved through large number of smaller personalized instances. This version of the Minio binary is built using Filesystem storage backend for magnetic and solid state disks.` app.Description = `Micro services environment provisions one Minio server per application instance. Scalability is achieved through large number of smaller personalized instances. This version of the Minio binary is built using Filesystem storage backend for magnetic and solid state disks.`
app.Flags = flags app.Flags = append(minioFlags, globalFlags...)
app.Commands = commands app.Commands = commands
app.CustomAppHelpTemplate = minioHelpTemplate app.CustomAppHelpTemplate = minioHelpTemplate
app.CommandNotFound = func(ctx *cli.Context, command string) { app.CommandNotFound = func(ctx *cli.Context, command string) {
@ -168,7 +176,7 @@ func checkMainSyntax(c *cli.Context) {
console.Fatalf("Unable to obtain user's home directory. \nError: %s\n", err) console.Fatalf("Unable to obtain user's home directory. \nError: %s\n", err)
} }
if configPath == "" { if configPath == "" {
console.Fatalln("Config folder cannot be empty, please specify --config-folder <foldername>.") console.Fatalln("Config folder cannot be empty, please specify --config-dir <foldername>.")
} }
} }
@ -179,8 +187,11 @@ func main() {
app := registerApp() app := registerApp()
app.Before = func(c *cli.Context) error { app.Before = func(c *cli.Context) error {
// Set global flags.
setGlobalsFromContext(c)
// Sets new config folder. // Sets new config folder.
setGlobalConfigPath(c.GlobalString("config-folder")) setGlobalConfigPath(c.GlobalString("config-dir"))
// Valid input arguments to main. // Valid input arguments to main.
checkMainSyntax(c) checkMainSyntax(c)
@ -191,11 +202,29 @@ func main() {
// Enable all loggers by now. // Enable all loggers by now.
enableLoggers() enableLoggers()
// Do not print update messages, if quiet flag is set.
if !globalQuiet {
// Do not print any errors in release update function.
noError := true
updateMsg := getReleaseUpdate(minioUpdateStableURL, noError)
if updateMsg.Update {
console.Println(updateMsg)
}
}
// Return here.
return nil return nil
} }
app.ExtraInfo = func() map[string]string { app.ExtraInfo = func() map[string]string {
if _, e := ts.GetSize(); e != nil {
globalQuiet = true
}
// Enable if debug is enabled.
if globalDebug {
return getSystemData() return getSystemData()
} }
return make(map[string]string)
}
// Run the app - exit on error.
app.RunAndExitOnError() app.RunAndExitOnError()
} }

@ -200,7 +200,7 @@ func initServer(c *cli.Context) {
} }
} }
// check init arguments. // Check init arguments.
func checkInitSyntax(c *cli.Context) { func checkInitSyntax(c *cli.Context) {
if !c.Args().Present() || c.Args().First() == "help" { if !c.Args().Present() || c.Args().First() == "help" {
cli.ShowCommandHelpAndExit(c, "init", 1) cli.ShowCommandHelpAndExit(c, "init", 1)
@ -214,8 +214,7 @@ func checkInitSyntax(c *cli.Context) {
} }
} }
// extract port number from address. // Extract port number from address address should be of the form host:port.
// address should be of the form host:port
func getPort(address string) int { func getPort(address string) int {
_, portStr, e := net.SplitHostPort(address) _, portStr, e := net.SplitHostPort(address)
fatalIf(probe.NewError(e), "Unable to split host port.", nil) fatalIf(probe.NewError(e), "Unable to split host port.", nil)
@ -307,6 +306,7 @@ func serverMain(c *cli.Context) {
cli.ShowCommandHelpAndExit(c, "server", 1) cli.ShowCommandHelpAndExit(c, "server", 1)
} }
// get backend.
backend := serverConfig.GetBackend() backend := serverConfig.GetBackend()
if backend.Type == "fs" { if backend.Type == "fs" {
// Initialize file system. // Initialize file system.
@ -348,6 +348,7 @@ func serverMain(c *cli.Context) {
// Start server. // Start server.
err = minhttp.ListenAndServe(apiServer) err = minhttp.ListenAndServe(apiServer)
errorIf(err.Trace(), "Failed to start the minio server.", nil) errorIf(err.Trace(), "Failed to start the minio server.", nil)
return
} }
console.Println(colorGreen("No known backends configured, please use ‘minio init --help’ to initialize a backend.")) console.Println(colorGreen("No known backends configured, please use ‘minio init --help’ to initialize a backend."))
} }

@ -18,7 +18,6 @@ package main
import ( import (
"fmt" "fmt"
"math"
"runtime" "runtime"
"strings" "strings"
@ -29,25 +28,22 @@ import (
// colorizeUpdateMessage - inspired from Yeoman project npm package https://github.com/yeoman/update-notifier // colorizeUpdateMessage - inspired from Yeoman project npm package https://github.com/yeoman/update-notifier
func colorizeUpdateMessage(updateString string) (string, *probe.Error) { func colorizeUpdateMessage(updateString string) (string, *probe.Error) {
// initialize coloring // Initialize coloring.
cyan := color.New(color.FgCyan, color.Bold).SprintFunc() cyan := color.New(color.FgCyan, color.Bold).SprintFunc()
yellow := color.New(color.FgYellow, color.Bold).SprintfFunc() yellow := color.New(color.FgYellow, color.Bold).SprintfFunc()
// calculate length without color coding, due to ANSI color characters padded to actual // Calculate length without color coding, due to ANSI color
// string the final length is wrong than the original string length // characters padded to actual string the final length is wrong
line1Str := fmt.Sprintf(" Update available: ") // than the original string length.
line2Str := fmt.Sprintf(" Run \"%s\" to update. ", updateString) line1Str := fmt.Sprintf(" New update: %s ", updateString)
line1Length := len(line1Str) line1Length := len(line1Str)
line2Length := len(line2Str)
// populate lines with color coding // Populate lines with color coding.
line1InColor := line1Str line1InColor := fmt.Sprintf(" New update: %s ", cyan(updateString))
line2InColor := fmt.Sprintf(" Run \"%s\" to update. ", cyan(updateString))
// calculate the rectangular box size // Calculate the rectangular box size.
maxContentWidth := int(math.Max(float64(line1Length), float64(line2Length))) maxContentWidth := line1Length
line1Rest := maxContentWidth - line1Length line1Rest := maxContentWidth - line1Length
line2Rest := maxContentWidth - line2Length
terminal, err := ts.GetSize() terminal, err := ts.GetSize()
if err != nil { if err != nil {
@ -56,31 +52,29 @@ func colorizeUpdateMessage(updateString string) (string, *probe.Error) {
var message string var message string
switch { switch {
case len(line2Str) > terminal.Col(): case len(line1Str) > terminal.Col():
message = "\n" + line1InColor + "\n" + line2InColor + "\n" message = "\n" + line1InColor + "\n"
default: default:
// on windows terminal turn off unicode characters // On windows terminal turn off unicode characters.
var top, bottom, sideBar string var top, bottom, sideBar string
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
top = yellow("*" + strings.Repeat("*", maxContentWidth) + "*") top = yellow("*" + strings.Repeat("*", maxContentWidth) + "*")
bottom = yellow("*" + strings.Repeat("*", maxContentWidth) + "*") bottom = yellow("*" + strings.Repeat("*", maxContentWidth) + "*")
sideBar = yellow("|") sideBar = yellow("|")
} else { } else {
// color the rectangular box, use unicode characters here // Color the rectangular box, use unicode characters here.
top = yellow("┏" + strings.Repeat("━", maxContentWidth) + "┓") top = yellow("┏" + strings.Repeat("━", maxContentWidth) + "┓")
bottom = yellow("┗" + strings.Repeat("━", maxContentWidth) + "┛") bottom = yellow("┗" + strings.Repeat("━", maxContentWidth) + "┛")
sideBar = yellow("┃") sideBar = yellow("┃")
} }
// fill spaces to the rest of the area // Fill spaces to the rest of the area.
spacePaddingLine1 := strings.Repeat(" ", line1Rest) spacePaddingLine1 := strings.Repeat(" ", line1Rest)
spacePaddingLine2 := strings.Repeat(" ", line2Rest)
// construct the final message // Construct the final message.
message = "\n" + top + "\n" + message = "\n" + top + "\n" +
sideBar + line1InColor + spacePaddingLine1 + sideBar + "\n" + sideBar + line1InColor + spacePaddingLine1 + sideBar + "\n" +
sideBar + line2InColor + spacePaddingLine2 + sideBar + "\n" +
bottom + "\n" bottom + "\n"
} }
// return the final message // Return the final message.
return message, nil return message, nil
} }

@ -89,13 +89,7 @@ func (u updateMessage) String() string {
updateMessage := color.New(color.FgGreen, color.Bold).SprintfFunc() updateMessage := color.New(color.FgGreen, color.Bold).SprintfFunc()
return updateMessage("You are already running the most recent version of ‘minio’.") return updateMessage("You are already running the most recent version of ‘minio’.")
} }
var msg string msg, err := colorizeUpdateMessage(u.Download)
if runtime.GOOS == "windows" {
msg = "Download " + u.Download
} else {
msg = "Download " + u.Download
}
msg, err := colorizeUpdateMessage(msg)
fatalIf(err.Trace(msg), "Unable to colorize experimental update notification string ‘"+msg+"’.", nil) fatalIf(err.Trace(msg), "Unable to colorize experimental update notification string ‘"+msg+"’.", nil)
return msg return msg
} }
@ -141,59 +135,108 @@ func parseReleaseData(data string) (time.Time, *probe.Error) {
} }
// verify updates for releases. // verify updates for releases.
func getReleaseUpdate(updateURL string) { func getReleaseUpdate(updateURL string, noError bool) updateMessage {
// Construct a new update url.
newUpdateURLPrefix := updateURL + "/" + runtime.GOOS + "-" + runtime.GOARCH newUpdateURLPrefix := updateURL + "/" + runtime.GOOS + "-" + runtime.GOARCH
newUpdateURL := newUpdateURLPrefix + "/minio.shasum" newUpdateURL := newUpdateURLPrefix + "/minio.shasum"
data, e := http.Get(newUpdateURL)
// Get the downloadURL.
var downloadURL string
switch runtime.GOOS {
case "windows", "darwin":
// For windows and darwin.
downloadURL = newUpdateURLPrefix + "/minio.zip"
default:
// For all other operating systems.
downloadURL = newUpdateURLPrefix + "/minio.gz"
}
// Initialize update message.
updateMsg := updateMessage{
Download: downloadURL,
Version: minioVersion,
}
// Instantiate a new client with 1 sec timeout.
client := &http.Client{
Timeout: 500 * time.Millisecond,
}
// Fetch new update.
data, e := client.Get(newUpdateURL)
if e != nil && noError {
return updateMsg
}
fatalIf(probe.NewError(e), "Unable to read from update URL ‘"+newUpdateURL+"’.", nil) fatalIf(probe.NewError(e), "Unable to read from update URL ‘"+newUpdateURL+"’.", nil)
if minioVersion == "DEVELOPMENT.GOGET" { // Error out if 'update' command is issued for development based builds.
if minioVersion == "DEVELOPMENT.GOGET" && !noError {
fatalIf(probe.NewError(errors.New("")), fatalIf(probe.NewError(errors.New("")),
"Update mechanism is not supported for ‘go get’ based binary builds. Please download official releases from https://minio.io/#minio", nil) "Update mechanism is not supported for ‘go get’ based binary builds. Please download official releases from https://minio.io/#minio", nil)
} }
// Parse current minio version into RFC3339.
current, e := time.Parse(time.RFC3339, minioVersion) current, e := time.Parse(time.RFC3339, minioVersion)
if e != nil && noError {
return updateMsg
}
fatalIf(probe.NewError(e), "Unable to parse version string as time.", nil) fatalIf(probe.NewError(e), "Unable to parse version string as time.", nil)
if current.IsZero() { // Verify if current minio version is zero.
if current.IsZero() && !noError {
fatalIf(probe.NewError(errors.New("")), fatalIf(probe.NewError(errors.New("")),
"Updates not supported for custom builds. Version field is empty. Please download official releases from https://minio.io/#minio", nil) "Updates not supported for custom builds. Version field is empty. Please download official releases from https://minio.io/#minio", nil)
} }
body, e := ioutil.ReadAll(data.Body) // Verify if we have a valid http response i.e http.StatusOK.
if data != nil {
if data.StatusCode != http.StatusOK {
// Return quickly if noError is set.
if noError {
return updateMsg
}
fatalIf(probe.NewError(errors.New("")), "Update server responsed with "+data.Status, nil)
}
}
// Read the response body.
updateBody, e := ioutil.ReadAll(data.Body)
if e != nil && noError {
return updateMsg
}
fatalIf(probe.NewError(e), "Fetching updates failed. Please try again.", nil) fatalIf(probe.NewError(e), "Fetching updates failed. Please try again.", nil)
latest, err := parseReleaseData(string(body)) // Parse the date if its valid.
latest, err := parseReleaseData(string(updateBody))
if err != nil && noError {
return updateMsg
}
fatalIf(err.Trace(updateURL), "Please report this issue at https://github.com/minio/minio/issues.", nil) fatalIf(err.Trace(updateURL), "Please report this issue at https://github.com/minio/minio/issues.", nil)
if latest.IsZero() { // Verify if the date is not zero.
if latest.IsZero() && !noError {
fatalIf(probe.NewError(errors.New("")), fatalIf(probe.NewError(errors.New("")),
"Unable to validate any update available at this time. Please open an issue at https://github.com/minio/minio/issues", nil) "Unable to validate any update available at this time. Please open an issue at https://github.com/minio/minio/issues", nil)
} }
var downloadURL string // Is the update latest?.
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
downloadURL = newUpdateURLPrefix + "/minio.zip"
} else {
downloadURL = newUpdateURLPrefix + "/minio.gz"
}
updateMsg := updateMessage{
Download: downloadURL,
Version: minioVersion,
}
if latest.After(current) { if latest.After(current) {
updateMsg.Update = true updateMsg.Update = true
} }
console.Println(updateMsg)
// Return update message.
return updateMsg
} }
// main entry point for update command. // main entry point for update command.
func mainUpdate(ctx *cli.Context) { func mainUpdate(ctx *cli.Context) {
// Print all errors as they occur.
noError := false
// Check for update. // Check for update.
if ctx.Bool("experimental") { if ctx.Bool("experimental") {
getReleaseUpdate(minioUpdateExperimentalURL) console.Println(getReleaseUpdate(minioUpdateExperimentalURL, noError))
} else { } else {
getReleaseUpdate(minioUpdateStableURL) console.Println(getReleaseUpdate(minioUpdateStableURL, noError))
} }
} }

Loading…
Cancel
Save