txn_command.go 4.8 KB

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