txn_command.go 4.9 KB

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