mirror of
https://github.com/minio/minio.git
synced 2024-12-25 14:45:54 -05:00
72929ec05b
This PR also brings --compat option to run MinIO in strict S3 compatibility mode, MinIO by default will now try to run high performance mode.
417 lines
14 KiB
Go
417 lines
14 KiB
Go
/*
|
|
* MinIO Cloud Storage, (C) 2017, 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 (
|
|
"crypto/tls"
|
|
"errors"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
etcd "github.com/coreos/etcd/clientv3"
|
|
dns2 "github.com/miekg/dns"
|
|
"github.com/minio/cli"
|
|
"github.com/minio/minio-go/pkg/set"
|
|
"github.com/minio/minio/cmd/logger"
|
|
"github.com/minio/minio/cmd/logger/target/console"
|
|
"github.com/minio/minio/cmd/logger/target/http"
|
|
"github.com/minio/minio/pkg/auth"
|
|
"github.com/minio/minio/pkg/dns"
|
|
xnet "github.com/minio/minio/pkg/net"
|
|
)
|
|
|
|
// Check for updates and print a notification message
|
|
func checkUpdate(mode string) {
|
|
// Its OK to ignore any errors during doUpdate() here.
|
|
if updateMsg, _, currentReleaseTime, latestReleaseTime, err := getUpdateInfo(2*time.Second, mode); err == nil {
|
|
if globalInplaceUpdateDisabled {
|
|
logger.StartupMessage(updateMsg)
|
|
} else {
|
|
logger.StartupMessage(prepareUpdateMessage("Run `minio update`", latestReleaseTime.Sub(currentReleaseTime)))
|
|
}
|
|
}
|
|
}
|
|
|
|
// Load logger targets based on user's configuration
|
|
func loadLoggers() {
|
|
auditEndpoint, ok := os.LookupEnv("MINIO_AUDIT_LOGGER_HTTP_ENDPOINT")
|
|
if ok {
|
|
// Enable audit HTTP logging through ENV.
|
|
logger.AddAuditTarget(http.New(auditEndpoint, NewCustomHTTPTransport()))
|
|
}
|
|
|
|
loggerEndpoint, ok := os.LookupEnv("MINIO_LOGGER_HTTP_ENDPOINT")
|
|
if ok {
|
|
// Enable HTTP logging through ENV.
|
|
logger.AddTarget(http.New(loggerEndpoint, NewCustomHTTPTransport()))
|
|
} else {
|
|
for _, l := range globalServerConfig.Logger.HTTP {
|
|
if l.Enabled {
|
|
// Enable http logging
|
|
logger.AddTarget(http.New(l.Endpoint, NewCustomHTTPTransport()))
|
|
}
|
|
}
|
|
}
|
|
|
|
if globalServerConfig.Logger.Console.Enabled {
|
|
// Enable console logging
|
|
logger.AddTarget(console.New())
|
|
}
|
|
|
|
}
|
|
|
|
func newConfigDirFromCtx(ctx *cli.Context, option string, getDefaultDir func() string) (*ConfigDir, bool) {
|
|
var dir string
|
|
var dirSet bool
|
|
|
|
switch {
|
|
case ctx.IsSet(option):
|
|
dir = ctx.String(option)
|
|
dirSet = true
|
|
case ctx.GlobalIsSet(option):
|
|
dir = ctx.GlobalString(option)
|
|
dirSet = true
|
|
// cli package does not expose parent's option option. Below code is workaround.
|
|
if dir == "" || dir == getDefaultDir() {
|
|
dirSet = false // Unset to false since GlobalIsSet() true is a false positive.
|
|
if ctx.Parent().GlobalIsSet(option) {
|
|
dir = ctx.Parent().GlobalString(option)
|
|
dirSet = true
|
|
}
|
|
}
|
|
default:
|
|
// Neither local nor global option is provided. In this case, try to use
|
|
// default directory.
|
|
dir = getDefaultDir()
|
|
if dir == "" {
|
|
logger.FatalIf(errInvalidArgument, "%s option must be provided", option)
|
|
}
|
|
}
|
|
|
|
if dir == "" {
|
|
logger.FatalIf(errors.New("empty directory"), "%s directory cannot be empty", option)
|
|
}
|
|
|
|
// Disallow relative paths, figure out absolute paths.
|
|
dirAbs, err := filepath.Abs(dir)
|
|
logger.FatalIf(err, "Unable to fetch absolute path for %s=%s", option, dir)
|
|
|
|
logger.FatalIf(mkdirAllIgnorePerm(dirAbs), "Unable to create directory specified %s=%s", option, dir)
|
|
|
|
return &ConfigDir{path: dirAbs}, dirSet
|
|
}
|
|
|
|
func handleCommonCmdArgs(ctx *cli.Context) {
|
|
|
|
// Get "json" flag from command line argument and
|
|
// enable json and quite modes if json flag is turned on.
|
|
globalCLIContext.JSON = ctx.IsSet("json") || ctx.GlobalIsSet("json")
|
|
if globalCLIContext.JSON {
|
|
logger.EnableJSON()
|
|
}
|
|
|
|
// Get quiet flag from command line argument.
|
|
globalCLIContext.Quiet = ctx.IsSet("quiet") || ctx.GlobalIsSet("quiet")
|
|
if globalCLIContext.Quiet {
|
|
logger.EnableQuiet()
|
|
}
|
|
|
|
// Get anonymous flag from command line argument.
|
|
globalCLIContext.Anonymous = ctx.IsSet("anonymous") || ctx.GlobalIsSet("anonymous")
|
|
if globalCLIContext.Anonymous {
|
|
logger.EnableAnonymous()
|
|
}
|
|
|
|
// Fetch address option
|
|
globalCLIContext.Addr = ctx.GlobalString("address")
|
|
if globalCLIContext.Addr == "" || globalCLIContext.Addr == ":"+globalMinioDefaultPort {
|
|
globalCLIContext.Addr = ctx.String("address")
|
|
}
|
|
|
|
// Set all config, certs and CAs directories.
|
|
var configSet, certsSet bool
|
|
globalConfigDir, configSet = newConfigDirFromCtx(ctx, "config-dir", defaultConfigDir.Get)
|
|
globalCertsDir, certsSet = newConfigDirFromCtx(ctx, "certs-dir", defaultCertsDir.Get)
|
|
|
|
// Remove this code when we deprecate and remove config-dir.
|
|
// This code is to make sure we inherit from the config-dir
|
|
// option if certs-dir is not provided.
|
|
if !certsSet && configSet {
|
|
globalCertsDir = &ConfigDir{path: filepath.Join(globalConfigDir.Get(), certsDir)}
|
|
}
|
|
|
|
globalCertsCADir = &ConfigDir{path: filepath.Join(globalCertsDir.Get(), certsCADir)}
|
|
|
|
logger.FatalIf(mkdirAllIgnorePerm(globalCertsCADir.Get()), "Unable to create certs CA directory at %s", globalCertsCADir.Get())
|
|
|
|
// Check "compat" flag from command line argument.
|
|
globalCLIContext.StrictS3Compat = ctx.IsSet("compat") || ctx.GlobalIsSet("compat")
|
|
}
|
|
|
|
// Parses the given compression exclude list `extensions` or `content-types`.
|
|
func parseCompressIncludes(includes []string) ([]string, error) {
|
|
for _, e := range includes {
|
|
if len(e) == 0 {
|
|
return nil, uiErrInvalidCompressionIncludesValue(nil).Msg("extension/mime-type (%s) cannot be empty", e)
|
|
}
|
|
}
|
|
return includes, nil
|
|
}
|
|
|
|
func handleCommonEnvVars() {
|
|
compressEnvDelimiter := ","
|
|
// Start profiler if env is set.
|
|
if profiler := os.Getenv("_MINIO_PROFILER"); profiler != "" {
|
|
var err error
|
|
globalProfiler, err = startProfiler(profiler, "")
|
|
logger.FatalIf(err, "Unable to setup a profiler")
|
|
}
|
|
|
|
accessKey := os.Getenv("MINIO_ACCESS_KEY")
|
|
secretKey := os.Getenv("MINIO_SECRET_KEY")
|
|
if accessKey != "" && secretKey != "" {
|
|
cred, err := auth.CreateCredentials(accessKey, secretKey)
|
|
if err != nil {
|
|
logger.Fatal(uiErrInvalidCredentials(err), "Unable to validate credentials inherited from the shell environment")
|
|
}
|
|
cred.Expiration = timeSentinel
|
|
|
|
// credential Envs are set globally.
|
|
globalIsEnvCreds = true
|
|
globalActiveCred = cred
|
|
}
|
|
|
|
if browser := os.Getenv("MINIO_BROWSER"); browser != "" {
|
|
browserFlag, err := ParseBoolFlag(browser)
|
|
if err != nil {
|
|
logger.Fatal(uiErrInvalidBrowserValue(nil).Msg("Unknown value `%s`", browser), "Invalid MINIO_BROWSER value in environment variable")
|
|
}
|
|
|
|
// browser Envs are set globally, this does not represent
|
|
// if browser is turned off or on.
|
|
globalIsEnvBrowser = true
|
|
globalIsBrowserEnabled = bool(browserFlag)
|
|
}
|
|
|
|
traceFile := os.Getenv("MINIO_HTTP_TRACE")
|
|
if traceFile != "" {
|
|
var err error
|
|
globalHTTPTraceFile, err = os.OpenFile(traceFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0660)
|
|
logger.FatalIf(err, "error opening file %s", traceFile)
|
|
}
|
|
|
|
etcdEndpointsEnv, ok := os.LookupEnv("MINIO_ETCD_ENDPOINTS")
|
|
if ok {
|
|
etcdEndpoints := strings.Split(etcdEndpointsEnv, ",")
|
|
|
|
var etcdSecure bool
|
|
for _, endpoint := range etcdEndpoints {
|
|
u, err := xnet.ParseURL(endpoint)
|
|
if err != nil {
|
|
logger.FatalIf(err, "Unable to initialize etcd with %s", etcdEndpoints)
|
|
}
|
|
// If one of the endpoint is https, we will use https directly.
|
|
etcdSecure = etcdSecure || u.Scheme == "https"
|
|
}
|
|
|
|
var err error
|
|
if etcdSecure {
|
|
// This is only to support client side certificate authentication
|
|
// https://coreos.com/etcd/docs/latest/op-guide/security.html
|
|
etcdClientCertFile, ok1 := os.LookupEnv("MINIO_ETCD_CLIENT_CERT")
|
|
etcdClientCertKey, ok2 := os.LookupEnv("MINIO_ETCD_CLIENT_CERT_KEY")
|
|
var getClientCertificate func(*tls.CertificateRequestInfo) (*tls.Certificate, error)
|
|
if ok1 && ok2 {
|
|
getClientCertificate = func(unused *tls.CertificateRequestInfo) (*tls.Certificate, error) {
|
|
cert, terr := tls.LoadX509KeyPair(etcdClientCertFile, etcdClientCertKey)
|
|
return &cert, terr
|
|
}
|
|
}
|
|
|
|
globalEtcdClient, err = etcd.New(etcd.Config{
|
|
Endpoints: etcdEndpoints,
|
|
DialTimeout: defaultDialTimeout,
|
|
DialKeepAliveTime: defaultDialKeepAlive,
|
|
TLS: &tls.Config{
|
|
RootCAs: globalRootCAs,
|
|
GetClientCertificate: getClientCertificate,
|
|
},
|
|
})
|
|
} else {
|
|
globalEtcdClient, err = etcd.New(etcd.Config{
|
|
Endpoints: etcdEndpoints,
|
|
DialTimeout: defaultDialTimeout,
|
|
DialKeepAliveTime: defaultDialKeepAlive,
|
|
})
|
|
}
|
|
logger.FatalIf(err, "Unable to initialize etcd with %s", etcdEndpoints)
|
|
}
|
|
|
|
v, ok := os.LookupEnv("MINIO_DOMAIN")
|
|
if ok {
|
|
for _, domainName := range strings.Split(v, ",") {
|
|
if _, ok = dns2.IsDomainName(domainName); !ok {
|
|
logger.Fatal(uiErrInvalidDomainValue(nil).Msg("Unknown value `%s`", domainName),
|
|
"Invalid MINIO_DOMAIN value in environment variable")
|
|
}
|
|
globalDomainNames = append(globalDomainNames, domainName)
|
|
}
|
|
}
|
|
|
|
minioEndpointsEnv, ok := os.LookupEnv("MINIO_PUBLIC_IPS")
|
|
if ok {
|
|
minioEndpoints := strings.Split(minioEndpointsEnv, ",")
|
|
var domainIPs = set.NewStringSet()
|
|
for _, endpoint := range minioEndpoints {
|
|
if net.ParseIP(endpoint) == nil {
|
|
// Checking if the IP is a DNS entry.
|
|
addrs, err := net.LookupHost(endpoint)
|
|
if err != nil {
|
|
logger.FatalIf(err, "Unable to initialize MinIO server with [%s] invalid entry found in MINIO_PUBLIC_IPS", endpoint)
|
|
}
|
|
for _, addr := range addrs {
|
|
domainIPs.Add(addr)
|
|
}
|
|
continue
|
|
}
|
|
domainIPs.Add(endpoint)
|
|
}
|
|
updateDomainIPs(domainIPs)
|
|
} else {
|
|
// Add found interfaces IP address to global domain IPS,
|
|
// loopback addresses will be naturally dropped.
|
|
updateDomainIPs(localIP4)
|
|
}
|
|
|
|
if len(globalDomainNames) != 0 && !globalDomainIPs.IsEmpty() && globalEtcdClient != nil {
|
|
var err error
|
|
globalDNSConfig, err = dns.NewCoreDNS(globalDomainNames, globalDomainIPs, globalMinioPort, globalEtcdClient)
|
|
logger.FatalIf(err, "Unable to initialize DNS config for %s.", globalDomainNames)
|
|
}
|
|
|
|
if drives := os.Getenv("MINIO_CACHE_DRIVES"); drives != "" {
|
|
driveList, err := parseCacheDrives(strings.Split(drives, cacheEnvDelimiter))
|
|
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))
|
|
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)
|
|
if err != nil {
|
|
logger.Fatal(uiErrInvalidCacheExpiryValue(err), "Unable to parse MINIO_CACHE_EXPIRY value (`%s`)", expiryStr)
|
|
}
|
|
globalCacheExpiry = expiry
|
|
}
|
|
|
|
if maxUseStr := os.Getenv("MINIO_CACHE_MAXUSE"); maxUseStr != "" {
|
|
maxUse, err := strconv.Atoi(maxUseStr)
|
|
if err != nil {
|
|
logger.Fatal(uiErrInvalidCacheMaxUse(err), "Unable to parse MINIO_CACHE_MAXUSE value (`%s`)", maxUseStr)
|
|
}
|
|
// maxUse should be a valid percentage.
|
|
if maxUse > 0 && maxUse <= 100 {
|
|
globalCacheMaxUse = maxUse
|
|
}
|
|
}
|
|
// In place update is true by default if the MINIO_UPDATE is not set
|
|
// or is not set to 'off', if MINIO_UPDATE is set to 'off' then
|
|
// in-place update is off.
|
|
globalInplaceUpdateDisabled = strings.EqualFold(os.Getenv("MINIO_UPDATE"), "off")
|
|
|
|
// Validate and store the storage class env variables only for XL/Dist XL setups
|
|
if globalIsXL {
|
|
var err error
|
|
|
|
// Check for environment variables and parse into storageClass struct
|
|
if ssc := os.Getenv(standardStorageClassEnv); ssc != "" {
|
|
globalStandardStorageClass, err = parseStorageClass(ssc)
|
|
logger.FatalIf(err, "Invalid value set in environment variable %s", standardStorageClassEnv)
|
|
}
|
|
|
|
if rrsc := os.Getenv(reducedRedundancyStorageClassEnv); rrsc != "" {
|
|
globalRRStorageClass, err = parseStorageClass(rrsc)
|
|
logger.FatalIf(err, "Invalid value set in environment variable %s", reducedRedundancyStorageClassEnv)
|
|
}
|
|
|
|
// Validation is done after parsing both the storage classes. This is needed because we need one
|
|
// storage class value to deduce the correct value of the other storage class.
|
|
if globalRRStorageClass.Scheme != "" {
|
|
err = validateParity(globalStandardStorageClass.Parity, globalRRStorageClass.Parity)
|
|
logger.FatalIf(err, "Invalid value set in environment variable %s", reducedRedundancyStorageClassEnv)
|
|
globalIsStorageClass = true
|
|
}
|
|
|
|
if globalStandardStorageClass.Scheme != "" {
|
|
err = validateParity(globalStandardStorageClass.Parity, globalRRStorageClass.Parity)
|
|
logger.FatalIf(err, "Invalid value set in environment variable %s", standardStorageClassEnv)
|
|
globalIsStorageClass = true
|
|
}
|
|
}
|
|
|
|
// Get WORM environment variable.
|
|
if worm := os.Getenv("MINIO_WORM"); worm != "" {
|
|
wormFlag, err := ParseBoolFlag(worm)
|
|
if err != nil {
|
|
logger.Fatal(uiErrInvalidWormValue(nil).Msg("Unknown value `%s`", worm), "Invalid MINIO_WORM value in environment variable")
|
|
}
|
|
|
|
// worm Envs are set globally, this does not represent
|
|
// if worm is turned off or on.
|
|
globalIsEnvWORM = true
|
|
globalWORMEnabled = bool(wormFlag)
|
|
}
|
|
|
|
if compress := os.Getenv("MINIO_COMPRESS"); compress != "" {
|
|
globalIsCompressionEnabled = strings.EqualFold(compress, "true")
|
|
}
|
|
|
|
compressExtensions := os.Getenv("MINIO_COMPRESS_EXTENSIONS")
|
|
compressMimeTypes := os.Getenv("MINIO_COMPRESS_MIMETYPES")
|
|
if compressExtensions != "" || compressMimeTypes != "" {
|
|
globalIsEnvCompression = true
|
|
if compressExtensions != "" {
|
|
extensions, err := parseCompressIncludes(strings.Split(compressExtensions, compressEnvDelimiter))
|
|
if err != nil {
|
|
logger.Fatal(err, "Invalid MINIO_COMPRESS_EXTENSIONS value (`%s`)", extensions)
|
|
}
|
|
globalCompressExtensions = extensions
|
|
}
|
|
if compressMimeTypes != "" {
|
|
contenttypes, err := parseCompressIncludes(strings.Split(compressMimeTypes, compressEnvDelimiter))
|
|
if err != nil {
|
|
logger.Fatal(err, "Invalid MINIO_COMPRESS_MIMETYPES value (`%s`)", contenttypes)
|
|
}
|
|
globalCompressMimeTypes = contenttypes
|
|
}
|
|
}
|
|
}
|