From 8de51ed6f028d80780d62f213e69fda4fe4eb92a Mon Sep 17 00:00:00 2001 From: Daniel Bennett <8812489+gulducat@users.noreply.github.com> Date: Wed, 6 May 2026 12:47:49 -0400 Subject: [PATCH] cli: automatically expand `exec -it` to `-i -t` so e.g. `nomad exec -it` works the same as `docker exec -it` --- .changelog/27906.txt | 3 +++ command/alloc_exec.go | 17 ++++++++++++++ command/alloc_exec_test.go | 48 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 .changelog/27906.txt diff --git a/.changelog/27906.txt b/.changelog/27906.txt new file mode 100644 index 00000000000..13a9746b451 --- /dev/null +++ b/.changelog/27906.txt @@ -0,0 +1,3 @@ +```release-note:improvement +cli: Automatically expand `nomad exec -it` to `-i -t` +``` diff --git a/command/alloc_exec.go b/command/alloc_exec.go index 2b1f8312c5b..66abdf405b1 100644 --- a/command/alloc_exec.go +++ b/command/alloc_exec.go @@ -118,6 +118,7 @@ func (l *AllocExecCommand) Run(args []string) int { flags.StringVar(&task, "task", "", "") flags.StringVar(&group, "group", "", "") + args = expandITFlags(args) if err := flags.Parse(args); err != nil { return 1 } @@ -340,6 +341,22 @@ func setRawTerminalOutput(stream interface{}) (cleanup func(), err error) { return func() { term.RestoreTerminal(fd, state) }, nil } +// expandITFlags preprocesses args to expand the combined short boolean flags +// -it and -ti into their individual forms -i -t, mirroring the convention +// established by tools like docker and ssh. +func expandITFlags(args []string) []string { + expanded := make([]string, 0, len(args)) + for _, arg := range args { + switch arg { + case "-it", "-ti": + expanded = append(expanded, "-i", "-t") + default: + expanded = append(expanded, arg) + } + } + return expanded +} + // watchTerminalSize watches terminal size changes to propagate to remote tty. func watchTerminalSize(out io.Writer, resize chan<- api.TerminalSize) (func(), error) { fd, isTerminal := term.GetFdInfo(out) diff --git a/command/alloc_exec_test.go b/command/alloc_exec_test.go index c51243056f2..6b310b272fc 100644 --- a/command/alloc_exec_test.go +++ b/command/alloc_exec_test.go @@ -77,6 +77,11 @@ func TestAllocExecCommand_Fails(t *testing.T) { []string{"-address=" + url, "-e", "es", "26470238-5CF2-438F-8772-DC67CFB0705C", "/bin/bash"}, `-e requires 'none' or a single character`, }, + { + "-it with stdin disabled is rejected", + []string{"-it", "-i=false", "26470238-5CF2-438F-8772-DC67CFB0705C", "/bin/bash"}, + `-i must be enabled if running with tty`, + }, } for _, c := range cases { @@ -165,6 +170,49 @@ func TestAllocExecCommand_AutocompleteArgs(t *testing.T) { must.Eq(t, a.ID, res[0]) } +func TestExpandITFlags(t *testing.T) { + ci.Parallel(t) + + cases := []struct { + name string + input []string + expected []string + }{ + { + name: "no combined flags", + input: []string{"-i", "-t", "alloc", "/bin/sh"}, + expected: []string{"-i", "-t", "alloc", "/bin/sh"}, + }, + { + name: "-it expanded", + input: []string{"-it", "alloc", "/bin/sh"}, + expected: []string{"-i", "-t", "alloc", "/bin/sh"}, + }, + { + name: "-ti expanded", + input: []string{"-ti", "alloc", "/bin/sh"}, + expected: []string{"-i", "-t", "alloc", "/bin/sh"}, + }, + { + name: "-it with other flags", + input: []string{"-address=http://localhost", "-it", "alloc", "/bin/sh"}, + expected: []string{"-address=http://localhost", "-i", "-t", "alloc", "/bin/sh"}, + }, + { + name: "empty args", + input: []string{}, + expected: []string{}, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + result := expandITFlags(c.input) + must.Eq(t, c.expected, result) + }) + } +} + func TestAllocExecCommand_Run(t *testing.T) { ci.Parallel(t) srv, client, url := testServer(t, true, nil)