From 32700fca526430e15cd5bb03e2e729c4f470d15a Mon Sep 17 00:00:00 2001 From: Anis Elleuch Date: Tue, 8 May 2018 19:04:36 -0700 Subject: [PATCH] Enhance fatal errors printing of common issues seen by users (#5878) --- cmd/certs.go | 25 +++--- cmd/common-main.go | 22 +++-- cmd/config-current.go | 2 +- cmd/disk-cache-config.go | 7 +- cmd/endpoint-ellipses.go | 14 ++-- cmd/endpoint.go | 31 ++++--- cmd/endpoint_test.go | 4 +- cmd/format-fs.go | 10 +-- cmd/fs-v1.go | 10 +-- cmd/gateway-main.go | 7 ++ cmd/globals.go | 9 +- cmd/logger/logger.go | 146 ++++++++++++++++++++++++-------- cmd/logger/utils.go | 52 ++++++++++++ cmd/net.go | 8 +- cmd/server-main.go | 57 ++++++++----- cmd/storage-class.go | 9 +- cmd/storage-class_test.go | 4 +- cmd/typed-errors.go | 4 +- cmd/ui-errors-utils.go | 135 ++++++++++++++++++++++++++++++ cmd/ui-errors.go | 170 ++++++++++++++++++++++++++++++++++++++ 20 files changed, 591 insertions(+), 135 deletions(-) create mode 100644 cmd/logger/utils.go create mode 100644 cmd/ui-errors-utils.go create mode 100644 cmd/ui-errors.go diff --git a/cmd/certs.go b/cmd/certs.go index 609263c2b..5e60ad915 100644 --- a/cmd/certs.go +++ b/cmd/certs.go @@ -22,7 +22,6 @@ import ( "crypto/tls" "crypto/x509" "encoding/pem" - "fmt" "io/ioutil" "os" ) @@ -44,19 +43,19 @@ func parsePublicCertFile(certFile string) (x509Certs []*x509.Certificate, err er for len(current) > 0 { var pemBlock *pem.Block if pemBlock, current = pem.Decode(current); pemBlock == nil { - return nil, fmt.Errorf("Could not read PEM block from file %s", certFile) + return nil, uiErrSSLUnexpectedData(nil).Msg("Could not read PEM block from file %s", certFile) } var x509Cert *x509.Certificate if x509Cert, err = x509.ParseCertificate(pemBlock.Bytes); err != nil { - return nil, err + return nil, uiErrSSLUnexpectedData(err) } x509Certs = append(x509Certs, x509Cert) } if len(x509Certs) == 0 { - return nil, fmt.Errorf("Empty public certificate file %s", certFile) + return nil, uiErrSSLUnexpectedData(nil).Msg("Empty public certificate file %s", certFile) } return x509Certs, nil @@ -107,28 +106,32 @@ func getRootCAs(certsCAsDir string) (*x509.CertPool, error) { func loadX509KeyPair(certFile, keyFile string) (tls.Certificate, error) { certPEMBlock, err := ioutil.ReadFile(certFile) if err != nil { - return tls.Certificate{}, fmt.Errorf("TLS: failed to read cert file: %v", err) + return tls.Certificate{}, uiErrSSLUnexpectedError(err) } keyPEMBlock, err := ioutil.ReadFile(keyFile) if err != nil { - return tls.Certificate{}, fmt.Errorf("TLS: failed to read private key: %v", err) + return tls.Certificate{}, uiErrSSLUnexpectedError(err) } key, rest := pem.Decode(keyPEMBlock) if len(rest) > 0 { - return tls.Certificate{}, fmt.Errorf("TLS: private key contains additional data") + return tls.Certificate{}, uiErrSSLUnexpectedData(nil).Msg("The private key contains additional data") } if x509.IsEncryptedPEMBlock(key) { password, ok := os.LookupEnv(TLSPrivateKeyPassword) if !ok { - return tls.Certificate{}, fmt.Errorf("TLS: private key is encrypted but no password is present - set env var: %s", TLSPrivateKeyPassword) + return tls.Certificate{}, uiErrSSLNoPassword(nil) } decryptedKey, decErr := x509.DecryptPEMBlock(key, []byte(password)) if decErr != nil { - return tls.Certificate{}, fmt.Errorf("TLS: failed to decrypt private key: %v", decErr) + return tls.Certificate{}, uiErrSSLWrongPassword(decErr) } keyPEMBlock = pem.EncodeToMemory(&pem.Block{Type: key.Type, Bytes: decryptedKey}) } - return tls.X509KeyPair(certPEMBlock, keyPEMBlock) + cert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock) + if err != nil { + return tls.Certificate{}, uiErrSSLUnexpectedData(nil).Msg(err.Error()) + } + return cert, nil } func getSSLConfig() (x509Certs []*x509.Certificate, rootCAs *x509.CertPool, tlsCert *tls.Certificate, secureConn bool, err error) { @@ -149,7 +152,7 @@ func getSSLConfig() (x509Certs []*x509.Certificate, rootCAs *x509.CertPool, tlsC if priv, ok := cert.PrivateKey.(crypto.Signer); ok { if pub, ok := priv.Public().(*ecdsa.PublicKey); ok { if name := pub.Params().Name; name == "P-384" || name == "P-521" { // unfortunately there is no cleaner way to check - return nil, nil, nil, false, fmt.Errorf("TLS: the ECDSA curve '%s' is not supported", name) + return nil, nil, nil, false, uiErrSSLUnexpectedData(nil).Msg("tls: the ECDSA curve '%s' is not supported", name) } } diff --git a/cmd/common-main.go b/cmd/common-main.go index 01dae9889..7a5531fa4 100644 --- a/cmd/common-main.go +++ b/cmd/common-main.go @@ -45,7 +45,7 @@ func initConfig() { // Config file does not exist, we create it fresh and return upon success. if isFile(getConfigFile()) { logger.FatalIf(migrateConfig(), "Config migration failed.") - logger.FatalIf(loadConfig(), "Unable to load config version: '%s'.", serverConfigVersion) + logger.FatalIf(loadConfig(), "Unable to load the configuration file") } else { logger.FatalIf(newConfig(), "Unable to initialize minio config for the first time.") logger.Info("Created minio configuration file successfully at " + getConfigDir()) @@ -95,7 +95,9 @@ func handleCommonEnvVars() { secretKey := os.Getenv("MINIO_SECRET_KEY") if accessKey != "" && secretKey != "" { cred, err := auth.CreateCredentials(accessKey, secretKey) - logger.FatalIf(err, "Invalid access/secret Key set in environment.") + if err != nil { + logger.Fatal(uiErrInvalidCredentials(err), "Unable to validate credentials inherited from the shell environment") + } // credential Envs are set globally. globalIsEnvCreds = true @@ -105,7 +107,7 @@ func handleCommonEnvVars() { if browser := os.Getenv("MINIO_BROWSER"); browser != "" { browserFlag, err := ParseBrowserFlag(browser) if err != nil { - logger.FatalIf(errors.New("invalid value"), "Unknown value ‘%s’ in MINIO_BROWSER environment variable.", browser) + logger.Fatal(uiErrInvalidBrowserValue(nil).Msg("Unknown value `%s`", browser), "Unable to validate MINIO_BROWSER environment variable") } // browser Envs are set globally, this does not represent @@ -128,18 +130,26 @@ func handleCommonEnvVars() { if drives := os.Getenv("MINIO_CACHE_DRIVES"); drives != "" { driveList, err := parseCacheDrives(strings.Split(drives, cacheEnvDelimiter)) - logger.FatalIf(err, "Invalid value set in environment variable MINIO_CACHE_DRIVES %s.", drives) + if err != nil { + logger.Fatal(err, "Unable to parse MINIO_CACHE_DRIVES value (%s)", drives) + } globalCacheDrives = driveList globalIsDiskCacheEnabled = true } + if excludes := os.Getenv("MINIO_CACHE_EXCLUDE"); excludes != "" { excludeList, err := parseCacheExcludes(strings.Split(excludes, cacheEnvDelimiter)) - logger.FatalIf(err, "Invalid value set in environment variable MINIO_CACHE_EXCLUDE %s.", excludes) + if err != nil { + logger.Fatal(err, "Unable to parse MINIO_CACHE_EXCLUDE value (`%s`).", excludes) + } globalCacheExcludes = excludeList } + if expiryStr := os.Getenv("MINIO_CACHE_EXPIRY"); expiryStr != "" { expiry, err := strconv.Atoi(expiryStr) - logger.FatalIf(err, "Invalid value set in environment variable MINIO_CACHE_EXPIRY %s.", expiryStr) + if err != nil { + logger.Fatal(uiErrInvalidCacheExpiryValue(err), "Unable to parse MINIO_CACHE_EXPIRY value (`%s`)", expiryStr) + } globalCacheExpiry = expiry } diff --git a/cmd/config-current.go b/cmd/config-current.go index 9f237cab5..6a4d378d2 100644 --- a/cmd/config-current.go +++ b/cmd/config-current.go @@ -287,7 +287,7 @@ func getValidConfig() (*serverConfig, error) { func loadConfig() error { srvCfg, err := getValidConfig() if err != nil { - return err + return uiErrInvalidConfig(nil).Msg(err.Error()) } // If env is set override the credentials from config file. diff --git a/cmd/disk-cache-config.go b/cmd/disk-cache-config.go index 0be8c8399..6ef9a43e6 100644 --- a/cmd/disk-cache-config.go +++ b/cmd/disk-cache-config.go @@ -18,7 +18,6 @@ package cmd import ( "encoding/json" - "fmt" "path/filepath" ) @@ -54,7 +53,7 @@ func (cfg *CacheConfig) UnmarshalJSON(data []byte) (err error) { func parseCacheDrives(drives []string) ([]string, error) { for _, d := range drives { if !filepath.IsAbs(d) { - return nil, fmt.Errorf("cache dir should be absolute path: %s", d) + return nil, uiErrInvalidCacheDrivesValue(nil).Msg("cache dir should be absolute path: %s", d) } } return drives, nil @@ -64,10 +63,10 @@ func parseCacheDrives(drives []string) ([]string, error) { func parseCacheExcludes(excludes []string) ([]string, error) { for _, e := range excludes { if len(e) == 0 { - return nil, fmt.Errorf("cache exclude path (%s) cannot be empty", e) + return nil, uiErrInvalidCacheExcludesValue(nil).Msg("cache exclude path (%s) cannot be empty", e) } if hasPrefix(e, slashSeparator) { - return nil, fmt.Errorf("cache exclude pattern (%s) cannot start with / as prefix", e) + return nil, uiErrInvalidCacheExcludesValue(nil).Msg("cache exclude pattern (%s) cannot start with / as prefix", e) } } return excludes, nil diff --git a/cmd/endpoint-ellipses.go b/cmd/endpoint-ellipses.go index ac0c34ba0..3bdb4b386 100644 --- a/cmd/endpoint-ellipses.go +++ b/cmd/endpoint-ellipses.go @@ -70,11 +70,10 @@ func getSetIndexes(args []string, totalSizes []uint64) (setIndexes [][]uint64, e } setIndexes = make([][]uint64, len(totalSizes)) - for i, totalSize := range totalSizes { + for _, totalSize := range totalSizes { // Check if totalSize has minimum range upto setSize if totalSize < setSizes[0] { - return nil, fmt.Errorf("Invalid inputs (%s). Ellipses range or number of args %d should be atleast divisible by least possible set size %d", - args[i], totalSize, setSizes[0]) + return nil, uiErrInvalidNumberOfErasureEndpoints(nil) } } @@ -103,8 +102,7 @@ func getSetIndexes(args []string, totalSizes []uint64) (setIndexes [][]uint64, e // Check whether setSize is with the supported range. if !isValidSetSize(setSize) { - return nil, fmt.Errorf("Invalid inputs (%s). Ellipses range or number of args %d should be atleast divisible by least possible set size %d", - args, setSize, setSizes[0]) + return nil, uiErrInvalidNumberOfErasureEndpoints(nil) } for i := range totalSizes { @@ -167,14 +165,14 @@ func parseEndpointSet(args ...string) (ep endpointSet, err error) { for i, arg := range args { patterns, perr := ellipses.FindEllipsesPatterns(arg) if perr != nil { - return endpointSet{}, perr + return endpointSet{}, uiErrInvalidErasureEndpoints(nil).Msg(perr.Error()) } argPatterns[i] = patterns } ep.setIndexes, err = getSetIndexes(args, getTotalSizes(argPatterns)) if err != nil { - return endpointSet{}, err + return endpointSet{}, uiErrInvalidErasureEndpoints(nil).Msg(err.Error()) } ep.argPatterns = argPatterns @@ -223,7 +221,7 @@ func getAllSets(args ...string) ([][]string, error) { for _, sargs := range setArgs { for _, arg := range sargs { if uniqueArgs.Contains(arg) { - return nil, fmt.Errorf("Input args (%s) has duplicate ellipses", args) + return nil, uiErrInvalidErasureEndpoints(nil).Msg(fmt.Sprintf("Input args (%s) has duplicate ellipses", args)) } uniqueArgs.Add(arg) } diff --git a/cmd/endpoint.go b/cmd/endpoint.go index 2f060253c..50c170ed9 100644 --- a/cmd/endpoint.go +++ b/cmd/endpoint.go @@ -252,14 +252,14 @@ func CreateEndpoints(serverAddr string, args ...[]string) (string, EndpointList, return serverAddr, endpoints, setupType, err } if endpoint.Type() != PathEndpointType { - return serverAddr, endpoints, setupType, fmt.Errorf("use path style endpoint for FS setup") + return serverAddr, endpoints, setupType, uiErrInvalidFSEndpoint(nil).Msg("use path style endpoint for FS setup") } endpoints = append(endpoints, endpoint) setupType = FSSetupType // Check for cross device mounts if any. if err = checkCrossDeviceMounts(endpoints); err != nil { - return serverAddr, endpoints, setupType, err + return serverAddr, endpoints, setupType, uiErrInvalidFSEndpoint(nil).Msg(err.Error()) } return serverAddr, endpoints, setupType, nil } @@ -270,12 +270,12 @@ func CreateEndpoints(serverAddr string, args ...[]string) (string, EndpointList, var eps EndpointList eps, err = NewEndpointList(iargs...) if err != nil { - return serverAddr, endpoints, setupType, err + return serverAddr, endpoints, setupType, uiErrInvalidErasureEndpoints(nil).Msg(err.Error()) } // Check for cross device mounts if any. if err = checkCrossDeviceMounts(eps); err != nil { - return serverAddr, endpoints, setupType, err + return serverAddr, endpoints, setupType, uiErrInvalidErasureEndpoints(nil).Msg(err.Error()) } for _, ep := range eps { @@ -316,7 +316,7 @@ func CreateEndpoints(serverAddr string, args ...[]string) (string, EndpointList, // No local endpoint found. if localEndpointCount == 0 { - return serverAddr, endpoints, setupType, fmt.Errorf("no endpoint found for this host") + return serverAddr, endpoints, setupType, uiErrInvalidErasureEndpoints(nil).Msg("no endpoint pointing to the local machine is found") } // Check whether same path is not used in endpoints of a host on different port. @@ -331,8 +331,8 @@ func CreateEndpoints(serverAddr string, args ...[]string) (string, EndpointList, hostIPSet, _ := getHostIP4(host) if IPSet, ok := pathIPMap[endpoint.Path]; ok { if !IPSet.Intersection(hostIPSet).IsEmpty() { - err = fmt.Errorf("path '%s' can not be served by different port on same address", endpoint.Path) - return serverAddr, endpoints, setupType, err + return serverAddr, endpoints, setupType, + uiErrInvalidErasureEndpoints(nil).Msg(fmt.Sprintf("path '%s' can not be served by different port on same address", endpoint.Path)) } pathIPMap[endpoint.Path] = IPSet.Union(hostIPSet) } else { @@ -349,8 +349,8 @@ func CreateEndpoints(serverAddr string, args ...[]string) (string, EndpointList, continue } if localPathSet.Contains(endpoint.Path) { - err = fmt.Errorf("path '%s' cannot be served by different address on same server", endpoint.Path) - return serverAddr, endpoints, setupType, err + return serverAddr, endpoints, setupType, + uiErrInvalidErasureEndpoints(nil).Msg(fmt.Sprintf("path '%s' cannot be served by different address on same server", endpoint.Path)) } localPathSet.Add(endpoint.Path) } @@ -360,12 +360,11 @@ func CreateEndpoints(serverAddr string, args ...[]string) (string, EndpointList, { if !localPortSet.Contains(serverAddrPort) { if len(localPortSet) > 1 { - err = fmt.Errorf("port number in server address must match with one of the port in local endpoints") - } else { - err = fmt.Errorf("server address and local endpoint have different ports") + return serverAddr, endpoints, setupType, + uiErrInvalidErasureEndpoints(nil).Msg("port number in server address must match with one of the port in local endpoints") } - - return serverAddr, endpoints, setupType, err + return serverAddr, endpoints, setupType, + uiErrInvalidErasureEndpoints(nil).Msg("server address and local endpoint have different ports") } } @@ -374,7 +373,7 @@ func CreateEndpoints(serverAddr string, args ...[]string) (string, EndpointList, // If all endpoints have same port number, then this is XL setup using URL style endpoints. if len(localPortSet) == 1 { if len(localServerAddrSet) > 1 { - // TODO: Eventhough all endpoints are local, the local host is referred by different IP/name. + // TODO: Even though all endpoints are local, the local host is referred by different IP/name. // eg '172.0.0.1', 'localhost' and 'mylocalhostname' point to same local host. // // In this case, we bind to 0.0.0.0 ie to all interfaces. @@ -388,7 +387,7 @@ func CreateEndpoints(serverAddr string, args ...[]string) (string, EndpointList, return serverAddr, endpoints, setupType, nil } - // Eventhough all endpoints are local, but those endpoints use different ports. + // Even though all endpoints are local, but those endpoints use different ports. // This means it is DistXL setup. } else { // This is DistXL setup. diff --git a/cmd/endpoint_test.go b/cmd/endpoint_test.go index 125241f3f..8b8fc120e 100644 --- a/cmd/endpoint_test.go +++ b/cmd/endpoint_test.go @@ -225,8 +225,8 @@ func TestCreateEndpoints(t *testing.T) { {"localhost:10000", [][]string{{"./d1"}}, "localhost:10000", EndpointList{Endpoint{URL: &url.URL{Path: "d1"}, IsLocal: true}}, FSSetupType, nil}, {"localhost:10000", [][]string{{`\d1`}}, "localhost:10000", EndpointList{Endpoint{URL: &url.URL{Path: `\d1`}, IsLocal: true}}, FSSetupType, nil}, {"localhost:10000", [][]string{{`.\d1`}}, "localhost:10000", EndpointList{Endpoint{URL: &url.URL{Path: `.\d1`}, IsLocal: true}}, FSSetupType, nil}, - {":8080", [][]string{{"https://example.org/d1", "https://example.org/d2", "https://example.org/d3", "https://example.org/d4"}}, "", EndpointList{}, -1, fmt.Errorf("no endpoint found for this host")}, - {":8080", [][]string{{"https://example.org/d1", "https://example.com/d2", "https://example.net:8000/d3", "https://example.edu/d1"}}, "", EndpointList{}, -1, fmt.Errorf("no endpoint found for this host")}, + {":8080", [][]string{{"https://example.org/d1", "https://example.org/d2", "https://example.org/d3", "https://example.org/d4"}}, "", EndpointList{}, -1, fmt.Errorf("no endpoint pointing to the local machine is found")}, + {":8080", [][]string{{"https://example.org/d1", "https://example.com/d2", "https://example.net:8000/d3", "https://example.edu/d1"}}, "", EndpointList{}, -1, fmt.Errorf("no endpoint pointing to the local machine is found")}, {"localhost:9000", [][]string{{"https://127.0.0.1:9000/d1", "https://localhost:9001/d1", "https://example.com/d1", "https://example.com/d2"}}, "", EndpointList{}, -1, fmt.Errorf("path '/d1' can not be served by different port on same address")}, {"localhost:9000", [][]string{{"https://127.0.0.1:8000/d1", "https://localhost:9001/d2", "https://example.com/d1", "https://example.com/d2"}}, "", EndpointList{}, -1, fmt.Errorf("port number in server address must match with one of the port in local endpoints")}, {"localhost:10000", [][]string{{"https://127.0.0.1:8000/d1", "https://localhost:8000/d2", "https://example.com/d1", "https://example.com/d2"}}, "", EndpointList{}, -1, fmt.Errorf("server address and local endpoint have different ports")}, diff --git a/cmd/format-fs.go b/cmd/format-fs.go index 68f769b5a..374b889f4 100644 --- a/cmd/format-fs.go +++ b/cmd/format-fs.go @@ -24,7 +24,6 @@ import ( "path" "time" - "github.com/minio/minio/cmd/logger" "github.com/minio/minio/pkg/lock" ) @@ -113,7 +112,6 @@ func formatFSMigrateV1ToV2(ctx context.Context, wlk *lock.LockedFile, fsPath str } if err = os.MkdirAll(path.Join(fsPath, minioMetaMultipartBucket), 0755); err != nil { - logger.LogIf(ctx, err) return err } @@ -147,7 +145,7 @@ func formatFSMigrate(ctx context.Context, wlk *lock.LockedFile, fsPath string) e return err } if version != formatFSVersionV2 { - return fmt.Errorf(`%s file: expected FS version: %s, found FS version: %s`, formatConfigFile, formatFSVersionV2, version) + return uiErrUnexpectedBackendVersion(fmt.Errorf(`%s file: expected FS version: %s, found FS version: %s`, formatConfigFile, formatFSVersionV2, version)) } return nil } @@ -158,7 +156,6 @@ func createFormatFS(ctx context.Context, fsFormatPath string) error { // file stored in minioMetaBucket(.minio.sys) directory. lk, err := lock.TryLockedOpenFile(fsFormatPath, os.O_RDWR|os.O_CREATE, 0600) if err != nil { - logger.LogIf(ctx, err) return err } // Close the locked file upon return. @@ -166,7 +163,6 @@ func createFormatFS(ctx context.Context, fsFormatPath string) error { fi, err := lk.Stat() if err != nil { - logger.LogIf(ctx, err) return err } if fi.Size() != 0 { @@ -195,7 +191,6 @@ func initFormatFS(ctx context.Context, fsPath string) (rlk *lock.RLockedFile, er var fi os.FileInfo fi, err = rlk.Stat() if err != nil { - logger.LogIf(ctx, err) return nil, err } isEmpty = fi.Size() == 0 @@ -214,7 +209,6 @@ func initFormatFS(ctx context.Context, fsPath string) (rlk *lock.RLockedFile, er continue } if err != nil { - logger.LogIf(ctx, err) return nil, err } // After successfully creating format.json try to hold a read-lock on @@ -222,13 +216,11 @@ func initFormatFS(ctx context.Context, fsPath string) (rlk *lock.RLockedFile, er continue } if err != nil { - logger.LogIf(ctx, err) return nil, err } formatBackend, err := formatMetaGetFormatBackendFS(rlk) if err != nil { - logger.LogIf(ctx, err) return nil, err } if formatBackend != formatBackendFS { diff --git a/cmd/fs-v1.go b/cmd/fs-v1.go index 1c7c1fa85..b4527b6f0 100644 --- a/cmd/fs-v1.go +++ b/cmd/fs-v1.go @@ -19,7 +19,6 @@ package cmd import ( "context" "encoding/hex" - "fmt" "io" "io/ioutil" "os" @@ -80,6 +79,7 @@ func initMetaVolumeFS(fsPath, fsUUID string) error { // optimizing all other calls. Create minio meta volume, // if it doesn't exist yet. metaBucketPath := pathJoin(fsPath, minioMetaBucket) + if err := os.MkdirAll(metaBucketPath, 0777); err != nil { return err } @@ -103,7 +103,7 @@ func NewFSObjectLayer(fsPath string) (ObjectLayer, error) { var err error if fsPath, err = checkPathValid(fsPath); err != nil { - return nil, err + return nil, uiErrUnableToWriteInBackend(err) } // Assign a new UUID for FS minio mode. Each server instance @@ -112,7 +112,7 @@ func NewFSObjectLayer(fsPath string) (ObjectLayer, error) { // Initialize meta volume, if volume already exists ignores it. if err = initMetaVolumeFS(fsPath, fsUUID); err != nil { - return nil, fmt.Errorf("Unable to initialize '.minio.sys' meta volume, %s", err) + return nil, err } // Initialize `format.json`, this function also returns. @@ -142,12 +142,12 @@ func NewFSObjectLayer(fsPath string) (ObjectLayer, error) { // Initialize notification system. if err = globalNotificationSys.Init(fs); err != nil { - return nil, fmt.Errorf("Unable to initialize notification system. %v", err) + return nil, uiErrUnableToReadFromBackend(err).Msg("Unable to initialize notification system") } // Initialize policy system. if err = globalPolicySys.Init(fs); err != nil { - return nil, fmt.Errorf("Unable to initialize policy system. %v", err) + return nil, uiErrUnableToReadFromBackend(err).Msg("Unable to initialize policy system") } go fs.cleanupStaleMultipartUploads(ctx, globalMultipartCleanupInterval, globalMultipartExpiry, globalServiceDoneCh) diff --git a/cmd/gateway-main.go b/cmd/gateway-main.go index c4b969ee1..ae34e4cd4 100644 --- a/cmd/gateway-main.go +++ b/cmd/gateway-main.go @@ -112,6 +112,10 @@ func StartGateway(ctx *cli.Context, gw Gateway) { logger.FatalIf(errUnexpected, "Gateway implementation not initialized, exiting.") } + // Disable logging until gateway initialization is complete, any + // error during initialization will be shown as a fatal message + logger.Disable = true + // Validate if we have access, secret set through environment. gatewayName := gw.Name() if ctx.Args().First() == "help" { @@ -219,5 +223,8 @@ func StartGateway(ctx *cli.Context, gw Gateway) { printGatewayStartupMessage(getAPIEndpoints(gatewayAddr), gatewayName) } + // Reenable logging + logger.Disable = false + handleSignals() } diff --git a/cmd/globals.go b/cmd/globals.go index 3fc601ad2..f4d06af01 100644 --- a/cmd/globals.go +++ b/cmd/globals.go @@ -192,9 +192,12 @@ var ( // global colors. var ( - colorBold = color.New(color.Bold).SprintFunc() - colorBlue = color.New(color.FgBlue).SprintfFunc() - colorYellow = color.New(color.FgYellow).SprintfFunc() + colorBold = color.New(color.Bold).SprintFunc() + colorRed = color.New(color.FgRed).SprintfFunc() + colorBlue = color.New(color.FgBlue).SprintfFunc() + colorYellow = color.New(color.FgYellow).SprintfFunc() + colorBgYellow = color.New(color.BgYellow).SprintfFunc() + colorBlack = color.New(color.FgBlack).SprintfFunc() ) // Returns minio global information, as a key value map. diff --git a/cmd/logger/logger.go b/cmd/logger/logger.go index 40d8e37bd..7d779ec14 100644 --- a/cmd/logger/logger.go +++ b/cmd/logger/logger.go @@ -27,14 +27,8 @@ import ( "strings" "time" - "github.com/fatih/color" c "github.com/minio/mc/pkg/console" -) - -// global colors. -var ( - colorBold = color.New(color.Bold).SprintFunc() - colorRed = color.New(color.FgRed).SprintfFunc() + "golang.org/x/crypto/ssh/terminal" ) // Disable disables all logging, false by default. (used for "go test") @@ -47,9 +41,9 @@ type Level int8 // Enumerated level types const ( - Information Level = iota + 1 - Error - Fatal + InformationLvl Level = iota + 1 + ErrorLvl + FatalLvl ) const loggerTimeFormat string = "15:04:05 MST 01/02/2006" @@ -64,11 +58,11 @@ var matchingFuncNames = [...]string{ func (level Level) String() string { var lvlStr string switch level { - case Information: + case InformationLvl: lvlStr = "INFO" - case Error: + case ErrorLvl: lvlStr = "ERROR" - case Fatal: + case FatalLvl: lvlStr = "FATAL" } return lvlStr @@ -82,10 +76,9 @@ type Console interface { } func consoleLog(console Console, msg string, args ...interface{}) { - if Disable { - return - } if jsonFlag { + // Strip escape control characters from json message + msg = ansiRE.ReplaceAllLiteralString(msg, "") console.json(msg, args...) } else if quiet { console.quiet(msg, args...) @@ -124,6 +117,8 @@ type logEntry struct { // jsonFlag: Display in JSON format, if enabled var ( quiet, jsonFlag bool + // Custom function to format error + errorFmtFunc func(string, error, bool) string ) // EnableQuiet - turns quiet option on. @@ -137,11 +132,18 @@ func EnableJSON() { quiet = true } +// RegisterUIError registers the specified rendering function. This latter +// will be called for a pretty rendering of fatal errors. +func RegisterUIError(f func(string, error, bool) string) { + errorFmtFunc = f +} + // Init sets the trimStrings to possible GOPATHs // and GOROOT directories. Also append github.com/minio/minio // This is done to clean up the filename, when stack trace is // displayed when an error happens. func Init(goPath string) { + var goPathList []string var defaultgoPathList []string // Add all possible GOPATH paths into trimStrings @@ -182,8 +184,8 @@ func trimTrace(f string) string { return filepath.FromSlash(f) } -func getSource() string { - pc, file, lineNumber, ok := runtime.Caller(5) +func getSource(level int) string { + pc, file, lineNumber, ok := runtime.Caller(level) if ok { // Clean up the common prefixes file = trimTrace(file) @@ -225,7 +227,8 @@ func getTrace(traceLevel int) []string { return trace } -// LogIf : +// LogIf prints a detailed error message during +// the execution of the server. func LogIf(ctx context.Context, err error) { if Disable { return @@ -251,15 +254,17 @@ func LogIf(ctx context.Context, err error) { tags[entry.Key] = entry.Val } - // Get the cause for the Error - message := err.Error() // Get full stack trace trace := getTrace(2) + + // Get the cause for the Error + message := err.Error() + // Output the formatted log message at console var output string if jsonFlag { logJSON, err := json.Marshal(&logEntry{ - Level: Error.String(), + Level: ErrorLvl.String(), RemoteHost: req.RemoteHost, RequestID: req.RequestID, UserAgent: req.UserAgent, @@ -317,9 +322,10 @@ func LogIf(ctx context.Context, err error) { tagString = "\n " + tagString } + var msg = colorFgRed(colorBold(message)) output = fmt.Sprintf("\n%s\n%s%s%s%s\nError: %s%s\n%s", apiString, timeString, requestID, remoteHost, userAgent, - colorRed(colorBold(message)), tagString, strings.Join(trace, "\n")) + msg, tagString, strings.Join(trace, "\n")) } fmt.Println(output) } @@ -334,17 +340,28 @@ func CriticalIf(ctx context.Context, err error) { } } -// FatalIf : -// Just fatal error message, no stack trace -// It'll be called for input validation failures +// FatalIf is similar to Fatal() but it ignores passed nil error func FatalIf(err error, msg string, data ...interface{}) { - if err != nil { - if msg != "" { - consoleLog(fatalMessage, msg, data...) - } else { - consoleLog(fatalMessage, err.Error()) - } + if err == nil { + return } + fatal(err, msg, data...) +} + +// Fatal prints only fatal error message without no stack trace +// it will be called for input validation failures +func Fatal(err error, msg string, data ...interface{}) { + fatal(err, msg, data...) +} + +func fatal(err error, msg string, data ...interface{}) { + var errMsg string + if msg != "" { + errMsg = errorFmtFunc(fmt.Sprintf(msg, data...), err, jsonFlag) + } else { + errMsg = err.Error() + } + consoleLog(fatalMessage, errMsg) } var fatalMessage fatalMsg @@ -354,14 +371,15 @@ type fatalMsg struct { func (f fatalMsg) json(msg string, args ...interface{}) { logJSON, err := json.Marshal(&logEntry{ - Level: Fatal.String(), + Level: FatalLvl.String(), Time: time.Now().UTC().Format(time.RFC3339Nano), - Trace: &traceEntry{Message: fmt.Sprintf(msg, args...), Source: []string{getSource()}}, + Trace: &traceEntry{Message: fmt.Sprintf(msg, args...), Source: []string{getSource(6)}}, }) if err != nil { panic(err) } fmt.Println(string(logJSON)) + os.Exit(1) } @@ -370,9 +388,65 @@ func (f fatalMsg) quiet(msg string, args ...interface{}) { f.pretty(msg, args...) } +var ( + logTag = "ERROR" + logBanner = colorBgRed(colorFgWhite(colorBold(logTag))) + " " + emptyBanner = colorBgRed(strings.Repeat(" ", len(logTag))) + " " + minimumWidth = 80 + bannerWidth = len(logTag) + 1 +) + func (f fatalMsg) pretty(msg string, args ...interface{}) { + // Build the passed error message errMsg := fmt.Sprintf(msg, args...) - fmt.Println(colorRed(colorBold("Error: " + errMsg))) + // Check terminal width + termWidth, _, err := terminal.GetSize(0) + if err != nil || termWidth < minimumWidth { + termWidth = minimumWidth + } + // Calculate available widht without the banner + width := termWidth - bannerWidth + + tagPrinted := false + + // Print the error message: the following code takes care + // of splitting error text and always pretty printing the + // red banner along with the error message. Since the error + // message itself contains some colored text, we needed + // to use some ANSI control escapes to cursor color state + // and freely move in the screen. + for _, line := range strings.Split(errMsg, "\n") { + if len(line) == 0 { + // No more text to print, just quit. + break + } + + for { + // Save the attributes of the current cursor helps + // us save the text color of the passed error message + ansiSaveAttributes() + // Print banner with or without the log tag + if !tagPrinted { + fmt.Print(logBanner) + tagPrinted = true + } else { + fmt.Print(emptyBanner) + } + // Restore the text color of the error message + ansiRestoreAttributes() + ansiMoveRight(bannerWidth) + // Continue error message printing + if len(line) > width { + fmt.Println(line[:width]) + line = line[width:] + } else { + fmt.Println(line) + break + } + } + } + + // Exit because this is a fatal error message os.Exit(1) } @@ -383,7 +457,7 @@ type infoMsg struct { func (i infoMsg) json(msg string, args ...interface{}) { logJSON, err := json.Marshal(&logEntry{ - Level: Information.String(), + Level: InformationLvl.String(), Message: fmt.Sprintf(msg, args...), Time: time.Now().UTC().Format(time.RFC3339Nano), }) diff --git a/cmd/logger/utils.go b/cmd/logger/utils.go new file mode 100644 index 000000000..a18186925 --- /dev/null +++ b/cmd/logger/utils.go @@ -0,0 +1,52 @@ +/* + * Minio Cloud Storage, (C) 2018 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 logger + +import ( + "fmt" + "regexp" + + "github.com/fatih/color" +) + +// Global colors. +var ( + colorBold = color.New(color.Bold).SprintFunc() + colorFgRed = color.New(color.FgRed).SprintfFunc() + colorBgRed = color.New(color.BgRed).SprintfFunc() + colorFgWhite = color.New(color.FgWhite).SprintfFunc() +) + +var ansiRE = regexp.MustCompile("(\x1b[^m]*m)") + +// Print ANSI Control escape +func ansiEscape(format string, args ...interface{}) { + var Esc = "\x1b" + fmt.Printf("%s%s", Esc, fmt.Sprintf(format, args...)) +} + +func ansiMoveRight(n int) { + ansiEscape("[%dC", n) +} + +func ansiSaveAttributes() { + ansiEscape("7") +} + +func ansiRestoreAttributes() { + ansiEscape("8") +} diff --git a/cmd/net.go b/cmd/net.go index 0bc7b4d8e..380bfc9ed 100644 --- a/cmd/net.go +++ b/cmd/net.go @@ -352,15 +352,15 @@ func sameLocalAddrs(addr1, addr2 string) (bool, error) { func CheckLocalServerAddr(serverAddr string) error { host, port, err := net.SplitHostPort(serverAddr) if err != nil { - return err + return uiErrInvalidAddressFlag(err) } // Check whether port is a valid port number. p, err := strconv.Atoi(port) if err != nil { - return fmt.Errorf("invalid port number") + return uiErrInvalidAddressFlag(err).Msg("invalid port number") } else if p < 1 || p > 65535 { - return fmt.Errorf("port number must be between 1 to 65535") + return uiErrInvalidAddressFlag(nil).Msg("port number must be between 1 to 65535") } // 0.0.0.0 is a wildcard address and refers to local network @@ -372,7 +372,7 @@ func CheckLocalServerAddr(serverAddr string) error { return err } if !isLocalHost { - return fmt.Errorf("host in server address should be this server") + return uiErrInvalidAddressFlag(nil).Msg("host in server address should be this server") } } diff --git a/cmd/server-main.go b/cmd/server-main.go index 4168169ff..da11203de 100644 --- a/cmd/server-main.go +++ b/cmd/server-main.go @@ -18,10 +18,10 @@ package cmd import ( "context" + "fmt" "net/http" "os" "os/signal" - "runtime" "syscall" "github.com/minio/cli" @@ -118,26 +118,26 @@ func serverHandleCmdArgs(ctx *cli.Context) { // Server address. serverAddr := ctx.String("address") - logger.FatalIf(CheckLocalServerAddr(serverAddr), "Invalid address ‘%s’ in command line argument.", serverAddr) + logger.FatalIf(CheckLocalServerAddr(serverAddr), "Unable to validate passed arguments") var setupType SetupType var err error if len(ctx.Args()) > serverCommandLineArgsMax { - logger.FatalIf(errInvalidArgument, "Invalid total number of arguments (%d) passed, supported upto 32 unique arguments", len(ctx.Args())) + uErr := uiErrInvalidErasureEndpoints(nil).Msg(fmt.Sprintf("Invalid total number of endpoints (%d) passed, supported upto 32 unique arguments", len(ctx.Args()))) + logger.FatalIf(uErr, "Unable to validate passed endpoints") } globalMinioAddr, globalEndpoints, setupType, globalXLSetCount, globalXLSetDriveCount, err = createServerEndpoints(serverAddr, ctx.Args()...) - logger.FatalIf(err, "Invalid command line arguments server=‘%s’, args=%s", serverAddr, ctx.Args()) + logger.FatalIf(err, "Invalid command line arguments") globalMinioHost, globalMinioPort = mustSplitHostPort(globalMinioAddr) - if runtime.GOOS == "darwin" { - // On macOS, if a process already listens on LOCALIPADDR:PORT, net.Listen() falls back - // to IPv6 address ie minio will start listening on IPv6 address whereas another - // (non-)minio process is listening on IPv4 of given port. - // To avoid this error sutiation we check for port availability only for macOS. - logger.FatalIf(checkPortAvailability(globalMinioPort), "Port %d already in use", globalMinioPort) - } + + // On macOS, if a process already listens on LOCALIPADDR:PORT, net.Listen() falls back + // to IPv6 address ie minio will start listening on IPv6 address whereas another + // (non-)minio process is listening on IPv4 of given port. + // To avoid this error sutiation we check for port availability. + logger.FatalIf(checkPortAvailability(globalMinioPort), "Unable to start the server") globalIsXL = (setupType == XLSetupType) globalIsDistXL = (setupType == DistXLSetupType) @@ -168,6 +168,10 @@ func serverMain(ctx *cli.Context) { cli.ShowCommandHelpAndExit(ctx, "server", 1) } + // Disable logging until server initialization is complete, any + // error during initialization will be shown as a fatal message + logger.Disable = true + // Get "json" flag from command line argument and // enable json and quite modes if jason flag is turned on. jsonFlag := ctx.IsSet("json") || ctx.GlobalIsSet("json") @@ -181,6 +185,8 @@ func serverMain(ctx *cli.Context) { logger.EnableQuiet() } + logger.RegisterUIError(fmtError) + // Handle all server command args. serverHandleCmdArgs(ctx) @@ -188,7 +194,7 @@ func serverMain(ctx *cli.Context) { serverHandleEnvVars() // Create certs path. - logger.FatalIf(createConfigDir(), "Unable to create configuration directories.") + logger.FatalIf(createConfigDir(), "Unable to initialize configuration files") // Initialize server config. initConfig() @@ -196,15 +202,15 @@ func serverMain(ctx *cli.Context) { // Check and load SSL certificates. var err error globalPublicCerts, globalRootCAs, globalTLSCertificate, globalIsSSL, err = getSSLConfig() - logger.FatalIf(err, "Invalid SSL certificate file") + logger.FatalIf(err, "Unable to load the TLS configuration") // Is distributed setup, error out if no certificates are found for HTTPS endpoints. if globalIsDistXL { if globalEndpoints.IsHTTPS() && !globalIsSSL { - logger.FatalIf(errInvalidArgument, "No certificates found, use HTTP endpoints (%s)", globalEndpoints) + logger.Fatal(uiErrNoCertsAndHTTPSEndpoints(nil), "Unable to start the server") } if !globalEndpoints.IsHTTPS() && globalIsSSL { - logger.FatalIf(errInvalidArgument, "TLS Certificates found, use HTTPS endpoints (%s)", globalEndpoints) + logger.Fatal(uiErrCertsAndHTTPEndpoints(nil), "Unable to start the server") } } @@ -225,7 +231,9 @@ func serverMain(ctx *cli.Context) { // Set nodes for dsync for distributed setup. if globalIsDistXL { globalDsync, err = dsync.New(newDsyncNodes(globalEndpoints)) - logger.FatalIf(err, "Unable to initialize distributed locking on %s", globalEndpoints) + if err != nil { + logger.Fatal(err, "Unable to initialize distributed locking on %s", globalEndpoints) + } } // Initialize name space lock. @@ -237,11 +245,15 @@ func serverMain(ctx *cli.Context) { // Configure server. var handler http.Handler handler, err = configureServerHandler(globalEndpoints) - logger.FatalIf(err, "Unable to configure one of server's RPC services.") + if err != nil { + logger.Fatal(uiErrUnexpectedError(err), "Unable to configure one of server's RPC services.") + } // Create new notification system. globalNotificationSys, err = NewNotificationSys(globalServerConfig, globalEndpoints) - logger.FatalIf(err, "Unable to create new notification system.") + if err != nil { + logger.Fatal(uiErrUnexpectedError(err), "Unable to create new notification system.") + } // Create new policy system. globalPolicySys = NewPolicySys() @@ -262,10 +274,8 @@ func serverMain(ctx *cli.Context) { newObject, err := newObjectLayer(globalEndpoints) if err != nil { - logger.LogIf(context.Background(), err) - err = globalHTTPServer.Shutdown() - logger.LogIf(context.Background(), err) - os.Exit(1) + globalHTTPServer.Shutdown() + logger.FatalIf(err, "Unable to initialize backend") } globalObjLayerMutex.Lock() @@ -279,6 +289,9 @@ func serverMain(ctx *cli.Context) { // Set uptime time after object layer has initialized. globalBootTime = UTCNow() + // Re-enable logging + logger.Disable = false + handleSignals() } diff --git a/cmd/storage-class.go b/cmd/storage-class.go index 6c039a67f..60361fb3a 100644 --- a/cmd/storage-class.go +++ b/cmd/storage-class.go @@ -18,7 +18,6 @@ package cmd import ( "encoding/json" - "errors" "fmt" "strconv" "strings" @@ -104,20 +103,20 @@ func parseStorageClass(storageClassEnv string) (sc storageClass, err error) { // only two elements allowed in the string - "scheme" and "number of parity disks" if len(s) > 2 { - return storageClass{}, errors.New("Too many sections in " + storageClassEnv) + return storageClass{}, uiErrStorageClassValue(nil).Msg("Too many sections in " + storageClassEnv) } else if len(s) < 2 { - return storageClass{}, errors.New("Too few sections in " + storageClassEnv) + return storageClass{}, uiErrStorageClassValue(nil).Msg("Too few sections in " + storageClassEnv) } // only allowed scheme is "EC" if s[0] != supportedStorageClassScheme { - return storageClass{}, errors.New("Unsupported scheme " + s[0] + ". Supported scheme is EC") + return storageClass{}, uiErrStorageClassValue(nil).Msg("Unsupported scheme " + s[0] + ". Supported scheme is EC") } // Number of parity disks should be integer parityDisks, err := strconv.Atoi(s[1]) if err != nil { - return storageClass{}, err + return storageClass{}, uiErrStorageClassValue(err) } sc = storageClass{ diff --git a/cmd/storage-class_test.go b/cmd/storage-class_test.go index cba2c6e44..72bec8aa4 100644 --- a/cmd/storage-class_test.go +++ b/cmd/storage-class_test.go @@ -69,8 +69,8 @@ func testParseStorageClass(obj ObjectLayer, instanceType string, t TestErrHandle t.Errorf("Test %d, Expected %v, got %v", i+1, tt.wantSc, gotSc) return } - if tt.expectedError != nil && !reflect.DeepEqual(err, tt.expectedError) { - t.Errorf("Test %d, Expected %v, got %v", i+1, tt.expectedError, err) + if tt.expectedError != nil && err.Error() != tt.expectedError.Error() { + t.Errorf("Test %d, Expected `%v`, got `%v`", i+1, tt.expectedError, err) } } } diff --git a/cmd/typed-errors.go b/cmd/typed-errors.go index 60ecab559..4a984a03d 100644 --- a/cmd/typed-errors.go +++ b/cmd/typed-errors.go @@ -16,7 +16,9 @@ package cmd -import "errors" +import ( + "errors" +) // errInvalidArgument means that input argument is invalid. var errInvalidArgument = errors.New("Invalid arguments specified") diff --git a/cmd/ui-errors-utils.go b/cmd/ui-errors-utils.go new file mode 100644 index 000000000..3bc0b16cb --- /dev/null +++ b/cmd/ui-errors-utils.go @@ -0,0 +1,135 @@ +/* + * Minio Cloud Storage, (C) 2018 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 ( + "fmt" + "io" + "net" + "os" +) + +// uiErr is a structure which contains all information +// to print a fatal error message in json or pretty mode +// uiErr implements error so we can use it anywhere +type uiErr struct { + msg string + detail string + action string + help string +} + +// Return the error message +func (u uiErr) Error() string { + if u.detail == "" { + return u.msg + } + return u.detail +} + +// Replace the current error's message +func (u uiErr) Msg(m string, args ...interface{}) uiErr { + return uiErr{ + msg: fmt.Sprintf(m, args...), + detail: u.detail, + action: u.action, + help: u.help, + } +} + +type uiErrFn func(err error) uiErr + +// Create a UI error generator, this is needed to simplify +// the update of the detailed error message in several places +// in Minio code +func newUIErrFn(msg, action, help string) uiErrFn { + return func(err error) uiErr { + u := uiErr{ + msg: msg, + action: action, + help: help, + } + if err != nil { + u.detail = err.Error() + } + return u + } +} + +// errorToUIError inspects the passed error and transforms it +// to the appropriate UI error. +func errorToUIErr(err error) uiErr { + // If this is already a uiErr, do nothing + if e, ok := err.(uiErr); ok { + return e + } + + // Show a generic message for known golang errors + switch e := err.(type) { + case *net.OpError: + if e.Op == "listen" { + return uiErrPortAlreadyInUse(e).Msg("Port " + e.Addr.String() + " is already in use") + + } + case *os.PathError: + if os.IsPermission(e) { + return uiErrNoPermissionsToAccessDirFiles(e).Msg("Unsufficent permissions to access `" + e.Path + "` path") + } + } + + switch err { + case io.ErrUnexpectedEOF: + return uiErrUnexpectedDataContent(err) + default: + // Failed to identify what type of error this, return a simple UI error + return uiErr{msg: err.Error()} + } + +} + +// fmtError() converts a fatal error message to a more understood error +// using some colors +func fmtError(introMsg string, err error, jsonFlag bool) string { + renderedTxt := "" + uiErr := errorToUIErr(err) + // JSON print + if jsonFlag { + // Message text in json should be simple + if uiErr.detail != "" { + return uiErr.msg + ": " + uiErr.detail + } + return uiErr.msg + } + // Pretty print error message + introMsg += ": " + if uiErr.msg != "" { + introMsg += colorBold(uiErr.msg + ".") + } else { + introMsg += colorBold(err.Error() + ".") + } + renderedTxt += colorRed(introMsg) + "\n" + // Add action message + if uiErr.action != "" { + renderedTxt += "> " + colorBgYellow(colorBlack(uiErr.action+".")) + "\n" + } + // Add help + if uiErr.help != "" { + renderedTxt += colorBold("HELP:") + "\n" + renderedTxt += " " + uiErr.help + } + return renderedTxt +} diff --git a/cmd/ui-errors.go b/cmd/ui-errors.go new file mode 100644 index 000000000..ef4c57720 --- /dev/null +++ b/cmd/ui-errors.go @@ -0,0 +1,170 @@ +/* + * Minio Cloud Storage, (C) 2018 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 + +var ( + uiErrInvalidConfig = newUIErrFn( + "Invalid value found in the configuration file", + "Please ensure a valid value in the configuration file, for more details refer https://docs.minio.io/docs/minio-server-configuration-guide", + "", + ) + + uiErrInvalidBrowserValue = newUIErrFn( + "Invalid browser value", + "Please check the passed value", + "Browser can only accept `on` and `off` values. To disable web browser access, set this value to `off`", + ) + + uiErrInvalidCacheDrivesValue = newUIErrFn( + "Invalid cache drive value", + "Please check the passed value", + "MINIO_CACHE_DRIVES: List of mounted drives or directories delimited by `;`.", + ) + + uiErrInvalidCacheExcludesValue = newUIErrFn( + "Invalid cache excludes value", + "Please check the passed value", + "MINIO_CACHE_EXCLUDE: List of cache exclusion patterns delimited by `;`.", + ) + + uiErrInvalidCacheExpiryValue = newUIErrFn( + "Invalid cache expiry value", + "Please check the passed value", + "MINIO_CACHE_EXPIRY: Cache expiry duration in days.", + ) + + uiErrInvalidCredentials = newUIErrFn( + "Passed credentials are not suitable for use", + "Please provide correct credentials", + `Access key length should be between minimum 3 characters in length. +Secret key should not be between 8 and 40 characters.`, + ) + + uiErrInvalidErasureEndpoints = newUIErrFn( + "Invalid endpoints to use in erasure mode", + "Please provide correct combinations of local/remote paths", + "For more information, please refer to the following link: https://docs.minio.io/docs/minio-erasure-code-quickstart-guide", + ) + + uiErrInvalidNumberOfErasureEndpoints = newUIErrFn( + "The total number of endpoints is not suitable for erasure mode", + "Please provide an even number of endpoints greater or equal to 4", + "For more information, please refer to the following link: https://docs.minio.io/docs/minio-erasure-code-quickstart-guide", + ) + + uiErrStorageClassValue = newUIErrFn( + "Invalid storage class value", + "Please check the passed value", + `MINIO_STORAGE_CLASS_STANDARD: Format "EC:" (e.g. "EC:3"). This sets the number of parity disks for Minio server in Standard mode. Object are stored in Standard mode, if storage class is not defined in Put request. +MINIO_STORAGE_CLASS_RRS: Format "EC:" (e.g. "EC:3"). This sets the number of parity disks for Minio server in reduced redundancy mode. Objects are stored in Reduced Redundancy mode, if Put request specifies RRS storage class. +Refer to the link https://github.com/minio/minio/tree/master/docs/erasure/storage-class for more information.`, + ) + + uiErrUnexpectedBackendVersion = newUIErrFn( + "Backend version seems to be too recent", + "Please update to the latest Minio version", + "", + ) + + uiErrInvalidAddressFlag = newUIErrFn( + "--address input is invalid", + "Please check --address parameter", + `--address binds a specific ADDRESS:PORT, ADDRESS can be an IP or hostname (default:':9000') + Examples: --address ':443' + --address '172.16.34.31:9000'`, + ) + + uiErrInvalidFSEndpoint = newUIErrFn( + "The given endpoint is not suitable to activate standalone FS mode", + "Please check the given FS endpoint", + `FS mode requires only one writable disk path. +Example 1: + $ minio server /data/minio/`, + ) + + uiErrUnableToWriteInBackend = newUIErrFn( + "Unable to write to the backend", + "Please ensure that Minio binary has write permissions to the backend", + "", + ) + + uiErrUnableToReadFromBackend = newUIErrFn( + "Unable to read from the backend", + "Please ensure that Minio binary has read permission from the backend", + "", + ) + + uiErrPortAlreadyInUse = newUIErrFn( + "Port is already in use", + "Please ensure no other program is using the same address/port", + "", + ) + + uiErrNoPermissionsToAccessDirFiles = newUIErrFn( + "Missing permissions to access to the specified path", + "Please ensure the specified path can be accessed", + "", + ) + + uiErrSSLUnexpectedError = newUIErrFn( + "Invalid TLS certificate", + "Please check the content of your certificate data", + `Only PEM (x.509) format is accepted as valid public & private certificates.`, + ) + + uiErrSSLUnexpectedData = newUIErrFn( + "Something is wrong with your TLS certificate", + "Please check your certificate", + "", + ) + + uiErrSSLNoPassword = newUIErrFn( + "no password was specified", + "Please set the password to this environment variable `"+TLSPrivateKeyPassword+"` so the private key can be decrypted", + "", + ) + + uiErrNoCertsAndHTTPSEndpoints = newUIErrFn( + "HTTPS is specified in endpoint URLs but no TLS certificate is found on the local machine", + "Please add a certificate or switch to HTTP.", + "Refer to https://docs.minio.io/docs/how-to-secure-access-to-minio-server-with-tls for information about how to load a TLS certificate in th server.", + ) + + uiErrCertsAndHTTPEndpoints = newUIErrFn( + "HTTP is specified in endpoint URLs but the server in the local machine is configured with a TLS certificate", + "Please remove the certificate in the configuration directory or switch to HTTPS", + "", + ) + + uiErrSSLWrongPassword = newUIErrFn( + "Unable to decrypt the private key using the provided password", + "Please set the correct password in "+TLSPrivateKeyPassword, + "", + ) + + uiErrUnexpectedDataContent = newUIErrFn( + "Unexpected data content", + "Please contact us at https://slack.minio.io", + "", + ) + + uiErrUnexpectedError = newUIErrFn( + "Unexpected error", + "Please contact us at https://slack.minio.io", + "", + ) +)