diff --git a/Sources/ContainerCommands/Container/ContainerExec.swift b/Sources/ContainerCommands/Container/ContainerExec.swift index f8d03553d..45bed35a0 100644 --- a/Sources/ContainerCommands/Container/ContainerExec.swift +++ b/Sources/ContainerCommands/Container/ContainerExec.swift @@ -77,6 +77,9 @@ extension Application { gid: processFlags.gid, defaultUser: defaultUser) config.user = user config.supplementalGroups.append(contentsOf: additionalGroups) + if !self.processFlags.ulimits.isEmpty { + config.rlimits = try Parser.rlimits(self.processFlags.ulimits) + } do { let io = try ProcessIO.create(tty: tty, interactive: stdin, detach: self.detach) diff --git a/Sources/ContainerCommands/Machine/MachineRun.swift b/Sources/ContainerCommands/Machine/MachineRun.swift index dc62132ca..a02d0bb63 100644 --- a/Sources/ContainerCommands/Machine/MachineRun.swift +++ b/Sources/ContainerCommands/Machine/MachineRun.swift @@ -111,7 +111,8 @@ extension Application { workingDirectory: cwd, terminal: tty, user: user, - supplementalGroups: additionalGroups + supplementalGroups: additionalGroups, + rlimits: try Parser.rlimits(processFlags.ulimits) ) let io = try ProcessIO.create(tty: tty, interactive: interactive, detach: detach) diff --git a/Tests/IntegrationTests/Containers/TestCLIExecCommand.swift b/Tests/IntegrationTests/Containers/TestCLIExecCommand.swift index 2ecdffeff..e97628597 100644 --- a/Tests/IntegrationTests/Containers/TestCLIExecCommand.swift +++ b/Tests/IntegrationTests/Containers/TestCLIExecCommand.swift @@ -114,4 +114,21 @@ struct TestCLIExecCommand { _ = try f.getContainerStatus(name) } } + + @Test func testExecUlimitNofile() async throws { + try await ContainerFixture.with { f in + let image = WarmupImage.alpine320.rawValue + let name = "\(f.testID)-c" + try f.doCreate(name: name, image: image) + f.addCleanup { try? f.doStop(name) } + try f.doStart(name) + try await f.waitForContainerRunning(name) + + let nofile = try f.run(["exec", "--ulimit", "nofile=1024:2048", name, "sh", "-c", "ulimit -n"]) + .check().output + .trimmingCharacters(in: .whitespacesAndNewlines) + #expect(nofile == "1024", "expected exec --ulimit to set the nofile soft limit, got \(nofile)") + try f.doStop(name) + } + } } diff --git a/docs/command-reference.md b/docs/command-reference.md index a99ac8528..637d0210e 100644 --- a/docs/command-reference.md +++ b/docs/command-reference.md @@ -361,7 +361,7 @@ Executes a command inside a running container. It uses the same process flags as **Usage** ```bash -container exec [--detach] [--env ...] [--env-file ...] [--gid ] [--interactive] [--tty] [--user ] [--uid ] [--workdir ] [--debug] ... +container exec [--detach] [--env ...] [--env-file ...] [--gid ] [--interactive] [--tty] [--user ] [--uid ] [--ulimit ] [--workdir ] [--debug] ... ``` **Arguments** @@ -382,6 +382,7 @@ container exec [--detach] [--env ...] [--env-file ...] [--gid < * `-t, --tty`: Open a TTY with the process * `-u, --user `: Set the user for the process (format: name|uid[:gid]) * `--uid `: Set the user ID for the process +* `--ulimit `: Set resource limits (format: `=[:]`) * `-w, --workdir, --cwd `: Set the initial working directory inside the container ### `container export` @@ -1168,6 +1169,7 @@ container machine run [] [] [ ...] * `-t, --tty`: Open a TTY with the process * `-u, --user `: Set the user for the process (format: name|uid[:gid]) * `--uid `: Set the user ID for the process +* `--ulimit `: Set resource limits (format: `=[:]`) * `-w, --workdir, --cwd `: Set the initial working directory inside the container **Examples**