Add support for reading and saving config on Gateway. (#4463)

This is also a first step towards supporting bucket
notification for gateway.
This commit is contained in:
Harshavardhana 2017-06-09 19:50:51 -07:00
parent 4fb5fc72d7
commit f99f218999
13 changed files with 325 additions and 420 deletions

117
cmd/common-main.go Normal file
View File

@ -0,0 +1,117 @@
/*
* Minio Cloud Storage, (C) 2017 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 (
"errors"
"os"
"path/filepath"
"strings"
"time"
"github.com/minio/cli"
)
// Check for updates and print a notification message
func checkUpdate(mode string) {
// Its OK to ignore any errors during getUpdateInfo() here.
if older, downloadURL, err := getUpdateInfo(1*time.Second, mode); err == nil {
if updateMsg := computeUpdateMessage(downloadURL, older); updateMsg != "" {
log.Println(updateMsg)
}
}
}
func enableLoggers() {
fileLogTarget := serverConfig.Logger.GetFile()
if fileLogTarget.Enable {
err := InitFileLogger(&fileLogTarget)
fatalIf(err, "Unable to initialize file logger")
log.AddTarget(fileLogTarget)
}
consoleLogTarget := serverConfig.Logger.GetConsole()
if consoleLogTarget.Enable {
InitConsoleLogger(&consoleLogTarget)
}
log.SetConsoleTarget(consoleLogTarget)
}
func initConfig() {
// Config file does not exist, we create it fresh and return upon success.
if isFile(getConfigFile()) {
fatalIf(migrateConfig(), "Config migration failed.")
fatalIf(loadConfig(), "Unable to load config version: '%s'.", v19)
} else {
fatalIf(newConfig(), "Unable to initialize minio config for the first time.")
log.Println("Created minio configuration file successfully at " + getConfigDir())
}
}
func handleCommonCmdArgs(ctx *cli.Context) {
// Set configuration directory.
{
// Get configuration directory from command line argument.
configDir := ctx.String("config-dir")
if !ctx.IsSet("config-dir") && ctx.GlobalIsSet("config-dir") {
configDir = ctx.GlobalString("config-dir")
}
if configDir == "" {
fatalIf(errors.New("empty directory"), "Configuration directory cannot be empty.")
}
// Disallow relative paths, figure out absolute paths.
configDirAbs, err := filepath.Abs(configDir)
fatalIf(err, "Unable to fetch absolute path for config directory %s", configDir)
setConfigDir(configDirAbs)
}
}
func handleCommonEnvVars() {
// Start profiler if env is set.
if profiler := os.Getenv("_MINIO_PROFILER"); profiler != "" {
globalProfiler = startProfiler(profiler)
}
// Check if object cache is disabled.
globalXLObjCacheDisabled = strings.EqualFold(os.Getenv("_MINIO_CACHE"), "off")
accessKey := os.Getenv("MINIO_ACCESS_KEY")
secretKey := os.Getenv("MINIO_SECRET_KEY")
if accessKey != "" && secretKey != "" {
cred, err := createCredential(accessKey, secretKey)
fatalIf(err, "Invalid access/secret Key set in environment.")
// credential Envs are set globally.
globalIsEnvCreds = true
globalActiveCred = cred
}
if browser := os.Getenv("MINIO_BROWSER"); browser != "" {
browserFlag, err := ParseBrowserFlag(browser)
if err != nil {
fatalIf(errors.New("invalid value"), "Unknown value %s in MINIO_BROWSER environment variable.", browser)
}
// browser Envs are set globally, this does not represent
// if browser is turned off or on.
globalIsEnvBrowser = true
globalIsBrowserEnabled = bool(browserFlag)
}
}

View File

@ -26,14 +26,12 @@ import (
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
"os"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/Azure/azure-sdk-for-go/storage" "github.com/Azure/azure-sdk-for-go/storage"
"github.com/minio/cli"
"github.com/minio/minio-go/pkg/policy" "github.com/minio/minio-go/pkg/policy"
"github.com/minio/sha256-simd" "github.com/minio/sha256-simd"
) )
@ -157,32 +155,30 @@ func azureToObjectError(err error, params ...string) error {
} }
// Inits azure blob storage client and returns AzureObjects. // Inits azure blob storage client and returns AzureObjects.
func newAzureLayer(args cli.Args) (GatewayLayer, error) { func newAzureLayer(host string) (GatewayLayer, error) {
var err error var err error
var endpoint = storage.DefaultBaseURL
// Default endpoint parameters var secure = true
endPoint := storage.DefaultBaseURL
secure := true
// If user provided some parameters // If user provided some parameters
if args.Present() { if host != "" {
endPoint, secure, err = parseGatewayEndpoint(args.First()) endpoint, secure, err = parseGatewayEndpoint(host)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
account := os.Getenv("MINIO_ACCESS_KEY") creds := serverConfig.GetCredential()
key := os.Getenv("MINIO_SECRET_KEY") if !creds.IsValid() && !globalIsEnvCreds {
if account == "" || key == "" { return nil, errors.New("Azure backend account and secret keys should be set through ENVs")
return nil, errors.New("No Azure account and key set")
} }
c, err := storage.NewClient(account, key, endPoint, globalAzureAPIVersion, secure) c, err := storage.NewClient(creds.AccessKey, creds.SecretKey, endpoint, globalAzureAPIVersion, secure)
if err != nil { if err != nil {
return &azureObjects{}, err return &azureObjects{}, err
} }
return &azureObjects{ return &azureObjects{
client: c.GetBlobService(), client: c.GetBlobService(),
metaInfo: azureMultipartMetaInfo{ metaInfo: azureMultipartMetaInfo{
@ -195,7 +191,6 @@ func newAzureLayer(args cli.Args) (GatewayLayer, error) {
// Shutdown - save any gateway metadata to disk // Shutdown - save any gateway metadata to disk
// if necessary and reload upon next restart. // if necessary and reload upon next restart.
func (a *azureObjects) Shutdown() error { func (a *azureObjects) Shutdown() error {
// TODO
return nil return nil
} }

View File

@ -19,9 +19,9 @@ package cmd
import "errors" import "errors"
var ( var (
// Project is format is not valid // ProjectID format is not valid.
errGCSInvalidProjectID = errors.New("invalid project id") errGCSInvalidProjectID = errors.New("GCS project id is either empty or invalid")
// Multipart identifier is not in the correct form // Multipart identifier is not in the correct form.
errGCSNotValidMultipartIdentifier = errors.New("Not a valid multipart identifier") errGCSNotValidMultipartIdentifier = errors.New("Not a valid multipart identifier")
) )

View File

@ -33,7 +33,6 @@ import (
"google.golang.org/api/googleapi" "google.golang.org/api/googleapi"
"google.golang.org/api/iterator" "google.golang.org/api/iterator"
"github.com/minio/cli"
minio "github.com/minio/minio-go" minio "github.com/minio/minio-go"
"github.com/minio/minio-go/pkg/policy" "github.com/minio/minio-go/pkg/policy"
) )
@ -175,29 +174,20 @@ type gcsGateway struct {
ctx context.Context ctx context.Context
} }
const googleStorageEndpoint = "storage.googleapis.com"
// newGCSGateway returns gcs gatewaylayer // newGCSGateway returns gcs gatewaylayer
func newGCSGateway(args cli.Args) (GatewayLayer, error) { func newGCSGateway(projectID string) (GatewayLayer, error) {
if !args.Present() {
return nil, fmt.Errorf("ProjectID expected")
}
endpoint := "storage.googleapis.com"
secure := true
projectID := args.First()
if !isValidGCSProjectID(projectID) {
fatalIf(errGCSInvalidProjectID, "Unable to initialize GCS gateway")
}
ctx := context.Background() ctx := context.Background()
// Creates a client. // Initialize a GCS client.
client, err := storage.NewClient(ctx) client, err := storage.NewClient(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
anonClient, err := minio.NewCore(endpoint, "", "", secure) // Initialize a anonymous client with minio core APIs.
anonClient, err := minio.NewCore(googleStorageEndpoint, "", "", true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -213,7 +203,6 @@ func newGCSGateway(args cli.Args) (GatewayLayer, error) {
// Shutdown - save any gateway metadata to disk // Shutdown - save any gateway metadata to disk
// if necessary and reload upon next restart. // if necessary and reload upon next restart.
func (l *gcsGateway) Shutdown() error { func (l *gcsGateway) Shutdown() error {
// TODO
return nil return nil
} }

View File

@ -19,12 +19,12 @@ package cmd
import ( import (
"io" "io"
"io/ioutil" "io/ioutil"
"net"
"net/http" "net/http"
"strconv" "strconv"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"encoding/xml"
router "github.com/gorilla/mux" router "github.com/gorilla/mux"
"github.com/minio/minio-go/pkg/policy" "github.com/minio/minio-go/pkg/policy"
@ -140,7 +140,7 @@ func (api gatewayAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Re
} }
// Reads the object at startOffset and writes to mw. // Reads the object at startOffset and writes to mw.
if err := getObject(bucket, object, startOffset, length, writer); err != nil { if err = getObject(bucket, object, startOffset, length, writer); err != nil {
errorIf(err, "Unable to write to client.") errorIf(err, "Unable to write to client.")
if !dataWritten { if !dataWritten {
// Error response only if no data has been written to client yet. i.e if // Error response only if no data has been written to client yet. i.e if
@ -157,6 +157,23 @@ func (api gatewayAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Re
// call wrter.Write(nil) to set appropriate headers. // call wrter.Write(nil) to set appropriate headers.
writer.Write(nil) writer.Write(nil)
} }
// Get host and port from Request.RemoteAddr.
host, port, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
host, port = "", ""
}
// Notify object accessed via a GET request.
eventNotify(eventData{
Type: ObjectAccessedGet,
Bucket: bucket,
ObjInfo: objInfo,
ReqParams: extractReqParams(r),
UserAgent: r.UserAgent(),
Host: host,
Port: port,
})
} }
// PutObjectHandler - PUT Object // PutObjectHandler - PUT Object
@ -293,6 +310,23 @@ func (api gatewayAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Re
w.Header().Set("ETag", "\""+objInfo.ETag+"\"") w.Header().Set("ETag", "\""+objInfo.ETag+"\"")
writeSuccessResponseHeadersOnly(w) writeSuccessResponseHeadersOnly(w)
// Get host and port from Request.RemoteAddr.
host, port, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
host, port = "", ""
}
// Notify object created event.
eventNotify(eventData{
Type: ObjectCreatedPut,
Bucket: bucket,
ObjInfo: objInfo,
ReqParams: extractReqParams(r),
UserAgent: r.UserAgent(),
Host: host,
Port: port,
})
} }
// HeadObjectHandler - HEAD Object // HeadObjectHandler - HEAD Object
@ -361,97 +395,23 @@ func (api gatewayAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.R
// Successful response. // Successful response.
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
}
// DeleteMultipleObjectsHandler - deletes multiple objects. // Get host and port from Request.RemoteAddr.
func (api gatewayAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *http.Request) { host, port, err := net.SplitHostPort(r.RemoteAddr)
vars := router.Vars(r) if err != nil {
bucket := vars["bucket"] host, port = "", ""
objectAPI := api.ObjectAPI()
if objectAPI == nil {
writeErrorResponse(w, ErrServerNotInitialized, r.URL)
return
} }
if s3Error := checkRequestAuthType(r, bucket, "s3:DeleteObject", serverConfig.GetRegion()); s3Error != ErrNone { // Notify object accessed via a HEAD request.
writeErrorResponse(w, s3Error, r.URL) eventNotify(eventData{
return Type: ObjectAccessedHead,
} Bucket: bucket,
ObjInfo: objInfo,
// Content-Length is required and should be non-zero ReqParams: extractReqParams(r),
// http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html UserAgent: r.UserAgent(),
if r.ContentLength <= 0 { Host: host,
writeErrorResponse(w, ErrMissingContentLength, r.URL) Port: port,
return
}
// Content-Md5 is requied should be set
// http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html
if _, ok := r.Header["Content-Md5"]; !ok {
writeErrorResponse(w, ErrMissingContentMD5, r.URL)
return
}
// Allocate incoming content length bytes.
deleteXMLBytes := make([]byte, r.ContentLength)
// Read incoming body XML bytes.
if _, err := io.ReadFull(r.Body, deleteXMLBytes); err != nil {
errorIf(err, "Unable to read HTTP body.")
writeErrorResponse(w, ErrInternalError, r.URL)
return
}
// Unmarshal list of keys to be deleted.
deleteObjects := &DeleteObjectsRequest{}
if err := xml.Unmarshal(deleteXMLBytes, deleteObjects); err != nil {
errorIf(err, "Unable to unmarshal delete objects request XML.")
writeErrorResponse(w, ErrMalformedXML, r.URL)
return
}
var dErrs = make([]error, len(deleteObjects.Objects))
// Delete all requested objects in parallel.
for index, object := range deleteObjects.Objects {
dErr := objectAPI.DeleteObject(bucket, object.ObjectName)
if dErr != nil {
dErrs[index] = dErr
}
}
// Collect deleted objects and errors if any.
var deletedObjects []ObjectIdentifier
var deleteErrors []DeleteError
for index, err := range dErrs {
object := deleteObjects.Objects[index]
// Success deleted objects are collected separately.
if err == nil {
deletedObjects = append(deletedObjects, object)
continue
}
if _, ok := errorCause(err).(ObjectNotFound); ok {
// If the object is not found it should be
// accounted as deleted as per S3 spec.
deletedObjects = append(deletedObjects, object)
continue
}
errorIf(err, "Unable to delete object. %s", object.ObjectName)
// Error during delete should be collected separately.
deleteErrors = append(deleteErrors, DeleteError{
Code: errorCodeResponse[toAPIErrorCode(err)].Code,
Message: errorCodeResponse[toAPIErrorCode(err)].Description,
Key: object.ObjectName,
}) })
}
// Generate response
response := generateMultiDeleteResponse(deleteObjects.Quiet, deletedObjects, deleteErrors)
encodedSuccessResponse := encodeResponse(response)
// Write success response.
writeSuccessResponseXML(w, encodedSuccessResponse)
} }
// PutBucketPolicyHandler - PUT Bucket policy // PutBucketPolicyHandler - PUT Bucket policy

View File

@ -20,7 +20,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/url" "net/url"
"os"
"runtime" "runtime"
"strings" "strings"
@ -60,20 +59,6 @@ EXAMPLES:
$ {{.HelpName}} https://azure.example.com $ {{.HelpName}} https://azure.example.com
` `
var azureBackendCmd = cli.Command{
Name: "azure",
Usage: "Microsoft Azure Blob Storage.",
Action: azureGatewayMain,
CustomHelpTemplate: azureGatewayTemplate,
Flags: append(serverFlags,
cli.BoolFlag{
Name: "quiet",
Usage: "Disable startup banner.",
},
),
HideHelpCommand: true,
}
const s3GatewayTemplate = `NAME: const s3GatewayTemplate = `NAME:
{{.HelpName}} - {{.Usage}} {{.HelpName}} - {{.Usage}}
@ -134,40 +119,41 @@ EXAMPLES:
` `
var s3BackendCmd = cli.Command{ var (
azureBackendCmd = cli.Command{
Name: "azure",
Usage: "Microsoft Azure Blob Storage.",
Action: azureGatewayMain,
CustomHelpTemplate: azureGatewayTemplate,
Flags: append(serverFlags, globalFlags...),
HideHelpCommand: true,
}
s3BackendCmd = cli.Command{
Name: "s3", Name: "s3",
Usage: "Amazon Simple Storage Service (S3).", Usage: "Amazon Simple Storage Service (S3).",
Action: s3GatewayMain, Action: s3GatewayMain,
CustomHelpTemplate: s3GatewayTemplate, CustomHelpTemplate: s3GatewayTemplate,
Flags: append(serverFlags, Flags: append(serverFlags, globalFlags...),
cli.BoolFlag{
Name: "quiet",
Usage: "Disable startup banner.",
},
),
HideHelpCommand: true, HideHelpCommand: true,
} }
gcsBackendCmd = cli.Command{
var gcsBackendCmd = cli.Command{
Name: "gcs", Name: "gcs",
Usage: "Google Cloud Storage.", Usage: "Google Cloud Storage.",
Action: gcsGatewayMain, Action: gcsGatewayMain,
CustomHelpTemplate: gcsGatewayTemplate, CustomHelpTemplate: gcsGatewayTemplate,
Flags: append(serverFlags, Flags: append(serverFlags, globalFlags...),
cli.BoolFlag{
Name: "quiet",
Usage: "Disable startup banner.",
},
),
HideHelpCommand: true, HideHelpCommand: true,
} }
var gatewayCmd = cli.Command{ gatewayCmd = cli.Command{
Name: "gateway", Name: "gateway",
Usage: "Start object storage gateway.", Usage: "Start object storage gateway.",
Flags: append(serverFlags, globalFlags...),
HideHelpCommand: true, HideHelpCommand: true,
Subcommands: []cli.Command{azureBackendCmd, s3BackendCmd, gcsBackendCmd}, Subcommands: []cli.Command{azureBackendCmd, s3BackendCmd, gcsBackendCmd},
} }
)
// Represents the type of the gateway backend. // Represents the type of the gateway backend.
type gatewayBackend string type gatewayBackend string
@ -179,38 +165,6 @@ const (
// Add more backends here. // Add more backends here.
) )
// Returns access and secretkey set from environment variables.
func mustGetGatewayConfigFromEnv() (string, string, string) {
// Fetch access keys from environment variables.
accessKey := os.Getenv("MINIO_ACCESS_KEY")
secretKey := os.Getenv("MINIO_SECRET_KEY")
if accessKey == "" || secretKey == "" {
fatalIf(errors.New("Missing credentials"), "Access and secret keys are mandatory to run Minio gateway server.")
}
region := globalMinioDefaultRegion
if v := os.Getenv("MINIO_REGION"); v != "" {
region = v
}
return accessKey, secretKey, region
}
// Set browser setting from environment variables
func mustSetBrowserSettingFromEnv() {
if browser := os.Getenv("MINIO_BROWSER"); browser != "" {
browserFlag, err := ParseBrowserFlag(browser)
if err != nil {
fatalIf(errors.New("invalid value"), "Unknown value %s in MINIO_BROWSER environment variable.", browser)
}
// browser Envs are set globally, this does not represent
// if browser is turned off or on.
globalIsEnvBrowser = true
globalIsBrowserEnabled = bool(browserFlag)
}
}
// Initialize gateway layer depending on the backend type. // Initialize gateway layer depending on the backend type.
// Supported backend types are // Supported backend types are
// //
@ -218,46 +172,19 @@ func mustSetBrowserSettingFromEnv() {
// - AWS S3. // - AWS S3.
// - Google Cloud Storage. // - Google Cloud Storage.
// - Add your favorite backend here. // - Add your favorite backend here.
func newGatewayLayer(backendType string, args cli.Args) (GatewayLayer, error) { func newGatewayLayer(backendType gatewayBackend, arg string) (GatewayLayer, error) {
switch gatewayBackend(backendType) { switch backendType {
case azureBackend: case azureBackend:
return newAzureLayer(args) return newAzureLayer(arg)
case s3Backend: case s3Backend:
return newS3Gateway(args) return newS3Gateway(arg)
case gcsBackend: case gcsBackend:
return newGCSGateway(args) return newGCSGateway(arg)
} }
return nil, fmt.Errorf("Unrecognized backend type %s", backendType) return nil, fmt.Errorf("Unrecognized backend type %s", backendType)
} }
// Initialize a new gateway config.
//
// DO NOT save this config, this is meant to be
// only used in memory.
func newGatewayConfig(accessKey, secretKey, region string) error {
// Initialize server config.
srvCfg := newServerConfigV19()
// If env is set for a fresh start, save them to config file.
srvCfg.SetCredential(credential{
AccessKey: accessKey,
SecretKey: secretKey,
})
// Set custom region.
srvCfg.SetRegion(region)
// hold the mutex lock before a new config is assigned.
// Save the new config globally.
// unlock the mutex.
serverConfigMu.Lock()
serverConfig = srvCfg
serverConfigMu.Unlock()
return nil
}
// Return endpoint. // Return endpoint.
func parseGatewayEndpoint(arg string) (endPoint string, secure bool, err error) { func parseGatewayEndpoint(arg string) (endPoint string, secure bool, err error) {
schemeSpecified := len(strings.Split(arg, "://")) > 1 schemeSpecified := len(strings.Split(arg, "://")) > 1
@ -317,6 +244,9 @@ func azureGatewayMain(ctx *cli.Context) {
cli.ShowCommandHelpAndExit(ctx, "azure", 1) cli.ShowCommandHelpAndExit(ctx, "azure", 1)
} }
// Validate gateway arguments.
fatalIf(validateGatewayArguments(ctx.String("address"), ctx.Args().First()), "Invalid argument")
gatewayMain(ctx, azureBackend) gatewayMain(ctx, azureBackend)
} }
@ -326,13 +256,21 @@ func s3GatewayMain(ctx *cli.Context) {
cli.ShowCommandHelpAndExit(ctx, "s3", 1) cli.ShowCommandHelpAndExit(ctx, "s3", 1)
} }
// Validate gateway arguments.
fatalIf(validateGatewayArguments(ctx.String("address"), ctx.Args().First()), "Invalid argument")
gatewayMain(ctx, s3Backend) gatewayMain(ctx, s3Backend)
} }
// Handler for 'minio gateway gcs' command line // Handler for 'minio gateway gcs' command line
func gcsGatewayMain(ctx *cli.Context) { func gcsGatewayMain(ctx *cli.Context) {
if ctx.Args().Present() && ctx.Args().First() == "help" { if ctx.Args().Present() && ctx.Args().First() == "help" {
cli.ShowCommandHelpAndExit(ctx, "s3", 1) cli.ShowCommandHelpAndExit(ctx, "gcs", 1)
}
if !isValidGCSProjectID(ctx.Args().First()) {
errorIf(errGCSInvalidProjectID, "Unable to start GCS gateway with %s", ctx.Args().First())
cli.ShowCommandHelpAndExit(ctx, "gcs", 1)
} }
gatewayMain(ctx, gcsBackend) gatewayMain(ctx, gcsBackend)
@ -340,43 +278,45 @@ func gcsGatewayMain(ctx *cli.Context) {
// Handler for 'minio gateway'. // Handler for 'minio gateway'.
func gatewayMain(ctx *cli.Context, backendType gatewayBackend) { func gatewayMain(ctx *cli.Context, backendType gatewayBackend) {
// Fetch access and secret key from env.
accessKey, secretKey, region := mustGetGatewayConfigFromEnv()
// Fetch browser env setting
mustSetBrowserSettingFromEnv()
// Initialize new gateway config.
newGatewayConfig(accessKey, secretKey, region)
// Get quiet flag from command line argument. // Get quiet flag from command line argument.
quietFlag := ctx.Bool("quiet") || ctx.GlobalBool("quiet") quietFlag := ctx.Bool("quiet") || ctx.GlobalBool("quiet")
if quietFlag { if quietFlag {
log.EnableQuiet() log.EnableQuiet()
} }
serverAddr := ctx.String("address") // Handle common command args.
endpointAddr := ctx.Args().First() handleCommonCmdArgs(ctx)
err := validateGatewayArguments(serverAddr, endpointAddr)
fatalIf(err, "Invalid argument")
// Create certs path for SSL configuration. // Handle common env vars.
fatalIf(createConfigDir(), "Unable to create configuration directory") handleCommonEnvVars()
// Create certs path.
fatalIf(createConfigDir(), "Unable to create configuration directories.")
// Initialize gateway config.
initConfig()
// Enable loggers as per configuration file.
enableLoggers()
// Init the error tracing module.
initError()
// Check and load SSL certificates.
var err error
globalPublicCerts, globalRootCAs, globalIsSSL, err = getSSLConfig()
fatalIf(err, "Invalid SSL key file")
initNSLock(false) // Enable local namespace lock. initNSLock(false) // Enable local namespace lock.
newObject, err := newGatewayLayer(backendType, ctx.Args()[1:]) newObject, err := newGatewayLayer(backendType, ctx.Args().First())
fatalIf(err, "Unable to initialize gateway layer") fatalIf(err, "Unable to initialize gateway layer")
router := mux.NewRouter().SkipClean(true) router := mux.NewRouter().SkipClean(true)
// credentials Envs are set globally.
globalIsEnvCreds = true
// Register web router when its enabled. // Register web router when its enabled.
if globalIsBrowserEnabled { if globalIsBrowserEnabled {
aerr := registerWebRouter(router) fatalIf(registerWebRouter(router), "Unable to configure web browser")
fatalIf(aerr, "Unable to configure web browser")
} }
registerGatewayAPIRouter(router, newObject) registerGatewayAPIRouter(router, newObject)
@ -409,10 +349,7 @@ func gatewayMain(ctx *cli.Context, backendType gatewayBackend) {
} }
apiServer := NewServerMux(serverAddr, registerHandlers(router, handlerFns...)) apiServer := NewServerMux(ctx.String("address"), registerHandlers(router, handlerFns...))
_, _, globalIsSSL, err = getSSLConfig()
fatalIf(err, "Invalid SSL key file")
// Start server, automatically configures TLS if certs are available. // Start server, automatically configures TLS if certs are available.
go func() { go func() {
@ -420,9 +357,7 @@ func gatewayMain(ctx *cli.Context, backendType gatewayBackend) {
if globalIsSSL { if globalIsSSL {
cert, key = getPublicCertFile(), getPrivateKeyFile() cert, key = getPublicCertFile(), getPrivateKeyFile()
} }
fatalIf(apiServer.ListenAndServe(cert, key), "Failed to start minio server")
aerr := apiServer.ListenAndServe(cert, key)
fatalIf(aerr, "Failed to start minio server")
}() }()
// Once endpoints are finalized, initialize the new object api. // Once endpoints are finalized, initialize the new object api.
@ -441,9 +376,12 @@ func gatewayMain(ctx *cli.Context, backendType gatewayBackend) {
case s3Backend: case s3Backend:
mode = globalMinioModeGatewayS3 mode = globalMinioModeGatewayS3
} }
// Check update mode.
checkUpdate(mode) checkUpdate(mode)
apiEndpoints := getAPIEndpoints(apiServer.Addr)
printGatewayStartupMessage(apiEndpoints, accessKey, secretKey, backendType) // Print gateway startup message.
printGatewayStartupMessage(getAPIEndpoints(apiServer.Addr), backendType)
} }
<-globalServiceDoneCh <-globalServiceDoneCh

View File

@ -17,7 +17,6 @@
package cmd package cmd
import ( import (
"os"
"strings" "strings"
"testing" "testing"
) )
@ -53,28 +52,6 @@ func TestParseGatewayEndpoint(t *testing.T) {
} }
} }
func TestSetBrowserFromEnv(t *testing.T) {
browser := os.Getenv("MINIO_BROWSER")
os.Setenv("MINIO_BROWSER", "on")
mustSetBrowserSettingFromEnv()
if globalIsBrowserEnabled != true {
t.Errorf("Expected the response status to be `%t`, but instead found `%t`", globalIsBrowserEnabled, false)
}
os.Setenv("MINIO_BROWSER", "off")
mustSetBrowserSettingFromEnv()
if globalIsBrowserEnabled != false {
t.Errorf("Expected the response status to be `%t`, but instead found `%t`", globalIsBrowserEnabled, true)
}
os.Setenv("MINIO_BROWSER", "")
mustSetBrowserSettingFromEnv()
if globalIsBrowserEnabled != false {
t.Errorf("Expected the response status to be `%t`, but instead found `%t`", globalIsBrowserEnabled, true)
}
os.Setenv("MINIO_BROWSER", browser)
}
// Test validateGatewayArguments // Test validateGatewayArguments
func TestValidateGatewayArguments(t *testing.T) { func TestValidateGatewayArguments(t *testing.T) {
nonLoopBackIPs := localIP4.FuncMatch(func(ip string, matchString string) bool { nonLoopBackIPs := localIP4.FuncMatch(func(ip string, matchString string) bool {

View File

@ -20,12 +20,10 @@ import (
"errors" "errors"
"io" "io"
"net/http" "net/http"
"os"
"path" "path"
"encoding/hex" "encoding/hex"
"github.com/minio/cli"
minio "github.com/minio/minio-go" minio "github.com/minio/minio-go"
"github.com/minio/minio-go/pkg/policy" "github.com/minio/minio-go/pkg/policy"
) )
@ -101,31 +99,33 @@ type s3Objects struct {
} }
// newS3Gateway returns s3 gatewaylayer // newS3Gateway returns s3 gatewaylayer
func newS3Gateway(args cli.Args) (GatewayLayer, error) { func newS3Gateway(host string) (GatewayLayer, error) {
var err error var err error
var endpoint string
var secure = true
// Default endpoint parameters // Validate host parameters.
endpoint := "s3.amazonaws.com" if host != "" {
secure := true // Override default params if the host is provided
endpoint, secure, err = parseGatewayEndpoint(host)
// Check if user provided some parameters
if args.Present() {
// Override default params if the endpoint is provided
endpoint, secure, err = parseGatewayEndpoint(args.First())
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
accessKey := os.Getenv("MINIO_ACCESS_KEY") // Default endpoint parameters
secretKey := os.Getenv("MINIO_SECRET_KEY") if endpoint == "" {
if accessKey == "" || secretKey == "" { endpoint = "s3.amazonaws.com"
return nil, errors.New("No S3 access and secret key") }
creds := serverConfig.GetCredential()
if !creds.IsValid() && !globalIsEnvCreds {
return nil, errors.New("S3 backend account and secret keys should be set through ENVs")
} }
// Initialize minio client object. // Initialize minio client object.
client, err := minio.NewCore(endpoint, accessKey, secretKey, secure) client, err := minio.NewCore(endpoint, creds.AccessKey, creds.SecretKey, secure)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -18,28 +18,19 @@ package cmd
import ( import (
"fmt" "fmt"
"runtime"
"strings" "strings"
) )
// Prints the formatted startup message. // Prints the formatted startup message.
func printGatewayStartupMessage(apiEndPoints []string, accessKey, secretKey string, backendType gatewayBackend) { func printGatewayStartupMessage(apiEndPoints []string, backendType gatewayBackend) {
strippedAPIEndpoints := stripStandardPorts(apiEndPoints)
// Prints credential. // Prints credential.
printGatewayCommonMsg(apiEndPoints, accessKey, secretKey) printGatewayCommonMsg(strippedAPIEndpoints)
// Prints `mc` cli configuration message chooses // Prints `mc` cli configuration message chooses
// first endpoint as default. // first endpoint as default.
endPoint := apiEndPoints[0] printCLIAccessMsg(strippedAPIEndpoints[0], fmt.Sprintf("my%s", backendType))
// Configure 'mc', following block prints platform specific information for minio client.
log.Println(colorBlue("\nCommand-line Access: ") + mcQuickStartGuide)
if runtime.GOOS == globalWindowsOSName {
mcMessage := fmt.Sprintf("$ mc.exe config host add my%s %s %s %s", backendType, endPoint, accessKey, secretKey)
log.Println(fmt.Sprintf(getFormatStr(len(mcMessage), 3), mcMessage))
} else {
mcMessage := fmt.Sprintf("$ mc config host add my%s %s %s %s", backendType, endPoint, accessKey, secretKey)
log.Println(fmt.Sprintf(getFormatStr(len(mcMessage), 3), mcMessage))
}
// Prints documentation message. // Prints documentation message.
printObjectAPIMsg() printObjectAPIMsg()
@ -52,10 +43,16 @@ func printGatewayStartupMessage(apiEndPoints []string, accessKey, secretKey stri
} }
// Prints common server startup message. Prints credential, region and browser access. // Prints common server startup message. Prints credential, region and browser access.
func printGatewayCommonMsg(apiEndpoints []string, accessKey, secretKey string) { func printGatewayCommonMsg(apiEndpoints []string) {
// Get saved credentials.
cred := serverConfig.GetCredential()
apiEndpointStr := strings.Join(apiEndpoints, " ") apiEndpointStr := strings.Join(apiEndpoints, " ")
// Colorize the message and print. // Colorize the message and print.
log.Println(colorBlue("\nEndpoint: ") + colorBold(fmt.Sprintf(getFormatStr(len(apiEndpointStr), 1), apiEndpointStr))) log.Println(colorBlue("\nEndpoint: ") + colorBold(fmt.Sprintf(getFormatStr(len(apiEndpointStr), 1), apiEndpointStr)))
log.Println(colorBlue("AccessKey: ") + colorBold(fmt.Sprintf("%s ", accessKey))) log.Println(colorBlue("AccessKey: ") + colorBold(fmt.Sprintf("%s ", cred.AccessKey)))
log.Println(colorBlue("SecretKey: ") + colorBold(fmt.Sprintf("%s ", secretKey))) log.Println(colorBlue("SecretKey: ") + colorBold(fmt.Sprintf("%s ", cred.SecretKey)))
log.Println(colorBlue("\nBrowser Access:"))
log.Println(fmt.Sprintf(getFormatStr(len(apiEndpointStr), 3), apiEndpointStr))
} }

View File

@ -20,12 +20,24 @@ import "testing"
// Test printing Gateway common message. // Test printing Gateway common message.
func TestPrintGatewayCommonMessage(t *testing.T) { func TestPrintGatewayCommonMessage(t *testing.T) {
apiEndpoints := []string{"127.0.0.1:9000"} root, err := newTestConfig(globalMinioDefaultRegion)
printGatewayCommonMsg(apiEndpoints, "abcd1", "abcd123") if err != nil {
t.Fatal(err)
}
defer removeAll(root)
apiEndpoints := []string{"http://127.0.0.1:9000"}
printGatewayCommonMsg(apiEndpoints)
} }
// Test print gateway startup message. // Test print gateway startup message.
func TestPrintGatewayStartupMessage(t *testing.T) { func TestPrintGatewayStartupMessage(t *testing.T) {
apiEndpoints := []string{"127.0.0.1:9000"} root, err := newTestConfig(globalMinioDefaultRegion)
printGatewayStartupMessage(apiEndpoints, "abcd1", "abcd123", "azure") if err != nil {
t.Fatal(err)
}
defer removeAll(root)
apiEndpoints := []string{"http://127.0.0.1:9000"}
printGatewayStartupMessage(apiEndpoints, "azure")
} }

View File

@ -17,12 +17,8 @@
package cmd package cmd
import ( import (
"errors"
"os" "os"
"path/filepath"
"runtime" "runtime"
"strings"
"time"
"github.com/minio/cli" "github.com/minio/cli"
"github.com/minio/dsync" "github.com/minio/dsync"
@ -78,61 +74,9 @@ EXAMPLES:
`, `,
} }
// Check for updates and print a notification message
func checkUpdate(mode string) {
// Its OK to ignore any errors during getUpdateInfo() here.
if older, downloadURL, err := getUpdateInfo(1*time.Second, mode); err == nil {
if updateMsg := computeUpdateMessage(downloadURL, older); updateMsg != "" {
log.Println(updateMsg)
}
}
}
func enableLoggers() {
fileLogTarget := serverConfig.Logger.GetFile()
if fileLogTarget.Enable {
err := InitFileLogger(&fileLogTarget)
fatalIf(err, "Unable to initialize file logger")
log.AddTarget(fileLogTarget)
}
consoleLogTarget := serverConfig.Logger.GetConsole()
if consoleLogTarget.Enable {
InitConsoleLogger(&consoleLogTarget)
}
log.SetConsoleTarget(consoleLogTarget)
}
func initConfig() {
// Config file does not exist, we create it fresh and return upon success.
if isFile(getConfigFile()) {
fatalIf(migrateConfig(), "Config migration failed.")
fatalIf(loadConfig(), "Unable to load config version: '%s'.", v19)
} else {
fatalIf(newConfig(), "Unable to initialize minio config for the first time.")
log.Println("Created minio configuration file successfully at " + getConfigDir())
}
}
func serverHandleCmdArgs(ctx *cli.Context) { func serverHandleCmdArgs(ctx *cli.Context) {
// Set configuration directory. // Handle common command args.
{ handleCommonCmdArgs(ctx)
// Get configuration directory from command line argument.
configDir := ctx.String("config-dir")
if !ctx.IsSet("config-dir") && ctx.GlobalIsSet("config-dir") {
configDir = ctx.GlobalString("config-dir")
}
if configDir == "" {
fatalIf(errors.New("empty directory"), "Configuration directory cannot be empty.")
}
// Disallow relative paths, figure out absolute paths.
configDirAbs, err := filepath.Abs(configDir)
fatalIf(err, "Unable to fetch absolute path for config directory %s", configDir)
setConfigDir(configDirAbs)
}
// Server address. // Server address.
serverAddr := ctx.String("address") serverAddr := ctx.String("address")
@ -159,36 +103,8 @@ func serverHandleCmdArgs(ctx *cli.Context) {
} }
func serverHandleEnvVars() { func serverHandleEnvVars() {
// Start profiler if env is set. // Handle common environment variables.
if profiler := os.Getenv("_MINIO_PROFILER"); profiler != "" { handleCommonEnvVars()
globalProfiler = startProfiler(profiler)
}
// Check if object cache is disabled.
globalXLObjCacheDisabled = strings.EqualFold(os.Getenv("_MINIO_CACHE"), "off")
accessKey := os.Getenv("MINIO_ACCESS_KEY")
secretKey := os.Getenv("MINIO_SECRET_KEY")
if accessKey != "" && secretKey != "" {
cred, err := createCredential(accessKey, secretKey)
fatalIf(err, "Invalid access/secret Key set in environment.")
// credential Envs are set globally.
globalIsEnvCreds = true
globalActiveCred = cred
}
if browser := os.Getenv("MINIO_BROWSER"); browser != "" {
browserFlag, err := ParseBrowserFlag(browser)
if err != nil {
fatalIf(errors.New("invalid value"), "Unknown value %s in MINIO_BROWSER environment variable.", browser)
}
// browser Envs are set globally, this does not represent
// if browser is turned off or on.
globalIsEnvBrowser = true
globalIsBrowserEnabled = bool(browserFlag)
}
if serverRegion := os.Getenv("MINIO_REGION"); serverRegion != "" { if serverRegion := os.Getenv("MINIO_REGION"); serverRegion != "" {
// region Envs are set globally. // region Envs are set globally.
@ -210,12 +126,16 @@ func serverMain(ctx *cli.Context) {
log.EnableQuiet() log.EnableQuiet()
} }
// Handle all server command args.
serverHandleCmdArgs(ctx) serverHandleCmdArgs(ctx)
// Handle all server environment vars.
serverHandleEnvVars() serverHandleEnvVars()
// Create certs path. // Create certs path.
fatalIf(createConfigDir(), "Unable to create configuration directories.") fatalIf(createConfigDir(), "Unable to create configuration directories.")
// Initialize server config.
initConfig() initConfig()
// Enable loggers as per configuration file. // Enable loggers as per configuration file.

View File

@ -52,7 +52,7 @@ func printStartupMessage(apiEndPoints []string) {
// Prints `mc` cli configuration message chooses // Prints `mc` cli configuration message chooses
// first endpoint as default. // first endpoint as default.
printCLIAccessMsg(strippedAPIEndpoints[0]) printCLIAccessMsg(strippedAPIEndpoints[0], "myminio")
// Prints documentation message. // Prints documentation message.
printObjectAPIMsg() printObjectAPIMsg()
@ -141,17 +141,17 @@ func printEventNotifiers() {
// Prints startup message for command line access. Prints link to our documentation // Prints startup message for command line access. Prints link to our documentation
// and custom platform specific message. // and custom platform specific message.
func printCLIAccessMsg(endPoint string) { func printCLIAccessMsg(endPoint string, alias string) {
// Get saved credentials. // Get saved credentials.
cred := serverConfig.GetCredential() cred := serverConfig.GetCredential()
// Configure 'mc', following block prints platform specific information for minio client. // Configure 'mc', following block prints platform specific information for minio client.
log.Println(colorBlue("\nCommand-line Access: ") + mcQuickStartGuide) log.Println(colorBlue("\nCommand-line Access: ") + mcQuickStartGuide)
if runtime.GOOS == globalWindowsOSName { if runtime.GOOS == globalWindowsOSName {
mcMessage := fmt.Sprintf("$ mc.exe config host add myminio %s %s %s", endPoint, cred.AccessKey, cred.SecretKey) mcMessage := fmt.Sprintf("$ mc.exe config host add %s %s %s %s", alias, endPoint, cred.AccessKey, cred.SecretKey)
log.Println(fmt.Sprintf(getFormatStr(len(mcMessage), 3), mcMessage)) log.Println(fmt.Sprintf(getFormatStr(len(mcMessage), 3), mcMessage))
} else { } else {
mcMessage := fmt.Sprintf("$ mc config host add myminio %s %s %s", endPoint, cred.AccessKey, cred.SecretKey) mcMessage := fmt.Sprintf("$ mc config host add %s %s %s %s", alias, endPoint, cred.AccessKey, cred.SecretKey)
log.Println(fmt.Sprintf(getFormatStr(len(mcMessage), 3), mcMessage)) log.Println(fmt.Sprintf(getFormatStr(len(mcMessage), 3), mcMessage))
} }
} }

View File

@ -140,7 +140,7 @@ func TestPrintCLIAccessMsg(t *testing.T) {
defer removeAll(root) defer removeAll(root)
apiEndpoints := []string{"http://127.0.0.1:9000"} apiEndpoints := []string{"http://127.0.0.1:9000"}
printCLIAccessMsg(apiEndpoints[0]) printCLIAccessMsg(apiEndpoints[0], "myminio")
} }
// Test print startup message. // Test print startup message.