Skip to content

Commit

Permalink
Merge pull request #7983 from heyitsanthony/etcdctl-lock-exec
Browse files Browse the repository at this point in the history
etcdctl: support exec on lock
  • Loading branch information
heyitsanthony committed May 25, 2017
2 parents 4a8d32e + 643c2a3 commit 967fc70
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 967fc70

Please sign in to comment.