Skip to content

Commit

Permalink
etcdctl: support exec on lock
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Anthony Romano committed May 24, 2017
1 parent 8c1ab62 commit 643c2a3
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 7 deletions.
13 changes: 12 additions & 1 deletion etcdctl/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -790,21 +790,32 @@ 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.

#### Output

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.
Expand Down
32 changes: 26 additions & 6 deletions etcdctl/ctlv3/command/lock_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ package command

import (
"errors"
"fmt"
"os"
"os/exec"
"os/signal"
"syscall"

Expand All @@ -29,24 +31,24 @@ 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,
}
return c
}

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
Expand All @@ -69,14 +71,25 @@ 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
}
if len(k.Kvs) == 0 {
return errors.New("lock lost on init")
}

display.Get(*k)

select {
Expand All @@ -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),
}
}

0 comments on commit 643c2a3

Please sign in to comment.