From 3538c9f598c189825d8eeba98d3dea3817d7df7e Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Thu, 24 Mar 2016 17:20:49 -0700 Subject: [PATCH] minio: Server upon start displays a message if update is available. This code also handles to turn itself off when network is not available and if request fails. Also prints only when the update is available. --- commands.go | 23 +++++++++-- flags.go | 18 --------- globals.go | 31 ++++++++++++++- main.go | 45 ++++++++++++++++++---- minio-main.go | 7 ++-- notifier.go | 38 ++++++++----------- update-main.go | 101 +++++++++++++++++++++++++++++++++++-------------- 7 files changed, 179 insertions(+), 84 deletions(-) diff --git a/commands.go b/commands.go index d03a693f7..e4ea007d2 100644 --- a/commands.go +++ b/commands.go @@ -18,13 +18,30 @@ package main import "github.com/minio/cli" -// Collection of minio commands currently supported are +// Collection of minio commands currently supported are. 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() -// 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) { commands = append(commands, command) commandsTree.Insert(command.Name) diff --git a/flags.go b/flags.go index 330f2103c..d2b3439d7 100644 --- a/flags.go +++ b/flags.go @@ -15,21 +15,3 @@ */ 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) -} diff --git a/globals.go b/globals.go index dd7551d00..94aec79d4 100644 --- a/globals.go +++ b/globals.go @@ -16,7 +16,11 @@ 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. const ( @@ -33,9 +37,34 @@ const ( 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. var ( colorMagenta = color.New(color.FgMagenta, color.Bold).SprintfFunc() colorWhite = color.New(color.FgWhite, 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) +} diff --git a/main.go b/main.go index 0575454ec..4a257843f 100644 --- a/main.go +++ b/main.go @@ -27,6 +27,17 @@ import ( "github.com/minio/cli" "github.com/minio/mc/pkg/console" "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. @@ -136,16 +147,13 @@ func registerApp() *cli.App { registerCommand(versionCmd) registerCommand(updateCmd) - // Register all flags. - registerFlag(configFolderFlag) - // Set up app. app := cli.NewApp() app.Name = "Minio" app.Author = "Minio.io" 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.Flags = flags + app.Flags = append(minioFlags, globalFlags...) app.Commands = commands app.CustomAppHelpTemplate = minioHelpTemplate 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) } if configPath == "" { - console.Fatalln("Config folder cannot be empty, please specify --config-folder .") + console.Fatalln("Config folder cannot be empty, please specify --config-dir .") } } @@ -179,8 +187,11 @@ func main() { app := registerApp() app.Before = func(c *cli.Context) error { + // Set global flags. + setGlobalsFromContext(c) + // Sets new config folder. - setGlobalConfigPath(c.GlobalString("config-folder")) + setGlobalConfigPath(c.GlobalString("config-dir")) // Valid input arguments to main. checkMainSyntax(c) @@ -191,11 +202,29 @@ func main() { // Enable all loggers by now. 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 } app.ExtraInfo = func() map[string]string { - return getSystemData() + if _, e := ts.GetSize(); e != nil { + globalQuiet = true + } + // Enable if debug is enabled. + if globalDebug { + return getSystemData() + } + return make(map[string]string) } - + // Run the app - exit on error. app.RunAndExitOnError() } diff --git a/minio-main.go b/minio-main.go index da46518e0..f69e2e492 100644 --- a/minio-main.go +++ b/minio-main.go @@ -200,7 +200,7 @@ func initServer(c *cli.Context) { } } -// check init arguments. +// Check init arguments. func checkInitSyntax(c *cli.Context) { if !c.Args().Present() || c.Args().First() == "help" { cli.ShowCommandHelpAndExit(c, "init", 1) @@ -214,8 +214,7 @@ func checkInitSyntax(c *cli.Context) { } } -// extract port number from address. -// address should be of the form host:port +// Extract port number from address address should be of the form host:port. func getPort(address string) int { _, portStr, e := net.SplitHostPort(address) fatalIf(probe.NewError(e), "Unable to split host port.", nil) @@ -307,6 +306,7 @@ func serverMain(c *cli.Context) { cli.ShowCommandHelpAndExit(c, "server", 1) } + // get backend. backend := serverConfig.GetBackend() if backend.Type == "fs" { // Initialize file system. @@ -348,6 +348,7 @@ func serverMain(c *cli.Context) { // Start server. err = minhttp.ListenAndServe(apiServer) 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.")) } diff --git a/notifier.go b/notifier.go index cc2d20daa..91cf54e14 100644 --- a/notifier.go +++ b/notifier.go @@ -18,7 +18,6 @@ package main import ( "fmt" - "math" "runtime" "strings" @@ -29,25 +28,22 @@ import ( // colorizeUpdateMessage - inspired from Yeoman project npm package https://github.com/yeoman/update-notifier func colorizeUpdateMessage(updateString string) (string, *probe.Error) { - // initialize coloring + // Initialize coloring. cyan := color.New(color.FgCyan, color.Bold).SprintFunc() yellow := color.New(color.FgYellow, color.Bold).SprintfFunc() - // calculate length without color coding, due to ANSI color characters padded to actual - // string the final length is wrong than the original string length - line1Str := fmt.Sprintf(" Update available: ") - line2Str := fmt.Sprintf(" Run \"%s\" to update. ", updateString) + // Calculate length without color coding, due to ANSI color + // characters padded to actual string the final length is wrong + // than the original string length. + line1Str := fmt.Sprintf(" New update: %s ", updateString) line1Length := len(line1Str) - line2Length := len(line2Str) - // populate lines with color coding - line1InColor := line1Str - line2InColor := fmt.Sprintf(" Run \"%s\" to update. ", cyan(updateString)) + // Populate lines with color coding. + line1InColor := fmt.Sprintf(" New update: %s ", cyan(updateString)) - // calculate the rectangular box size - maxContentWidth := int(math.Max(float64(line1Length), float64(line2Length))) + // Calculate the rectangular box size. + maxContentWidth := line1Length line1Rest := maxContentWidth - line1Length - line2Rest := maxContentWidth - line2Length terminal, err := ts.GetSize() if err != nil { @@ -56,31 +52,29 @@ func colorizeUpdateMessage(updateString string) (string, *probe.Error) { var message string switch { - case len(line2Str) > terminal.Col(): - message = "\n" + line1InColor + "\n" + line2InColor + "\n" + case len(line1Str) > terminal.Col(): + message = "\n" + line1InColor + "\n" default: - // on windows terminal turn off unicode characters + // On windows terminal turn off unicode characters. var top, bottom, sideBar string if runtime.GOOS == "windows" { top = yellow("*" + strings.Repeat("*", maxContentWidth) + "*") bottom = yellow("*" + strings.Repeat("*", maxContentWidth) + "*") sideBar = yellow("|") } else { - // color the rectangular box, use unicode characters here + // Color the rectangular box, use unicode characters here. top = yellow("┏" + strings.Repeat("━", maxContentWidth) + "┓") bottom = yellow("┗" + strings.Repeat("━", maxContentWidth) + "┛") sideBar = yellow("┃") } - // fill spaces to the rest of the area + // Fill spaces to the rest of the area. spacePaddingLine1 := strings.Repeat(" ", line1Rest) - spacePaddingLine2 := strings.Repeat(" ", line2Rest) - // construct the final message + // Construct the final message. message = "\n" + top + "\n" + sideBar + line1InColor + spacePaddingLine1 + sideBar + "\n" + - sideBar + line2InColor + spacePaddingLine2 + sideBar + "\n" + bottom + "\n" } - // return the final message + // Return the final message. return message, nil } diff --git a/update-main.go b/update-main.go index b51345d44..5f9cd4882 100644 --- a/update-main.go +++ b/update-main.go @@ -89,13 +89,7 @@ func (u updateMessage) String() string { updateMessage := color.New(color.FgGreen, color.Bold).SprintfFunc() return updateMessage("You are already running the most recent version of ‘minio’.") } - var msg string - if runtime.GOOS == "windows" { - msg = "Download " + u.Download - } else { - msg = "Download " + u.Download - } - msg, err := colorizeUpdateMessage(msg) + msg, err := colorizeUpdateMessage(u.Download) fatalIf(err.Trace(msg), "Unable to colorize experimental update notification string ‘"+msg+"’.", nil) return msg } @@ -141,59 +135,108 @@ func parseReleaseData(data string) (time.Time, *probe.Error) { } // 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 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) - 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("")), - "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) + if e != nil && noError { + return updateMsg + } 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("")), "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) - 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) - if latest.IsZero() { + // Verify if the date is not zero. + if latest.IsZero() && !noError { 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) } - var downloadURL string - if runtime.GOOS == "windows" || runtime.GOOS == "darwin" { - downloadURL = newUpdateURLPrefix + "/minio.zip" - } else { - downloadURL = newUpdateURLPrefix + "/minio.gz" - } - - updateMsg := updateMessage{ - Download: downloadURL, - Version: minioVersion, - } + // Is the update latest?. if latest.After(current) { updateMsg.Update = true } - console.Println(updateMsg) + + // Return update message. + return updateMsg } // main entry point for update command. func mainUpdate(ctx *cli.Context) { + // Print all errors as they occur. + noError := false + // Check for update. if ctx.Bool("experimental") { - getReleaseUpdate(minioUpdateExperimentalURL) + console.Println(getReleaseUpdate(minioUpdateExperimentalURL, noError)) } else { - getReleaseUpdate(minioUpdateStableURL) + console.Println(getReleaseUpdate(minioUpdateStableURL, noError)) } }