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  }