From c047ed593c2f7f355f1d8e6554cde16be679288c Mon Sep 17 00:00:00 2001 From: garenchan Date: Mon, 5 Apr 2021 11:55:55 +0800 Subject: [PATCH] etcdctl: lock return exit code of exec-command Sometimes we expect to get the exit code of the command being executed. --- CHANGELOG-3.5.md | 1 + etcdctl/ctlv3/command/lock_command.go | 17 +++++++++++++++- tests/e2e/ctl_v3_lock_test.go | 29 +++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/CHANGELOG-3.5.md b/CHANGELOG-3.5.md index bf8cb510cef..d1b0a4939e4 100644 --- a/CHANGELOG-3.5.md +++ b/CHANGELOG-3.5.md @@ -205,6 +205,7 @@ Note that any `etcd_debugging_*` metrics are experimental and subject to change. - Add [`etcdctl auth status`](https://github.com/etcd-io/etcd/pull/11536) command to check if authentication is enabled - Add [`etcdctl get --count-only`](https://github.com/etcd-io/etcd/pull/11743) flag for output type `fields`. - Add [`etcdctl member list -w=json --hex`](https://github.com/etcd-io/etcd/pull/11812) flag to print memberListResponse in hex format json. +- Changed [`etcdctl lock exec-command`](https://github.com/etcd-io/etcd/pull/12829) to return exit code of exec-command. ### gRPC gateway diff --git a/etcdctl/ctlv3/command/lock_command.go b/etcdctl/ctlv3/command/lock_command.go index a78a8799b9e..8bbb16cfd0c 100644 --- a/etcdctl/ctlv3/command/lock_command.go +++ b/etcdctl/ctlv3/command/lock_command.go @@ -48,10 +48,25 @@ func lockCommandFunc(cmd *cobra.Command, args []string) { } c := mustClientFromCmd(cmd) if err := lockUntilSignal(c, args[0], args[1:]); err != nil { - ExitWithError(ExitError, err) + code := getExitCodeFromError(err) + ExitWithError(code, err) } } +func getExitCodeFromError(err error) int { + if err == nil { + return ExitSuccess + } + + if exitErr, ok := err.(*exec.ExitError); ok { + if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { + return status.ExitStatus() + } + } + + return ExitError +} + func lockUntilSignal(c *clientv3.Client, lockname string, cmdArgs []string) error { s, err := concurrency.NewSession(c, concurrency.WithTTL(lockTTL)) if err != nil { diff --git a/tests/e2e/ctl_v3_lock_test.go b/tests/e2e/ctl_v3_lock_test.go index 8a1598735a4..e88000a1516 100644 --- a/tests/e2e/ctl_v3_lock_test.go +++ b/tests/e2e/ctl_v3_lock_test.go @@ -15,6 +15,7 @@ package e2e import ( + "fmt" "os" "strings" "testing" @@ -27,6 +28,10 @@ func TestCtlV3Lock(t *testing.T) { testCtl(t, testLock) } +func TestCtlV3LockWithCmd(t *testing.T) { + testCtl(t, testLockWithCmd) +} + func testLock(cx ctlCtx) { name := "a" @@ -95,6 +100,22 @@ func testLock(cx ctlCtx) { } } +func testLockWithCmd(cx ctlCtx) { + // exec command with zero exit code + echoCmd := []string{"echo"} + if err := ctlV3LockWithCmd(cx, echoCmd, ""); err != nil { + cx.t.Fatal(err) + } + + // exec command with non-zero exit code + code := 3 + awkCmd := []string{"awk", fmt.Sprintf("BEGIN{exit %d}", code)} + expect := fmt.Sprintf("Error: exit status %d", code) + if err := ctlV3LockWithCmd(cx, awkCmd, expect); err != nil { + cx.t.Fatal(err) + } +} + // ctlV3Lock creates a lock process with a channel listening for when it acquires the lock. func ctlV3Lock(cx ctlCtx, name string) (*expect.ExpectProcess, <-chan string, error) { cmdArgs := append(cx.PrefixArgs(), "lock", name) @@ -113,3 +134,11 @@ func ctlV3Lock(cx ctlCtx, name string) (*expect.ExpectProcess, <-chan string, er }() return proc, outc, err } + +// ctlV3LockWithCmd creates a lock process to exec command. +func ctlV3LockWithCmd(cx ctlCtx, execCmd []string, as ...string) error { + // use command as lock name + cmdArgs := append(cx.PrefixArgs(), "lock", execCmd[0]) + cmdArgs = append(cmdArgs, execCmd...) + return spawnWithExpects(cmdArgs, as...) +}