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