etcdctl: support exec on lock

The lock command is clumsy to use from the command line, needing mkfifo,
wait, etc. Instead, make like consul and support launching a command if
one is given.
This commit is contained in:
Anthony Romano 2017-05-24 13:57:16 -07:00
parent 8c1ab62bc5
commit 643c2a310d
2 changed files with 38 additions and 7 deletions

View File

@ -790,7 +790,7 @@ Prints a line of JSON encoding the database hash, revision, total keys, and size
## Concurrency commands ## Concurrency commands
### LOCK \<lockname\> ### LOCK \<lockname\> [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. 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. 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 #### Example
Acquire lock with standard output display:
```bash ```bash
./etcdctl lock mylock ./etcdctl lock mylock
# mylock/1234534535445 # mylock/1234534535445
``` ```
Acquire lock and execute `echo lock acquired`:
```bash
./etcdctl lock mylock echo lock acquired
# lock acquired
```
#### Remarks #### Remarks
LOCK returns a zero exit code only if it is terminated by a signal and releases the lock. LOCK returns a zero exit code only if it is terminated by a signal and releases the lock.

View File

@ -16,7 +16,9 @@ package command
import ( import (
"errors" "errors"
"fmt"
"os" "os"
"os/exec"
"os/signal" "os/signal"
"syscall" "syscall"
@ -29,7 +31,7 @@ import (
// NewLockCommand returns the cobra command for "lock". // NewLockCommand returns the cobra command for "lock".
func NewLockCommand() *cobra.Command { func NewLockCommand() *cobra.Command {
c := &cobra.Command{ c := &cobra.Command{
Use: "lock <lockname>", Use: "lock <lockname> [exec-command arg1 arg2 ...]",
Short: "Acquires a named lock", Short: "Acquires a named lock",
Run: lockCommandFunc, Run: lockCommandFunc,
} }
@ -37,16 +39,16 @@ func NewLockCommand() *cobra.Command {
} }
func lockCommandFunc(cmd *cobra.Command, args []string) { func lockCommandFunc(cmd *cobra.Command, args []string) {
if len(args) != 1 { if len(args) == 0 {
ExitWithError(ExitBadArgs, errors.New("lock takes one lock name argument.")) ExitWithError(ExitBadArgs, errors.New("lock takes a lock name argument and an optional command to execute."))
} }
c := mustClientFromCmd(cmd) c := mustClientFromCmd(cmd)
if err := lockUntilSignal(c, args[0]); err != nil { if err := lockUntilSignal(c, args[0], args[1:]); err != nil {
ExitWithError(ExitError, err) 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) s, err := concurrency.NewSession(c)
if err != nil { if err != nil {
return err return err
@ -69,6 +71,18 @@ func lockUntilSignal(c *clientv3.Client, lockname string) error {
return err 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()) k, kerr := c.Get(ctx, m.Key())
if kerr != nil { if kerr != nil {
return kerr return kerr
@ -76,7 +90,6 @@ func lockUntilSignal(c *clientv3.Client, lockname string) error {
if len(k.Kvs) == 0 { if len(k.Kvs) == 0 {
return errors.New("lock lost on init") return errors.New("lock lost on init")
} }
display.Get(*k) display.Get(*k)
select { select {
@ -87,3 +100,10 @@ func lockUntilSignal(c *clientv3.Client, lockname string) error {
return errors.New("session expired") 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),
}
}