123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 |
- // Copyright 2015 The etcd Authors
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package command
- import (
- "bufio"
- "context"
- "fmt"
- "os"
- "strconv"
- "strings"
- "go.etcd.io/etcd/clientv3"
- pb "go.etcd.io/etcd/etcdserver/etcdserverpb"
- "github.com/spf13/cobra"
- )
- var txnInteractive bool
- // NewTxnCommand returns the cobra command for "txn".
- func NewTxnCommand() *cobra.Command {
- cmd := &cobra.Command{
- Use: "txn [options]",
- Short: "Txn processes all the requests in one transaction",
- Run: txnCommandFunc,
- }
- cmd.Flags().BoolVarP(&txnInteractive, "interactive", "i", false, "Input transaction in interactive mode")
- return cmd
- }
- // txnCommandFunc executes the "txn" command.
- func txnCommandFunc(cmd *cobra.Command, args []string) {
- if len(args) != 0 {
- ExitWithError(ExitBadArgs, fmt.Errorf("txn command does not accept argument"))
- }
- reader := bufio.NewReader(os.Stdin)
- txn := mustClientFromCmd(cmd).Txn(context.Background())
- promptInteractive("compares:")
- txn.If(readCompares(reader)...)
- promptInteractive("success requests (get, put, del):")
- txn.Then(readOps(reader)...)
- promptInteractive("failure requests (get, put, del):")
- txn.Else(readOps(reader)...)
- resp, err := txn.Commit()
- if err != nil {
- ExitWithError(ExitError, err)
- }
- display.Txn(*resp)
- }
- func promptInteractive(s string) {
- if txnInteractive {
- fmt.Println(s)
- }
- }
- func readCompares(r *bufio.Reader) (cmps []clientv3.Cmp) {
- for {
- line, err := r.ReadString('\n')
- if err != nil {
- ExitWithError(ExitInvalidInput, err)
- }
- // remove space from the line
- line = strings.TrimSpace(line)
- if len(line) == 0 {
- break
- }
- cmp, err := parseCompare(line)
- if err != nil {
- ExitWithError(ExitInvalidInput, err)
- }
- cmps = append(cmps, *cmp)
- }
- return cmps
- }
- func readOps(r *bufio.Reader) (ops []clientv3.Op) {
- for {
- line, err := r.ReadString('\n')
- if err != nil {
- ExitWithError(ExitInvalidInput, err)
- }
- // remove space from the line
- line = strings.TrimSpace(line)
- if len(line) == 0 {
- break
- }
- op, err := parseRequestUnion(line)
- if err != nil {
- ExitWithError(ExitInvalidInput, err)
- }
- ops = append(ops, *op)
- }
- return ops
- }
- func parseRequestUnion(line string) (*clientv3.Op, error) {
- args := argify(line)
- if len(args) < 2 {
- return nil, fmt.Errorf("invalid txn compare request: %s", line)
- }
- opc := make(chan clientv3.Op, 1)
- put := NewPutCommand()
- put.Run = func(cmd *cobra.Command, args []string) {
- key, value, opts := getPutOp(args)
- opc <- clientv3.OpPut(key, value, opts...)
- }
- get := NewGetCommand()
- get.Run = func(cmd *cobra.Command, args []string) {
- key, opts := getGetOp(args)
- opc <- clientv3.OpGet(key, opts...)
- }
- del := NewDelCommand()
- del.Run = func(cmd *cobra.Command, args []string) {
- key, opts := getDelOp(args)
- opc <- clientv3.OpDelete(key, opts...)
- }
- cmds := &cobra.Command{SilenceErrors: true}
- cmds.AddCommand(put, get, del)
- cmds.SetArgs(args)
- if err := cmds.Execute(); err != nil {
- return nil, fmt.Errorf("invalid txn request: %s", line)
- }
- op := <-opc
- return &op, nil
- }
- func parseCompare(line string) (*clientv3.Cmp, error) {
- var (
- key string
- op string
- val string
- )
- lparenSplit := strings.SplitN(line, "(", 2)
- if len(lparenSplit) != 2 {
- return nil, fmt.Errorf("malformed comparison: %s", line)
- }
- target := lparenSplit[0]
- n, serr := fmt.Sscanf(lparenSplit[1], "%q) %s %q", &key, &op, &val)
- if n != 3 {
- return nil, fmt.Errorf("malformed comparison: %s; got %s(%q) %s %q", line, target, key, op, val)
- }
- if serr != nil {
- return nil, fmt.Errorf("malformed comparison: %s (%v)", line, serr)
- }
- var (
- v int64
- err error
- cmp clientv3.Cmp
- )
- switch target {
- case "ver", "version":
- if v, err = strconv.ParseInt(val, 10, 64); err == nil {
- cmp = clientv3.Compare(clientv3.Version(key), op, v)
- }
- case "c", "create":
- if v, err = strconv.ParseInt(val, 10, 64); err == nil {
- cmp = clientv3.Compare(clientv3.CreateRevision(key), op, v)
- }
- case "m", "mod":
- if v, err = strconv.ParseInt(val, 10, 64); err == nil {
- cmp = clientv3.Compare(clientv3.ModRevision(key), op, v)
- }
- case "val", "value":
- cmp = clientv3.Compare(clientv3.Value(key), op, val)
- case "lease":
- cmp = clientv3.Compare(clientv3.Cmp{Target: pb.Compare_LEASE}, op, val)
- default:
- return nil, fmt.Errorf("malformed comparison: %s (unknown target %s)", line, target)
- }
- if err != nil {
- return nil, fmt.Errorf("invalid txn compare request: %s", line)
- }
- return &cmp, nil
- }
|