mirror of https://github.com/minio/minio.git
Support looking up environment remotely (#10215)
adds a feature where we can fetch the MinIO command-line remotely, this is primarily meant to add some stateless nature to the MinIO deployment in k8s environments, MinIO operator would run a webhook service endpoint which can be used to fetch any environment value in a generalized approach.
This commit is contained in:
parent
adcaa6f9de
commit
77509ce391
|
@ -31,11 +31,12 @@ const (
|
|||
EnvDomain = "MINIO_DOMAIN"
|
||||
EnvRegionName = "MINIO_REGION_NAME"
|
||||
EnvPublicIPs = "MINIO_PUBLIC_IPS"
|
||||
EnvEndpoints = "MINIO_ENDPOINTS"
|
||||
EnvFSOSync = "MINIO_FS_OSYNC"
|
||||
EnvArgs = "MINIO_ARGS"
|
||||
|
||||
EnvUpdate = "MINIO_UPDATE"
|
||||
|
||||
EnvEndpoints = "MINIO_ENDPOINTS" // legacy
|
||||
EnvWorm = "MINIO_WORM" // legacy
|
||||
EnvRegion = "MINIO_REGION" // legacy
|
||||
)
|
||||
|
|
|
@ -172,6 +172,18 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
|
|||
// Handle common command args.
|
||||
handleCommonCmdArgs(ctx)
|
||||
|
||||
// Check and load TLS certificates.
|
||||
var err error
|
||||
globalPublicCerts, globalTLSCerts, globalIsSSL, err = getTLSConfig()
|
||||
logger.FatalIf(err, "Invalid TLS certificate file")
|
||||
|
||||
// Check and load Root CAs.
|
||||
globalRootCAs, err = config.GetRootCAs(globalCertsCADir.Get())
|
||||
logger.FatalIf(err, "Failed to read root CAs (%v)", err)
|
||||
|
||||
// Register root CAs for remote ENVs
|
||||
env.RegisterGlobalCAs(globalRootCAs)
|
||||
|
||||
// Initialize all help
|
||||
initHelp()
|
||||
|
||||
|
@ -184,15 +196,6 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
|
|||
// To avoid this error situation we check for port availability.
|
||||
logger.FatalIf(checkPortAvailability(globalMinioHost, globalMinioPort), "Unable to start the gateway")
|
||||
|
||||
// Check and load TLS certificates.
|
||||
var err error
|
||||
globalPublicCerts, globalTLSCerts, globalIsSSL, err = getTLSConfig()
|
||||
logger.FatalIf(err, "Invalid TLS certificate file")
|
||||
|
||||
// Check and load Root CAs.
|
||||
globalRootCAs, err = config.GetRootCAs(globalCertsCADir.Get())
|
||||
logger.FatalIf(err, "Failed to read root CAs (%v)", err)
|
||||
|
||||
globalMinioEndpoint = func() string {
|
||||
host := globalMinioHost
|
||||
if host == "" {
|
||||
|
|
|
@ -93,11 +93,16 @@ EXAMPLES:
|
|||
`,
|
||||
}
|
||||
|
||||
// Checks if endpoints are either available through environment
|
||||
// or command line, returns false if both fails.
|
||||
func endpointsPresent(ctx *cli.Context) bool {
|
||||
endpoints := env.Get(config.EnvEndpoints, strings.Join(ctx.Args(), config.ValueSeparator))
|
||||
return len(endpoints) != 0
|
||||
func serverCmdArgs(ctx *cli.Context) []string {
|
||||
v := env.Get(config.EnvArgs, "")
|
||||
if v == "" {
|
||||
// Fall back to older ENV MINIO_ENDPOINTS
|
||||
v = env.Get(config.EnvEndpoints, "")
|
||||
}
|
||||
if v == "" {
|
||||
return ctx.Args()
|
||||
}
|
||||
return strings.Fields(v)
|
||||
}
|
||||
|
||||
func serverHandleCmdArgs(ctx *cli.Context) {
|
||||
|
@ -106,18 +111,24 @@ func serverHandleCmdArgs(ctx *cli.Context) {
|
|||
|
||||
logger.FatalIf(CheckLocalServerAddr(globalCLIContext.Addr), "Unable to validate passed arguments")
|
||||
|
||||
var setupType SetupType
|
||||
var err error
|
||||
var setupType SetupType
|
||||
|
||||
// Check and load TLS certificates.
|
||||
globalPublicCerts, globalTLSCerts, globalIsSSL, err = getTLSConfig()
|
||||
logger.FatalIf(err, "Unable to load the TLS configuration")
|
||||
|
||||
// Check and load Root CAs.
|
||||
globalRootCAs, err = config.GetRootCAs(globalCertsCADir.Get())
|
||||
logger.FatalIf(err, "Failed to read root CAs (%v)", err)
|
||||
|
||||
// Register root CAs for remote ENVs
|
||||
env.RegisterGlobalCAs(globalRootCAs)
|
||||
|
||||
globalMinioAddr = globalCLIContext.Addr
|
||||
|
||||
globalMinioHost, globalMinioPort = mustSplitHostPort(globalMinioAddr)
|
||||
endpoints := strings.Fields(env.Get(config.EnvEndpoints, ""))
|
||||
if len(endpoints) > 0 {
|
||||
globalEndpoints, setupType, err = createServerEndpoints(globalCLIContext.Addr, endpoints...)
|
||||
} else {
|
||||
globalEndpoints, setupType, err = createServerEndpoints(globalCLIContext.Addr, ctx.Args()...)
|
||||
}
|
||||
globalEndpoints, setupType, err = createServerEndpoints(globalCLIContext.Addr, serverCmdArgs(ctx)...)
|
||||
logger.FatalIf(err, "Invalid command line arguments")
|
||||
|
||||
// On macOS, if a process already listens on LOCALIPADDR:PORT, net.Listen() falls back
|
||||
|
@ -370,9 +381,6 @@ func startBackgroundOps(ctx context.Context, objAPI ObjectLayer) {
|
|||
|
||||
// serverMain handler called for 'minio server' command.
|
||||
func serverMain(ctx *cli.Context) {
|
||||
if ctx.Args().First() == "help" || !endpointsPresent(ctx) {
|
||||
cli.ShowCommandHelpAndExit(ctx, "server", 1)
|
||||
}
|
||||
setDefaultProfilerRates()
|
||||
|
||||
// Initialize globalConsoleSys system
|
||||
|
@ -392,15 +400,7 @@ func serverMain(ctx *cli.Context) {
|
|||
// Initialize all help
|
||||
initHelp()
|
||||
|
||||
// Check and load TLS certificates.
|
||||
var err error
|
||||
globalPublicCerts, globalTLSCerts, globalIsSSL, err = getTLSConfig()
|
||||
logger.FatalIf(err, "Unable to load the TLS configuration")
|
||||
|
||||
// Check and load Root CAs.
|
||||
globalRootCAs, err = config.GetRootCAs(globalCertsCADir.Get())
|
||||
logger.FatalIf(err, "Failed to read root CAs (%v)", err)
|
||||
|
||||
globalProxyEndpoints, err = GetProxyEndpoints(globalEndpoints)
|
||||
logger.FatalIf(err, "Invalid command line arguments")
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
package env
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
@ -46,7 +45,7 @@ func SetEnvOn() {
|
|||
|
||||
// IsSet returns if the given env key is set.
|
||||
func IsSet(key string) bool {
|
||||
_, ok := os.LookupEnv(key)
|
||||
_, ok := LookupEnv(key)
|
||||
return ok
|
||||
}
|
||||
|
||||
|
@ -61,7 +60,7 @@ func Get(key, defaultValue string) string {
|
|||
if ok {
|
||||
return defaultValue
|
||||
}
|
||||
if v, ok := os.LookupEnv(key); ok {
|
||||
if v, ok := LookupEnv(key); ok {
|
||||
return v
|
||||
}
|
||||
return defaultValue
|
||||
|
@ -69,7 +68,7 @@ func Get(key, defaultValue string) string {
|
|||
|
||||
// List all envs with a given prefix.
|
||||
func List(prefix string) (envs []string) {
|
||||
for _, env := range os.Environ() {
|
||||
for _, env := range Environ() {
|
||||
if strings.HasPrefix(env, prefix) {
|
||||
values := strings.SplitN(env, "=", 2)
|
||||
if len(values) == 2 {
|
||||
|
|
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* MinIO Cloud Storage, (C) 2020 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 env
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
webEnvScheme = "env"
|
||||
webEnvSchemeSecure = "env+tls"
|
||||
)
|
||||
|
||||
var (
|
||||
globalRootCAs *x509.CertPool
|
||||
)
|
||||
|
||||
// RegisterGlobalCAs register the global root CAs
|
||||
func RegisterGlobalCAs(CAs *x509.CertPool) {
|
||||
globalRootCAs = CAs
|
||||
}
|
||||
|
||||
func isValidEnvScheme(scheme string) bool {
|
||||
switch scheme {
|
||||
case webEnvScheme:
|
||||
fallthrough
|
||||
case webEnvSchemeSecure:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var (
|
||||
hostKeys = regexp.MustCompile("^(https?://)(.*?):(.*?)@(.*?)$")
|
||||
)
|
||||
|
||||
func fetchEnvHTTP(envKey string, u *url.URL) (string, error) {
|
||||
switch u.Scheme {
|
||||
case webEnvScheme:
|
||||
u.Scheme = "http"
|
||||
case webEnvSchemeSecure:
|
||||
u.Scheme = "https"
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var (
|
||||
username, password string
|
||||
)
|
||||
|
||||
envURL := u.String()
|
||||
if hostKeys.MatchString(envURL) {
|
||||
parts := hostKeys.FindStringSubmatch(envURL)
|
||||
if len(parts) != 5 {
|
||||
return "", errors.New("invalid arguments")
|
||||
}
|
||||
username = parts[2]
|
||||
password = parts[3]
|
||||
envURL = fmt.Sprintf("%s%s", parts[1], parts[4])
|
||||
}
|
||||
|
||||
if username == "" && password == "" && u.User != nil {
|
||||
username = u.User.Username()
|
||||
password, _ = u.User.Password()
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, envURL+"?key="+envKey, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if username != "" && password != "" {
|
||||
req.SetBasicAuth(username, password)
|
||||
}
|
||||
|
||||
clnt := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 3 * time.Second,
|
||||
KeepAlive: 5 * time.Second,
|
||||
}).DialContext,
|
||||
ResponseHeaderTimeout: 3 * time.Second,
|
||||
TLSHandshakeTimeout: 3 * time.Second,
|
||||
ExpectContinueTimeout: 3 * time.Second,
|
||||
TLSClientConfig: &tls.Config{
|
||||
RootCAs: globalRootCAs,
|
||||
},
|
||||
// Go net/http automatically unzip if content-type is
|
||||
// gzip disable this feature, as we are always interested
|
||||
// in raw stream.
|
||||
DisableCompression: true,
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := clnt.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
envValueBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(envValueBytes), nil
|
||||
}
|
||||
|
||||
// Environ returns a copy of strings representing the
|
||||
// environment, in the form "key=value".
|
||||
func Environ() []string {
|
||||
return os.Environ()
|
||||
}
|
||||
|
||||
// LookupEnv retrieves the value of the environment variable
|
||||
// named by the key. If the variable is present in the
|
||||
// environment the value (which may be empty) is returned
|
||||
// and the boolean is true. Otherwise the returned value
|
||||
// will be empty and the boolean will be false.
|
||||
//
|
||||
// Additionally if the input is env://username:password@remote:port/
|
||||
// to fetch ENV values for the env value from a remote server.
|
||||
func LookupEnv(key string) (string, bool) {
|
||||
v, ok := os.LookupEnv(key)
|
||||
if ok {
|
||||
u, err := url.Parse(v)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
if !isValidEnvScheme(u.Scheme) {
|
||||
return v, true
|
||||
}
|
||||
v, err = fetchEnvHTTP(key, u)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
return v, true
|
||||
}
|
||||
return "", false
|
||||
}
|
Loading…
Reference in New Issue