diff --git a/etcdctl/README.md b/etcdctl/README.md index 2781177f200..22682370a4f 100644 --- a/etcdctl/README.md +++ b/etcdctl/README.md @@ -790,7 +790,7 @@ Prints a line of JSON encoding the database hash, revision, total keys, and size ## Concurrency commands -### LOCK \ +### LOCK \ [command arg1 arg2 ...] LOCK acquires a distributed named mutex with a given name. Once the lock is acquired, it will be held until etcdctl is terminated. @@ -798,13 +798,24 @@ LOCK acquires a distributed named mutex with a given name. Once the lock is acqu Once the lock is acquired, the result for the GET on the unique lock holder key is displayed. +If a command is given, it will be launched with environment variables `ETCD_LOCK_KEY` and `ETCD_LOCK_REV` set to the lock's holder key and revision. + #### Example +Acquire lock with standard output display: + ```bash ./etcdctl lock mylock # mylock/1234534535445 ``` +Acquire lock and execute `echo lock acquired`: + +```bash +./etcdctl lock mylock echo lock acquired +# lock acquired +``` + #### Remarks LOCK returns a zero exit code only if it is terminated by a signal and releases the lock. diff --git a/etcdctl/ctlv3/command/lock_command.go b/etcdctl/ctlv3/command/lock_command.go index 2e55c49df87..e130493f81f 100644 --- a/etcdctl/ctlv3/command/lock_command.go +++ b/etcdctl/ctlv3/command/lock_command.go @@ -16,7 +16,9 @@ package command import ( "errors" + "fmt" "os" + "os/exec" "os/signal" "syscall" @@ -29,7 +31,7 @@ import ( // NewLockCommand returns the cobra command for "lock". func NewLockCommand() *cobra.Command { c := &cobra.Command{ - Use: "lock ", + Use: "lock [exec-command arg1 arg2 ...]", Short: "Acquires a named lock", Run: lockCommandFunc, } @@ -37,16 +39,16 @@ func NewLockCommand() *cobra.Command { } func lockCommandFunc(cmd *cobra.Command, args []string) { - if len(args) != 1 { - ExitWithError(ExitBadArgs, errors.New("lock takes one lock name argument.")) + if len(args) == 0 { + ExitWithError(ExitBadArgs, errors.New("lock takes a lock name argument and an optional command to execute.")) } c := mustClientFromCmd(cmd) - if err := lockUntilSignal(c, args[0]); err != nil { + if err := lockUntilSignal(c, args[0], args[1:]); err != nil { ExitWithError(ExitError, err) } } -func lockUntilSignal(c *clientv3.Client, lockname string) error { +func lockUntilSignal(c *clientv3.Client, lockname string, cmdArgs []string) error { s, err := concurrency.NewSession(c) if err != nil { return err @@ -69,6 +71,18 @@ func lockUntilSignal(c *clientv3.Client, lockname string) error { return err } + if len(cmdArgs) > 0 { + cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...) + cmd.Env = append(environLockResponse(m), os.Environ()...) + cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr + err := cmd.Run() + unlockErr := m.Unlock(context.TODO()) + if err != nil { + return err + } + return unlockErr + } + k, kerr := c.Get(ctx, m.Key()) if kerr != nil { return kerr @@ -76,7 +90,6 @@ func lockUntilSignal(c *clientv3.Client, lockname string) error { if len(k.Kvs) == 0 { return errors.New("lock lost on init") } - display.Get(*k) select { @@ -87,3 +100,10 @@ func lockUntilSignal(c *clientv3.Client, lockname string) error { return errors.New("session expired") } + +func environLockResponse(m *concurrency.Mutex) []string { + return []string{ + "ETCD_LOCK_KEY=" + m.Key(), + fmt.Sprintf("ETCD_LOCK_REV=%d", m.Header().Revision), + } +}