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 }