global.go 11 KB

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