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
### 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.
@ -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.

View File

@ -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 <lockname>",
Use: "lock <lockname> [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),
}
}