Support dumb terminals by turning off color (#6246)

ANSI colors do not work on dumb terminals, in situations
when minio is running as a service under systemd.

This PR ensures we turn off color in those situations.
This commit is contained in:
Harshavardhana 2018-08-06 05:46:49 -07:00 committed by Nitish Tiwari
parent 2dede2fdc2
commit a82500f162
4 changed files with 118 additions and 31 deletions

View File

@ -18,10 +18,12 @@ package cmd
import ( import (
"crypto/x509" "crypto/x509"
"fmt"
"os" "os"
"runtime" "runtime"
"time" "time"
isatty "github.com/mattn/go-isatty"
"github.com/minio/minio-go/pkg/set" "github.com/minio/minio-go/pkg/set"
etcd "github.com/coreos/etcd/clientv3" etcd "github.com/coreos/etcd/clientv3"
@ -219,12 +221,72 @@ var (
// global colors. // global colors.
var ( var (
colorBold = color.New(color.Bold).SprintFunc() // Check if we stderr, stdout are dumb terminals, we do not apply
colorRed = color.New(color.FgRed).SprintfFunc() // ansi coloring on dumb terminals.
colorBlue = color.New(color.FgBlue).SprintfFunc() isTerminal = func() bool {
colorYellow = color.New(color.FgYellow).SprintfFunc() return isatty.IsTerminal(os.Stdout.Fd()) && isatty.IsTerminal(os.Stderr.Fd())
colorBgYellow = color.New(color.BgYellow).SprintfFunc() }
colorBlack = color.New(color.FgBlack).SprintfFunc()
colorBold = func() func(a ...interface{}) string {
if isTerminal() {
return color.New(color.Bold).SprintFunc()
}
return fmt.Sprint
}()
colorRed = func() func(format string, a ...interface{}) string {
if isTerminal() {
return color.New(color.FgRed).SprintfFunc()
}
return fmt.Sprintf
}()
colorBlue = func() func(format string, a ...interface{}) string {
if isTerminal() {
return color.New(color.FgBlue).SprintfFunc()
}
return fmt.Sprintf
}()
colorYellow = func() func(format string, a ...interface{}) string {
if isTerminal() {
return color.New(color.FgYellow).SprintfFunc()
}
return fmt.Sprintf
}()
colorCyanBold = func() func(a ...interface{}) string {
if isTerminal() {
color.New(color.FgCyan, color.Bold).SprintFunc()
}
return fmt.Sprint
}()
colorYellowBold = func() func(format string, a ...interface{}) string {
if isTerminal() {
return color.New(color.FgYellow, color.Bold).SprintfFunc()
}
return fmt.Sprintf
}()
colorBgYellow = func() func(format string, a ...interface{}) string {
if isTerminal() {
return color.New(color.BgYellow).SprintfFunc()
}
return fmt.Sprintf
}()
colorBlack = func() func(format string, a ...interface{}) string {
if isTerminal() {
return color.New(color.FgBlack).SprintfFunc()
}
return fmt.Sprintf
}()
colorGreenBold = func() func(format string, a ...interface{}) string {
if isTerminal() {
return color.New(color.FgGreen, color.Bold).SprintfFunc()
}
return fmt.Sprintf
}()
colorRedBold = func() func(format string, a ...interface{}) string {
if isTerminal() {
return color.New(color.FgRed, color.Bold).SprintfFunc()
}
return fmt.Sprintf
}()
) )
// Returns minio global information, as a key value map. // Returns minio global information, as a key value map.

View File

@ -18,17 +18,45 @@ package logger
import ( import (
"fmt" "fmt"
"os"
"regexp" "regexp"
"github.com/fatih/color" "github.com/fatih/color"
isatty "github.com/mattn/go-isatty"
) )
// Global colors. // Global colors.
var ( var (
colorBold = color.New(color.Bold).SprintFunc() // Check if we stderr, stdout are dumb terminals, we do not apply
colorFgRed = color.New(color.FgRed).SprintfFunc() // ansi coloring on dumb terminals.
colorBgRed = color.New(color.BgRed).SprintfFunc() isTerminal = func() bool {
colorFgWhite = color.New(color.FgWhite).SprintfFunc() return isatty.IsTerminal(os.Stdout.Fd()) && isatty.IsTerminal(os.Stderr.Fd())
}
colorBold = func() func(a ...interface{}) string {
if isTerminal() {
return color.New(color.Bold).SprintFunc()
}
return fmt.Sprint
}()
colorFgRed = func() func(format string, a ...interface{}) string {
if isTerminal() {
return color.New(color.FgRed).SprintfFunc()
}
return fmt.Sprintf
}()
colorBgRed = func() func(format string, a ...interface{}) string {
if isTerminal() {
return color.New(color.BgRed).SprintfFunc()
}
return fmt.Sprintf
}()
colorFgWhite = func() func(format string, a ...interface{}) string {
if isTerminal() {
return color.New(color.FgWhite).SprintfFunc()
}
return fmt.Sprintf
}()
) )
var ansiRE = regexp.MustCompile("(\x1b[^m]*m)") var ansiRE = regexp.MustCompile("(\x1b[^m]*m)")
@ -40,15 +68,21 @@ func ansiEscape(format string, args ...interface{}) {
} }
func ansiMoveRight(n int) { func ansiMoveRight(n int) {
ansiEscape("[%dC", n) if isTerminal() {
ansiEscape("[%dC", n)
}
} }
func ansiSaveAttributes() { func ansiSaveAttributes() {
ansiEscape("7") if isTerminal() {
ansiEscape("7")
}
} }
func ansiRestoreAttributes() { func ansiRestoreAttributes() {
ansiEscape("8") if isTerminal() {
ansiEscape("8")
}
} }
func uniqueEntries(paths []string) []string { func uniqueEntries(paths []string) []string {

View File

@ -30,7 +30,6 @@ import (
"strings" "strings"
"time" "time"
"github.com/fatih/color"
"github.com/inconshreveable/go-update" "github.com/inconshreveable/go-update"
"github.com/minio/cli" "github.com/minio/cli"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
@ -458,7 +457,7 @@ func getUpdateInfo(timeout time.Duration, mode string) (updateMsg string, sha256
func doUpdate(sha256Hex string, latestReleaseTime time.Time, ok bool) (updateStatusMsg string, err error) { func doUpdate(sha256Hex string, latestReleaseTime time.Time, ok bool) (updateStatusMsg string, err error) {
if !ok { if !ok {
updateStatusMsg = greenColorSprintf("Minio update to version RELEASE.%s canceled.", updateStatusMsg = colorGreenBold("Minio update to version RELEASE.%s canceled.",
latestReleaseTime.Format(minioReleaseTagTimeLayout)) latestReleaseTime.Format(minioReleaseTagTimeLayout))
return updateStatusMsg, nil return updateStatusMsg, nil
} }
@ -484,21 +483,18 @@ func doUpdate(sha256Hex string, latestReleaseTime time.Time, ok bool) (updateSta
return updateStatusMsg, err return updateStatusMsg, err
} }
return greenColorSprintf("Minio updated to version RELEASE.%s successfully.", return colorGreenBold("Minio updated to version RELEASE.%s successfully.",
latestReleaseTime.Format(minioReleaseTagTimeLayout)), nil latestReleaseTime.Format(minioReleaseTagTimeLayout)), nil
} }
func shouldUpdate(quiet bool, sha256Hex string, latestReleaseTime time.Time) (ok bool) { func shouldUpdate(quiet bool, sha256Hex string, latestReleaseTime time.Time) (ok bool) {
ok = true ok = true
if !quiet { if !quiet {
ok = prompt.Confirm(greenColorSprintf("Update to RELEASE.%s [%s]", latestReleaseTime.Format(minioReleaseTagTimeLayout), "yes")) ok = prompt.Confirm(colorGreenBold("Update to RELEASE.%s [%s]", latestReleaseTime.Format(minioReleaseTagTimeLayout), "yes"))
} }
return ok return ok
} }
var greenColorSprintf = color.New(color.FgGreen, color.Bold).SprintfFunc()
var redColorSprintf = color.New(color.FgRed, color.Bold).SprintfFunc()
func mainUpdate(ctx *cli.Context) { func mainUpdate(ctx *cli.Context) {
if len(ctx.Args()) != 0 { if len(ctx.Args()) != 0 {
cli.ShowCommandHelpAndExit(ctx, "update", -1) cli.ShowCommandHelpAndExit(ctx, "update", -1)
@ -520,7 +516,7 @@ func mainUpdate(ctx *cli.Context) {
// Nothing to update running the latest release. // Nothing to update running the latest release.
if updateMsg == "" { if updateMsg == "" {
logger.Info(greenColorSprintf("You are already running the most recent version of minio.")) logger.Info(colorGreenBold("You are already running the most recent version of minio."))
os.Exit(0) os.Exit(0)
} }
@ -531,7 +527,7 @@ func mainUpdate(ctx *cli.Context) {
var updateStatusMsg string var updateStatusMsg string
updateStatusMsg, err = doUpdate(sha256Hex, latestReleaseTime, shouldUpdate(quiet, sha256Hex, latestReleaseTime)) updateStatusMsg, err = doUpdate(sha256Hex, latestReleaseTime, shouldUpdate(quiet, sha256Hex, latestReleaseTime))
if err != nil { if err != nil {
logger.Info(redColorSprintf("Unable to update minio.")) logger.Info(colorRedBold("Unable to update minio."))
logger.Info(err.Error()) logger.Info(err.Error())
os.Exit(-1) os.Exit(-1)
} }

View File

@ -25,7 +25,6 @@ import (
"github.com/cheggaaa/pb" "github.com/cheggaaa/pb"
humanize "github.com/dustin/go-humanize" humanize "github.com/dustin/go-humanize"
"github.com/fatih/color"
) )
// prepareUpdateMessage - prepares the update message, only if a // prepareUpdateMessage - prepares the update message, only if a
@ -46,10 +45,6 @@ func prepareUpdateMessage(downloadURL string, older time.Duration) string {
// 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, newerThan string) string { func colorizeUpdateMessage(updateString string, newerThan string) string {
// Initialize coloring.
cyan := color.New(color.FgCyan, color.Bold).SprintFunc()
yellow := color.New(color.FgYellow, color.Bold).SprintfFunc()
msgLine1Fmt := " You are running an older version of Minio released %s " msgLine1Fmt := " You are running an older version of Minio released %s "
msgLine2Fmt := " Update: %s " msgLine2Fmt := " Update: %s "
@ -59,8 +54,8 @@ func colorizeUpdateMessage(updateString string, newerThan string) string {
line2Length := len(fmt.Sprintf(msgLine2Fmt, updateString)) line2Length := len(fmt.Sprintf(msgLine2Fmt, updateString))
// Populate lines with color coding. // Populate lines with color coding.
line1InColor := fmt.Sprintf(msgLine1Fmt, yellow(newerThan)) line1InColor := fmt.Sprintf(msgLine1Fmt, colorYellowBold(newerThan))
line2InColor := fmt.Sprintf(msgLine2Fmt, cyan(updateString)) line2InColor := fmt.Sprintf(msgLine2Fmt, colorCyanBold(updateString))
// calculate the rectangular box size. // calculate the rectangular box size.
maxContentWidth := int(math.Max(float64(line1Length), float64(line2Length))) maxContentWidth := int(math.Max(float64(line1Length), float64(line2Length)))
@ -94,10 +89,10 @@ func colorizeUpdateMessage(updateString string, newerThan string) string {
} }
lines := []string{ lines := []string{
yellow(topLeftChar + strings.Repeat(horizBarChar, maxContentWidth) + topRightChar), colorYellowBold(topLeftChar + strings.Repeat(horizBarChar, maxContentWidth) + topRightChar),
vertBarChar + line1InColor + strings.Repeat(" ", maxContentWidth-line1Length) + vertBarChar, vertBarChar + line1InColor + strings.Repeat(" ", maxContentWidth-line1Length) + vertBarChar,
vertBarChar + line2InColor + strings.Repeat(" ", maxContentWidth-line2Length) + vertBarChar, vertBarChar + line2InColor + strings.Repeat(" ", maxContentWidth-line2Length) + vertBarChar,
yellow(bottomLeftChar + strings.Repeat(horizBarChar, maxContentWidth) + bottomRightChar), colorYellowBold(bottomLeftChar + strings.Repeat(horizBarChar, maxContentWidth) + bottomRightChar),
} }
return "\n" + strings.Join(lines, "\n") + "\n" return "\n" + strings.Join(lines, "\n") + "\n"
} }