global.go 11 KB

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