Skip to content

Commit 14d4e2c

Browse files
committed
feat(run): allow -i and -d together when -t is set
Previously `nerdctl run` rejected -i with -d unconditionally via a FIXME error. The combination is valid when -t is also given: the containerd shim holds the pty open, so the detached container keeps a usable stdin. Without -t, a detached interactive container cannot work in nerdctl's daemonless model -- there is no persistent process to hold the container's stdin open, so the process would read EOF immediately. That case now returns a clear error instead of the FIXME. Add integration tests for the accepted (-t -d -i) and rejected (-d -i) cases. Signed-off-by: Mayur Das <mayur.das@neevcloud.com>
1 parent dbb53e9 commit 14d4e2c

2 files changed

Lines changed: 91 additions & 4 deletions

File tree

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package container
18+
19+
import (
20+
"errors"
21+
"strings"
22+
"testing"
23+
24+
"gotest.tools/v3/assert"
25+
26+
"github.com/containerd/nerdctl/mod/tigron/expect"
27+
"github.com/containerd/nerdctl/mod/tigron/require"
28+
"github.com/containerd/nerdctl/mod/tigron/test"
29+
"github.com/containerd/nerdctl/mod/tigron/tig"
30+
31+
"github.com/containerd/nerdctl/v2/pkg/testutil"
32+
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
33+
)
34+
35+
// TestRunDetachInteractiveWithTTY verifies that `run -t -d -i` is accepted and
36+
// starts a detached container that keeps running, matching Docker. The TTY's pty
37+
// is held by the shim, so the combination is valid.
38+
func TestRunDetachInteractiveWithTTY(t *testing.T) {
39+
testCase := nerdtest.Setup()
40+
41+
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
42+
helpers.Anyhow("rm", "-f", data.Identifier())
43+
}
44+
45+
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
46+
return helpers.Command("run", "-t", "-d", "-i", "--name", data.Identifier(),
47+
testutil.CommonImage, "sleep", "infinity")
48+
}
49+
50+
testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {
51+
return &test.Expected{
52+
ExitCode: 0,
53+
Output: func(stdout string, t tig.T) {
54+
assert.Assert(t, strings.Contains(
55+
helpers.Capture("inspect", "--format", "{{.State.Running}}", data.Identifier()), "true"))
56+
},
57+
}
58+
}
59+
60+
testCase.Run(t)
61+
}
62+
63+
// TestRunDetachInteractiveWithoutTTYFails verifies that `run -d -i` without -t is
64+
// rejected: being daemonless, nerdctl has no process to keep stdin open after
65+
// detaching. Docker (daemon-backed) supports it, so this is nerdctl-only.
66+
func TestRunDetachInteractiveWithoutTTYFails(t *testing.T) {
67+
testCase := nerdtest.Setup()
68+
testCase.Require = require.Not(nerdtest.Docker)
69+
70+
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
71+
helpers.Anyhow("rm", "-f", data.Identifier())
72+
}
73+
74+
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
75+
return helpers.Command("run", "-d", "-i", "--name", data.Identifier(), testutil.CommonImage, "cat")
76+
}
77+
78+
testCase.Expected = func(data test.Data, helpers test.Helpers) *test.Expected {
79+
return &test.Expected{
80+
ExitCode: expect.ExitCodeGenericFail,
81+
Errors: []error{errors.New("can only be specified together with -t")},
82+
}
83+
}
84+
85+
testCase.Run(t)
86+
}

pkg/cmd/container/create.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -253,10 +253,11 @@ func Create(ctx context.Context, client *containerd.Client, args []string, netMa
253253
return nil, generateRemoveStateDirFunc(ctx, id, internalLabels), err
254254
}
255255

256-
if options.Interactive {
257-
if options.Detach {
258-
return nil, generateRemoveStateDirFunc(ctx, id, internalLabels), errors.New("currently flag -i and -d cannot be specified together (FIXME)")
259-
}
256+
// -i with -d requires -t. Without a pty, nerdctl (being daemonless) has no
257+
// process to keep the container's stdin open after it detaches, so the
258+
// process would read EOF immediately; with -t the shim holds the pty open.
259+
if options.Interactive && options.Detach && !options.TTY {
260+
return nil, generateRemoveStateDirFunc(ctx, id, internalLabels), errors.New("flags -i and -d can only be specified together with -t")
260261
}
261262

262263
if options.TTY {

0 commit comments

Comments
 (0)