txn_command.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  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. "bufio"
  17. "context"
  18. "fmt"
  19. "os"
  20. "strconv"
  21. "strings"
  22. "go.etcd.io/etcd/clientv3"
  23. pb "go.etcd.io/etcd/etcdserver/etcdserverpb"
  24. "github.com/spf13/cobra"
  25. )
  26. var txnInteractive bool
  27. // NewTxnCommand returns the cobra command for "txn".
  28. func NewTxnCommand() *cobra.Command {
  29. cmd := &cobra.Command{
  30. Use: "txn [options]",
  31. Short: "Txn processes all the requests in one transaction",
  32. Run: txnCommandFunc,
  33. }
  34. cmd.Flags().BoolVarP(&txnInteractive, "interactive", "i", false, "Input transaction in interactive mode")
  35. return cmd
  36. }
  37. // txnCommandFunc executes the "txn" command.
  38. func txnCommandFunc(cmd *cobra.Command, args []string) {
  39. if len(args) != 0 {
  40. ExitWithError(ExitBadArgs, fmt.Errorf("txn command does not accept argument"))
  41. }
  42. reader := bufio.NewReader(os.Stdin)
  43. txn := mustClientFromCmd(cmd).Txn(context.Background())
  44. promptInteractive("compares:")
  45. txn.If(readCompares(reader)...)
  46. promptInteractive("success requests (get, put, del):")
  47. txn.Then(readOps(reader)...)
  48. promptInteractive("failure requests (get, put, del):")
  49. txn.Else(readOps(reader)...)
  50. resp, err := txn.Commit()
  51. if err != nil {
  52. ExitWithError(ExitError, err)
  53. }
  54. display.Txn(*resp)
  55. }
  56. func promptInteractive(s string) {
  57. if txnInteractive {
  58. fmt.Println(s)
  59. }
  60. }
  61. func readCompares(r *bufio.Reader) (cmps []clientv3.Cmp) {
  62. for {
  63. line, err := r.ReadString('\n')
  64. if err != nil {
  65. ExitWithError(ExitInvalidInput, err)
  66. }
  67. // remove space from the line
  68. line = strings.TrimSpace(line)
  69. if len(line) == 0 {
  70. break
  71. }
  72. cmp, err := parseCompare(line)
  73. if err != nil {
  74. ExitWithError(ExitInvalidInput, err)
  75. }
  76. cmps = append(cmps, *cmp)
  77. }
  78. return cmps
  79. }
  80. func readOps(r *bufio.Reader) (ops []clientv3.Op) {
  81. for {
  82. line, err := r.ReadString('\n')
  83. if err != nil {
  84. ExitWithError(ExitInvalidInput, err)
  85. }
  86. // remove space from the line
  87. line = strings.TrimSpace(line)
  88. if len(line) == 0 {
  89. break
  90. }
  91. op, err := parseRequestUnion(line)
  92. if err != nil {
  93. ExitWithError(ExitInvalidInput, err)
  94. }
  95. ops = append(ops, *op)
  96. }
  97. return ops
  98. }
  99. func parseRequestUnion(line string) (*clientv3.Op, error) {
  100. args := argify(line)
  101. if len(args) < 2 {
  102. return nil, fmt.Errorf("invalid txn compare request: %s", line)
  103. }
  104. opc := make(chan clientv3.Op, 1)
  105. put := NewPutCommand()
  106. put.Run = func(cmd *cobra.Command, args []string) {
  107. key, value, opts := getPutOp(args)
  108. opc <- clientv3.OpPut(key, value, opts...)
  109. }
  110. get := NewGetCommand()
  111. get.Run = func(cmd *cobra.Command, args []string) {
  112. key, opts := getGetOp(args)
  113. opc <- clientv3.OpGet(key, opts...)
  114. }
  115. del := NewDelCommand()
  116. del.Run = func(cmd *cobra.Command, args []string) {
  117. key, opts := getDelOp(args)
  118. opc <- clientv3.OpDelete(key, opts...)
  119. }
  120. cmds := &cobra.Command{SilenceErrors: true}
  121. cmds.AddCommand(put, get, del)
  122. cmds.SetArgs(args)
  123. if err := cmds.Execute(); err != nil {
  124. return nil, fmt.Errorf("invalid txn request: %s", line)
  125. }
  126. op := <-opc
  127. return &op, nil
  128. }
  129. func parseCompare(line string) (*clientv3.Cmp, error) {
  130. var (
  131. key string
  132. op string
  133. val string
  134. )
  135. lparenSplit := strings.SplitN(line, "(", 2)
  136. if len(lparenSplit) != 2 {
  137. return nil, fmt.Errorf("malformed comparison: %s", line)
  138. }
  139. target := lparenSplit[0]
  140. n, serr := fmt.Sscanf(lparenSplit[1], "%q) %s %q", &key, &op, &val)
  141. if n != 3 {
  142. return nil, fmt.Errorf("malformed comparison: %s; got %s(%q) %s %q", line, target, key, op, val)
  143. }
  144. if serr != nil {
  145. return nil, fmt.Errorf("malformed comparison: %s (%v)", line, serr)
  146. }
  147. var (
  148. v int64
  149. err error
  150. cmp clientv3.Cmp
  151. )
  152. switch target {
  153. case "ver", "version":
  154. if v, err = strconv.ParseInt(val, 10, 64); err == nil {
  155. cmp = clientv3.Compare(clientv3.Version(key), op, v)
  156. }
  157. case "c", "create":
  158. if v, err = strconv.ParseInt(val, 10, 64); err == nil {
  159. cmp = clientv3.Compare(clientv3.CreateRevision(key), op, v)
  160. }
  161. case "m", "mod":
  162. if v, err = strconv.ParseInt(val, 10, 64); err == nil {
  163. cmp = clientv3.Compare(clientv3.ModRevision(key), op, v)
  164. }
  165. case "val", "value":
  166. cmp = clientv3.Compare(clientv3.Value(key), op, val)
  167. case "lease":
  168. cmp = clientv3.Compare(clientv3.Cmp{Target: pb.Compare_LEASE}, op, val)
  169. default:
  170. return nil, fmt.Errorf("malformed comparison: %s (unknown target %s)", line, target)
  171. }
  172. if err != nil {
  173. return nil, fmt.Errorf("invalid txn compare request: %s", line)
  174. }
  175. return &cmp, nil
  176. }