vctx: exec login and operator commands uncoloured; test signal forwarding
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful

This commit is contained in:
2026-10-23 23:12:33 +11:00
parent f151902e43
commit 92223ce33d
3 changed files with 49 additions and 5 deletions
+1 -1
View File
@@ -71,7 +71,7 @@ coloured, SIGINT/SIGTERM are forwarded, and vault's exit code is returned
Without colour (piped output, `NO_COLOR`, `table`/`raw`/`pretty` formats)
`vctx` execs `vault` exactly as before, so scripts see unchanged output.
Streaming or interactive commands (`monitor`, `debug`, `events`, `ssh`,
`agent`, `proxy`, `server`) are always exec'd and never coloured. `JQ_COLORS`
`agent`, `proxy`, `server`, `login`, `operator`) are always exec'd and never coloured. `JQ_COLORS`
overrides the JSON palette as in `jq`.
## Flags
+5 -4
View File
@@ -35,9 +35,10 @@ var execVault = syscall.Exec
// osExit is swapped in tests to observe the child's exit code.
var osExit = os.Exit
// streaming commands are never captured: buffering their stdout would hide
// output until exit (or break the TTY, for ssh).
var streaming = map[string]bool{"monitor": true, "debug": true, "events": true, "ssh": true, "agent": true, "proxy": true, "server": true}
// uncaptured commands are always exec'd: buffering their stdout would hide
// streamed output or prompts (unseal keys, MFA passcodes) until exit, or break
// the TTY, for ssh.
var uncaptured = map[string]bool{"monitor": true, "debug": true, "events": true, "ssh": true, "agent": true, "proxy": true, "server": true, "login": true, "operator": true}
func main() {
if err := newRootCmd().Execute(); err != nil {
@@ -269,7 +270,7 @@ func colourFormat(args, env []string) string {
f = val
}
f = strings.ToLower(f)
if streaming[cmd] || (f != "json" && f != "yaml") {
if uncaptured[cmd] || (f != "json" && f != "yaml") {
return ""
}
return f
+43
View File
@@ -7,7 +7,9 @@ import (
"path/filepath"
"reflect"
"strings"
"syscall"
"testing"
"time"
"git.unkin.net/unkin/vault-tools/shared"
)
@@ -278,6 +280,10 @@ func TestColourFormat(t *testing.T) {
{[]string{"events", "subscribe", "kv*"}, []string{"VAULT_FORMAT=json"}, ""},
{[]string{"monitor"}, []string{"VAULT_FORMAT=json"}, ""},
{[]string{"ssh", "-format=json", "host"}, nil, ""},
{[]string{"login", "-method=userpass"}, []string{"VAULT_FORMAT=json"}, ""},
{[]string{"operator", "unseal"}, []string{"VAULT_FORMAT=json"}, ""},
{[]string{"-format=yaml", "operator", "generate-root"}, nil, ""},
{[]string{"operator", "rekey", "-format=json"}, nil, ""},
}
for _, c := range cases {
if got := colourFormat(c.args, c.env); got != c.want {
@@ -323,6 +329,41 @@ func TestRunChild(t *testing.T) {
}
}
func TestRunChildForwardsSignal(t *testing.T) {
dir := t.TempDir()
bin := fakeVault(t, `trap 'echo TERM > "$D/got"; trap - TERM; kill -TERM $$' TERM
touch "$D/ready"
i=0; while [ $i -lt 100 ]; do sleep 0.05; i=$((i+1)); done`)
type res struct {
code int
err error
}
ch := make(chan res, 1)
go func() {
code, err := runChild(bin, []string{bin}, append(os.Environ(), "D="+dir), "json", &bytes.Buffer{})
ch <- res{code, err}
}()
for i := 0; i < 200; i++ {
if _, err := os.Stat(filepath.Join(dir, "ready")); err == nil {
break
}
time.Sleep(10 * time.Millisecond)
}
if err := syscall.Kill(os.Getpid(), syscall.SIGTERM); err != nil {
t.Fatal(err)
}
r := <-ch
if r.err != nil {
t.Fatal(r.err)
}
if got, _ := os.ReadFile(filepath.Join(dir, "got")); string(got) != "TERM\n" {
t.Errorf("child did not receive SIGTERM (got %q)", got)
}
if r.code != 128+15 {
t.Errorf("code = %d, want %d", r.code, 128+15)
}
}
func TestRunChildPassesArgsAndEnv(t *testing.T) {
bin := fakeVault(t, `echo "{\"args\":\"$*\",\"tok\":\"$VAULT_TOKEN\"}"`)
var out bytes.Buffer
@@ -353,6 +394,8 @@ func TestRunVctxColourDecision(t *testing.T) {
{"always", []string{"status"}, true},
{"always", []string{"kv", "get", "kv/foo"}, false},
{"always", []string{"read", "-format=json", "x"}, false},
{"always", []string{"operator", "unseal", "-format=json"}, true},
{"always", []string{"login", "-format=json"}, true},
}
for _, c := range cases {
execed, exited := false, -1