go.etcd.io/etcd@v3.3.27+incompatible/etcdctl/ctlv3/command/global.go (about)

     1  // Copyright 2015 The etcd Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package command
    16  
    17  import (
    18  	"crypto/tls"
    19  	"errors"
    20  	"fmt"
    21  	"io"
    22  	"io/ioutil"
    23  	"os"
    24  	"strings"
    25  	"time"
    26  
    27  	"github.com/bgentry/speakeasy"
    28  	"github.com/coreos/etcd/clientv3"
    29  	"github.com/coreos/etcd/pkg/flags"
    30  	"github.com/coreos/etcd/pkg/srv"
    31  	"github.com/coreos/etcd/pkg/transport"
    32  	"github.com/spf13/cobra"
    33  	"github.com/spf13/pflag"
    34  	"google.golang.org/grpc/grpclog"
    35  )
    36  
    37  // GlobalFlags are flags that defined globally
    38  // and are inherited to all sub-commands.
    39  type GlobalFlags struct {
    40  	Insecure           bool
    41  	InsecureSkipVerify bool
    42  	InsecureDiscovery  bool
    43  	Endpoints          []string
    44  	DialTimeout        time.Duration
    45  	CommandTimeOut     time.Duration
    46  	KeepAliveTime      time.Duration
    47  	KeepAliveTimeout   time.Duration
    48  
    49  	TLS transport.TLSInfo
    50  
    51  	OutputFormat string
    52  	IsHex        bool
    53  
    54  	User string
    55  
    56  	Debug bool
    57  }
    58  
    59  type secureCfg struct {
    60  	cert       string
    61  	key        string
    62  	cacert     string
    63  	serverName string
    64  
    65  	insecureTransport  bool
    66  	insecureSkipVerify bool
    67  }
    68  
    69  type authCfg struct {
    70  	username string
    71  	password string
    72  }
    73  
    74  type discoveryCfg struct {
    75  	domain   string
    76  	insecure bool
    77  }
    78  
    79  var display printer = &simplePrinter{}
    80  
    81  func initDisplayFromCmd(cmd *cobra.Command) {
    82  	isHex, err := cmd.Flags().GetBool("hex")
    83  	if err != nil {
    84  		ExitWithError(ExitError, err)
    85  	}
    86  	outputType, err := cmd.Flags().GetString("write-out")
    87  	if err != nil {
    88  		ExitWithError(ExitError, err)
    89  	}
    90  	if display = NewPrinter(outputType, isHex); display == nil {
    91  		ExitWithError(ExitBadFeature, errors.New("unsupported output format"))
    92  	}
    93  }
    94  
    95  type clientConfig struct {
    96  	endpoints        []string
    97  	dialTimeout      time.Duration
    98  	keepAliveTime    time.Duration
    99  	keepAliveTimeout time.Duration
   100  	scfg             *secureCfg
   101  	acfg             *authCfg
   102  }
   103  
   104  type discardValue struct{}
   105  
   106  func (*discardValue) String() string   { return "" }
   107  func (*discardValue) Set(string) error { return nil }
   108  func (*discardValue) Type() string     { return "" }
   109  
   110  func clientConfigFromCmd(cmd *cobra.Command) *clientConfig {
   111  	fs := cmd.InheritedFlags()
   112  	if strings.HasPrefix(cmd.Use, "watch") {
   113  		// silence "pkg/flags: unrecognized environment variable ETCDCTL_WATCH_KEY=foo" warnings
   114  		// silence "pkg/flags: unrecognized environment variable ETCDCTL_WATCH_RANGE_END=bar" warnings
   115  		fs.AddFlag(&pflag.Flag{Name: "watch-key", Value: &discardValue{}})
   116  		fs.AddFlag(&pflag.Flag{Name: "watch-range-end", Value: &discardValue{}})
   117  	}
   118  	flags.SetPflagsFromEnv("ETCDCTL", fs)
   119  
   120  	debug, err := cmd.Flags().GetBool("debug")
   121  	if err != nil {
   122  		ExitWithError(ExitError, err)
   123  	}
   124  	if debug {
   125  		clientv3.SetLogger(grpclog.NewLoggerV2WithVerbosity(os.Stderr, os.Stderr, os.Stderr, 4))
   126  		fs.VisitAll(func(f *pflag.Flag) {
   127  			fmt.Fprintf(os.Stderr, "%s=%v\n", flags.FlagToEnv("ETCDCTL", f.Name), f.Value)
   128  		})
   129  	} else {
   130  		clientv3.SetLogger(grpclog.NewLoggerV2(ioutil.Discard, ioutil.Discard, ioutil.Discard))
   131  	}
   132  
   133  	cfg := &clientConfig{}
   134  	cfg.endpoints, err = endpointsFromCmd(cmd)
   135  	if err != nil {
   136  		ExitWithError(ExitError, err)
   137  	}
   138  
   139  	cfg.dialTimeout = dialTimeoutFromCmd(cmd)
   140  	cfg.keepAliveTime = keepAliveTimeFromCmd(cmd)
   141  	cfg.keepAliveTimeout = keepAliveTimeoutFromCmd(cmd)
   142  
   143  	cfg.scfg = secureCfgFromCmd(cmd)
   144  	cfg.acfg = authCfgFromCmd(cmd)
   145  
   146  	initDisplayFromCmd(cmd)
   147  	return cfg
   148  }
   149  
   150  func mustClientFromCmd(cmd *cobra.Command) *clientv3.Client {
   151  	cfg := clientConfigFromCmd(cmd)
   152  	return cfg.mustClient()
   153  }
   154  
   155  func (cc *clientConfig) mustClient() *clientv3.Client {
   156  	cfg, err := newClientCfg(cc.endpoints, cc.dialTimeout, cc.keepAliveTime, cc.keepAliveTimeout, cc.scfg, cc.acfg)
   157  	if err != nil {
   158  		ExitWithError(ExitBadArgs, err)
   159  	}
   160  
   161  	client, err := clientv3.New(*cfg)
   162  	if err != nil {
   163  		ExitWithError(ExitBadConnection, err)
   164  	}
   165  
   166  	return client
   167  }
   168  
   169  func newClientCfg(endpoints []string, dialTimeout, keepAliveTime, keepAliveTimeout time.Duration, scfg *secureCfg, acfg *authCfg) (*clientv3.Config, error) {
   170  	// set tls if any one tls option set
   171  	var cfgtls *transport.TLSInfo
   172  	tlsinfo := transport.TLSInfo{}
   173  	if scfg.cert != "" {
   174  		tlsinfo.CertFile = scfg.cert
   175  		cfgtls = &tlsinfo
   176  	}
   177  
   178  	if scfg.key != "" {
   179  		tlsinfo.KeyFile = scfg.key
   180  		cfgtls = &tlsinfo
   181  	}
   182  
   183  	if scfg.cacert != "" {
   184  		tlsinfo.CAFile = scfg.cacert
   185  		cfgtls = &tlsinfo
   186  	}
   187  
   188  	if scfg.serverName != "" {
   189  		tlsinfo.ServerName = scfg.serverName
   190  		cfgtls = &tlsinfo
   191  	}
   192  
   193  	cfg := &clientv3.Config{
   194  		Endpoints:            endpoints,
   195  		DialTimeout:          dialTimeout,
   196  		DialKeepAliveTime:    keepAliveTime,
   197  		DialKeepAliveTimeout: keepAliveTimeout,
   198  	}
   199  
   200  	if cfgtls != nil {
   201  		clientTLS, err := cfgtls.ClientConfig()
   202  		if err != nil {
   203  			return nil, err
   204  		}
   205  		cfg.TLS = clientTLS
   206  	}
   207  
   208  	// if key/cert is not given but user wants secure connection, we
   209  	// should still setup an empty tls configuration for gRPC to setup
   210  	// secure connection.
   211  	if cfg.TLS == nil && !scfg.insecureTransport {
   212  		cfg.TLS = &tls.Config{}
   213  	}
   214  
   215  	// If the user wants to skip TLS verification then we should set
   216  	// the InsecureSkipVerify flag in tls configuration.
   217  	if scfg.insecureSkipVerify && cfg.TLS != nil {
   218  		cfg.TLS.InsecureSkipVerify = true
   219  	}
   220  
   221  	if acfg != nil {
   222  		cfg.Username = acfg.username
   223  		cfg.Password = acfg.password
   224  	}
   225  
   226  	return cfg, nil
   227  }
   228  
   229  func argOrStdin(args []string, stdin io.Reader, i int) (string, error) {
   230  	if i < len(args) {
   231  		return args[i], nil
   232  	}
   233  	bytes, err := ioutil.ReadAll(stdin)
   234  	if string(bytes) == "" || err != nil {
   235  		return "", errors.New("no available argument and stdin")
   236  	}
   237  	return string(bytes), nil
   238  }
   239  
   240  func dialTimeoutFromCmd(cmd *cobra.Command) time.Duration {
   241  	dialTimeout, err := cmd.Flags().GetDuration("dial-timeout")
   242  	if err != nil {
   243  		ExitWithError(ExitError, err)
   244  	}
   245  	return dialTimeout
   246  }
   247  
   248  func keepAliveTimeFromCmd(cmd *cobra.Command) time.Duration {
   249  	keepAliveTime, err := cmd.Flags().GetDuration("keepalive-time")
   250  	if err != nil {
   251  		ExitWithError(ExitError, err)
   252  	}
   253  	return keepAliveTime
   254  }
   255  
   256  func keepAliveTimeoutFromCmd(cmd *cobra.Command) time.Duration {
   257  	keepAliveTimeout, err := cmd.Flags().GetDuration("keepalive-timeout")
   258  	if err != nil {
   259  		ExitWithError(ExitError, err)
   260  	}
   261  	return keepAliveTimeout
   262  }
   263  
   264  func secureCfgFromCmd(cmd *cobra.Command) *secureCfg {
   265  	cert, key, cacert := keyAndCertFromCmd(cmd)
   266  	insecureTr := insecureTransportFromCmd(cmd)
   267  	skipVerify := insecureSkipVerifyFromCmd(cmd)
   268  	discoveryCfg := discoveryCfgFromCmd(cmd)
   269  
   270  	if discoveryCfg.insecure {
   271  		discoveryCfg.domain = ""
   272  	}
   273  
   274  	return &secureCfg{
   275  		cert:       cert,
   276  		key:        key,
   277  		cacert:     cacert,
   278  		serverName: discoveryCfg.domain,
   279  
   280  		insecureTransport:  insecureTr,
   281  		insecureSkipVerify: skipVerify,
   282  	}
   283  }
   284  
   285  func insecureTransportFromCmd(cmd *cobra.Command) bool {
   286  	insecureTr, err := cmd.Flags().GetBool("insecure-transport")
   287  	if err != nil {
   288  		ExitWithError(ExitError, err)
   289  	}
   290  	return insecureTr
   291  }
   292  
   293  func insecureSkipVerifyFromCmd(cmd *cobra.Command) bool {
   294  	skipVerify, err := cmd.Flags().GetBool("insecure-skip-tls-verify")
   295  	if err != nil {
   296  		ExitWithError(ExitError, err)
   297  	}
   298  	return skipVerify
   299  }
   300  
   301  func keyAndCertFromCmd(cmd *cobra.Command) (cert, key, cacert string) {
   302  	var err error
   303  	if cert, err = cmd.Flags().GetString("cert"); err != nil {
   304  		ExitWithError(ExitBadArgs, err)
   305  	} else if cert == "" && cmd.Flags().Changed("cert") {
   306  		ExitWithError(ExitBadArgs, errors.New("empty string is passed to --cert option"))
   307  	}
   308  
   309  	if key, err = cmd.Flags().GetString("key"); err != nil {
   310  		ExitWithError(ExitBadArgs, err)
   311  	} else if key == "" && cmd.Flags().Changed("key") {
   312  		ExitWithError(ExitBadArgs, errors.New("empty string is passed to --key option"))
   313  	}
   314  
   315  	if cacert, err = cmd.Flags().GetString("cacert"); err != nil {
   316  		ExitWithError(ExitBadArgs, err)
   317  	} else if cacert == "" && cmd.Flags().Changed("cacert") {
   318  		ExitWithError(ExitBadArgs, errors.New("empty string is passed to --cacert option"))
   319  	}
   320  
   321  	return cert, key, cacert
   322  }
   323  
   324  func authCfgFromCmd(cmd *cobra.Command) *authCfg {
   325  	userFlag, err := cmd.Flags().GetString("user")
   326  	if err != nil {
   327  		ExitWithError(ExitBadArgs, err)
   328  	}
   329  
   330  	if userFlag == "" {
   331  		return nil
   332  	}
   333  
   334  	var cfg authCfg
   335  
   336  	splitted := strings.SplitN(userFlag, ":", 2)
   337  	if len(splitted) < 2 {
   338  		cfg.username = userFlag
   339  		cfg.password, err = speakeasy.Ask("Password: ")
   340  		if err != nil {
   341  			ExitWithError(ExitError, err)
   342  		}
   343  	} else {
   344  		cfg.username = splitted[0]
   345  		cfg.password = splitted[1]
   346  	}
   347  
   348  	return &cfg
   349  }
   350  
   351  func insecureDiscoveryFromCmd(cmd *cobra.Command) bool {
   352  	discovery, err := cmd.Flags().GetBool("insecure-discovery")
   353  	if err != nil {
   354  		ExitWithError(ExitError, err)
   355  	}
   356  	return discovery
   357  }
   358  
   359  func discoverySrvFromCmd(cmd *cobra.Command) string {
   360  	domainStr, err := cmd.Flags().GetString("discovery-srv")
   361  	if err != nil {
   362  		ExitWithError(ExitBadArgs, err)
   363  	}
   364  	return domainStr
   365  }
   366  
   367  func discoveryCfgFromCmd(cmd *cobra.Command) *discoveryCfg {
   368  	return &discoveryCfg{
   369  		domain:   discoverySrvFromCmd(cmd),
   370  		insecure: insecureDiscoveryFromCmd(cmd),
   371  	}
   372  }
   373  
   374  func endpointsFromCmd(cmd *cobra.Command) ([]string, error) {
   375  	eps, err := endpointsFromFlagValue(cmd)
   376  	if err != nil {
   377  		return nil, err
   378  	}
   379  	// If domain discovery returns no endpoints, check endpoints flag
   380  	if len(eps) == 0 {
   381  		eps, err = cmd.Flags().GetStringSlice("endpoints")
   382  	}
   383  	return eps, err
   384  }
   385  
   386  func endpointsFromFlagValue(cmd *cobra.Command) ([]string, error) {
   387  	discoveryCfg := discoveryCfgFromCmd(cmd)
   388  
   389  	// If we still don't have domain discovery, return nothing
   390  	if discoveryCfg.domain == "" {
   391  		return []string{}, nil
   392  	}
   393  
   394  	srvs, err := srv.GetClient("etcd-client", discoveryCfg.domain)
   395  	if err != nil {
   396  		return nil, err
   397  	}
   398  	eps := srvs.Endpoints
   399  	if discoveryCfg.insecure {
   400  		return eps, err
   401  	}
   402  	// strip insecure connections
   403  	ret := []string{}
   404  	for _, ep := range eps {
   405  		if strings.HasPrefix(ep, "http://") {
   406  			fmt.Fprintf(os.Stderr, "ignoring discovered insecure endpoint %q\n", ep)
   407  			continue
   408  		}
   409  		ret = append(ret, ep)
   410  	}
   411  	return ret, err
   412  }