mirror of
https://github.com/apple/container.git
synced 2026-09-24 08:35:36 +00:00
@@ -34,6 +34,9 @@ extension Application {
|
||||
@OptionGroup
|
||||
var global: Flags.Global
|
||||
|
||||
@Flag(name: .shortAndLong, help: "Run the process and detach from it")
|
||||
var detach = false
|
||||
|
||||
@Argument(help: "Container ID")
|
||||
var containerId: String
|
||||
|
||||
@@ -71,11 +74,24 @@ extension Application {
|
||||
config.supplementalGroups.append(contentsOf: additionalGroups)
|
||||
|
||||
do {
|
||||
let io = try ProcessIO.create(tty: tty, interactive: stdin, detach: false)
|
||||
let io = try ProcessIO.create(tty: tty, interactive: stdin, detach: self.detach)
|
||||
defer {
|
||||
try? io.close()
|
||||
}
|
||||
|
||||
let process = try await container.createProcess(
|
||||
id: UUID().uuidString.lowercased(),
|
||||
configuration: config,
|
||||
stdio: io.stdio
|
||||
)
|
||||
|
||||
if self.detach {
|
||||
try await process.start()
|
||||
try io.closeAfterStart()
|
||||
print(containerId)
|
||||
return
|
||||
}
|
||||
|
||||
if !self.processFlags.tty {
|
||||
var handler = SignalThreshold(threshold: 3, signals: [SIGINT, SIGTERM])
|
||||
handler.start {
|
||||
@@ -84,12 +100,6 @@ extension Application {
|
||||
}
|
||||
}
|
||||
|
||||
let process = try await container.createProcess(
|
||||
id: UUID().uuidString.lowercased(),
|
||||
configuration: config,
|
||||
stdio: io.stdio
|
||||
)
|
||||
|
||||
exitCode = try await io.handleProcess(process: process, log: log)
|
||||
} catch {
|
||||
if error is ContainerizationError {
|
||||
|
||||
@@ -39,4 +39,72 @@ class TestCLIExecCommand: CLITest {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@Test func testExecDetach() throws {
|
||||
do {
|
||||
let name = getTestName()
|
||||
try doCreate(name: name)
|
||||
defer {
|
||||
try? doStop(name: name)
|
||||
}
|
||||
try doStart(name: name)
|
||||
|
||||
// Run a long-running process in detached mode
|
||||
let output = try doExec(name: name, cmd: ["sh", "-c", "touch /tmp/detach_test_marker"], detach: true)
|
||||
let containerIdOutput = output.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
try #require(containerIdOutput == name, "exec --detach should print the container ID")
|
||||
|
||||
// Verify the detached process is running by checking if we can still exec commands
|
||||
var lsActual = try doExec(name: name, cmd: ["ls", "/"])
|
||||
lsActual = lsActual.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
try #require(lsActual.contains("tmp"), "container should still be running and accepting exec commands")
|
||||
|
||||
// Retry loop to check if the marker file was created by the detached process
|
||||
var markerFound = false
|
||||
for _ in 0..<3 {
|
||||
let (_, _, status) = try run(arguments: [
|
||||
"exec",
|
||||
name,
|
||||
"test", "-f", "/tmp/detach_test_marker",
|
||||
])
|
||||
if status == 0 {
|
||||
markerFound = true
|
||||
break
|
||||
}
|
||||
sleep(1)
|
||||
}
|
||||
try #require(markerFound, "marker file should be created by detached process within 3 seconds")
|
||||
|
||||
try doStop(name: name)
|
||||
} catch {
|
||||
Issue.record("failed to exec with detach in container \(error)")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@Test func testExecDetachProcessRunning() throws {
|
||||
do {
|
||||
let name = getTestName()
|
||||
try doCreate(name: name)
|
||||
defer {
|
||||
try? doStop(name: name)
|
||||
}
|
||||
try doStart(name: name)
|
||||
|
||||
// Run a long-running process in detached mode
|
||||
let output = try doExec(name: name, cmd: ["sleep", "10"], detach: true)
|
||||
let containerIdOutput = output.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
try #require(containerIdOutput == name, "exec --detach should print the container ID")
|
||||
|
||||
// Immediately check if the process is running using ps
|
||||
var psOutput = try doExec(name: name, cmd: ["ps", "aux"])
|
||||
psOutput = psOutput.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
try #require(psOutput.contains("sleep 10"), "detached process 'sleep 10' should be visible in ps output")
|
||||
|
||||
try doStop(name: name)
|
||||
} catch {
|
||||
Issue.record("failed to verify detached process is running \(error)")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,11 +226,14 @@ class CLITest {
|
||||
}
|
||||
}
|
||||
|
||||
func doExec(name: String, cmd: [String]) throws -> String {
|
||||
func doExec(name: String, cmd: [String], detach: Bool = false) throws -> String {
|
||||
var execArgs = [
|
||||
"exec",
|
||||
name,
|
||||
"exec"
|
||||
]
|
||||
if detach {
|
||||
execArgs.append("-d")
|
||||
}
|
||||
execArgs.append(name)
|
||||
execArgs.append(contentsOf: cmd)
|
||||
let (resp, error, status) = try run(arguments: execArgs)
|
||||
if status != 0 {
|
||||
|
||||
@@ -313,7 +313,7 @@ Executes a command inside a running container. It uses the same process flags as
|
||||
**Usage**
|
||||
|
||||
```bash
|
||||
container exec [--env <env> ...] [--env-file <env-file> ...] [--gid <gid>] [--interactive] [--tty] [--user <user>] [--uid <uid>] [--workdir <dir>] [--debug] <container-id> <arguments> ...
|
||||
container exec [--detach] [--env <env> ...] [--env-file <env-file> ...] [--gid <gid>] [--interactive] [--tty] [--user <user>] [--uid <uid>] [--workdir <dir>] [--debug] <container-id> <arguments> ...
|
||||
```
|
||||
|
||||
**Arguments**
|
||||
@@ -321,6 +321,10 @@ container exec [--env <env> ...] [--env-file <env-file> ...] [--gid <gid>] [--in
|
||||
* `<container-id>`: Container ID
|
||||
* `<arguments>`: New process arguments
|
||||
|
||||
**Options**
|
||||
|
||||
* `-d, --detach`: Run the process and detach from it
|
||||
|
||||
**Process Options**
|
||||
|
||||
* `-e, --env <env>`: Set environment variables (format: key=value)
|
||||
|
||||
Reference in New Issue
Block a user