global.go 11 KB

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