LinuxContainer: Move Process type out of Configuration (#340)

As a first step to making a pod type, move the nested process
configuration out of LinuxContainer.Configuration.Process and into a
standalone LinuxProcessConfiguration type.
This commit is contained in:
Danny Canter
2025-10-20 10:52:48 -07:00
committed by GitHub
parent 26dcd68340
commit 4f996d3509
4 changed files with 108 additions and 71 deletions
+4 -65
View File
@@ -27,9 +27,6 @@ import struct ContainerizationOS.Terminal
/// `LinuxContainer` is an easy to use type for launching and managing the
/// full lifecycle of a Linux container ran inside of a virtual machine.
public final class LinuxContainer: Container, Sendable {
/// The default PATH value for a process.
public static let defaultPath = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
/// The identifier of the container.
public let id: String
@@ -41,66 +38,8 @@ public final class LinuxContainer: Container, Sendable {
/// The configuration for the LinuxContainer.
public struct Configuration: Sendable {
/// Configuration of a container process.
public struct Process: Sendable {
/// The arguments for the container process.
public var arguments: [String] = []
/// The environment variables for the container process.
public var environmentVariables: [String] = ["PATH=\(LinuxContainer.defaultPath)"]
/// The working directory for the container process.
public var workingDirectory: String = "/"
/// The user the container process will run as.
public var user: ContainerizationOCI.User = .init()
/// The rlimits for the container process.
public var rlimits: [POSIXRlimit] = []
/// Whether to allocate a pseudo terminal for the process. If you'd like interactive
/// behavior and are planning to use a terminal for stdin/out/err on the client side,
/// this should likely be set to true.
public var terminal: Bool = false
/// The stdin for the process.
public var stdin: ReaderStream?
/// The stdout for the process.
public var stdout: Writer?
/// The stderr for the process.
public var stderr: Writer?
public init() {}
public init(from config: ImageConfig) {
self.workingDirectory = config.workingDir ?? "/"
self.environmentVariables = config.env ?? []
self.arguments = (config.entrypoint ?? []) + (config.cmd ?? [])
self.user = {
if let rawString = config.user {
return User(username: rawString)
}
return User()
}()
}
func toOCI() -> ContainerizationOCI.Process {
ContainerizationOCI.Process(
args: self.arguments,
cwd: self.workingDirectory,
env: self.environmentVariables,
user: self.user,
rlimits: self.rlimits,
terminal: self.terminal
)
}
/// Sets up IO to be handled by the passed in Terminal, and edits the
/// process configuration to set the necessary state for using a pty.
mutating public func setTerminalIO(terminal: Terminal) {
self.environmentVariables.append("TERM=xterm")
self.terminal = true
self.stdin = terminal
self.stdout = terminal
}
}
/// Configuration for the init process of the container.
public var process = Process.init()
public var process = LinuxProcessConfiguration.init()
/// The amount of cpus for the container.
public var cpus: Int = 4
/// The memory in bytes to give to the container.
@@ -615,12 +554,12 @@ extension LinuxContainer {
/// Execute a new process in the container. The process is not started after this call, and must be manually started
/// via the `start` method.
public func exec(_ id: String, configuration: @Sendable @escaping (inout Configuration.Process) throws -> Void) async throws -> LinuxProcess {
public func exec(_ id: String, configuration: @Sendable @escaping (inout LinuxProcessConfiguration) throws -> Void) async throws -> LinuxProcess {
try await self.state.withLock {
let state = try $0.startedState("exec")
var spec = self.generateRuntimeSpec()
var config = Configuration.Process()
var config = LinuxProcessConfiguration()
try configuration(&config)
spec.process = config.toOCI()
@@ -646,7 +585,7 @@ extension LinuxContainer {
/// Execute a new process in the container. The process is not started after this call, and must be manually started
/// via the `start` method.
public func exec(_ id: String, configuration: Configuration.Process) async throws -> LinuxProcess {
public func exec(_ id: String, configuration: LinuxProcessConfiguration) async throws -> LinuxProcess {
try await self.state.withLock {
let state = try $0.startedState("exec")
@@ -0,0 +1,100 @@
//===----------------------------------------------------------------------===//
// Copyright © 2025 Apple Inc. and the Containerization project authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//===----------------------------------------------------------------------===//
import ContainerizationOCI
import ContainerizationOS
public struct LinuxProcessConfiguration: Sendable {
/// The default PATH value for a process.
public static let defaultPath = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
/// The arguments for the container process.
public var arguments: [String] = []
/// The environment variables for the container process.
public var environmentVariables: [String] = ["PATH=\(Self.defaultPath)"]
/// The working directory for the container process.
public var workingDirectory: String = "/"
/// The user the container process will run as.
public var user: ContainerizationOCI.User = .init()
/// The rlimits for the container process.
public var rlimits: [POSIXRlimit] = []
/// Whether to allocate a pseudo terminal for the process. If you'd like interactive
/// behavior and are planning to use a terminal for stdin/out/err on the client side,
/// this should likely be set to true.
public var terminal: Bool = false
/// The stdin for the process.
public var stdin: ReaderStream?
/// The stdout for the process.
public var stdout: Writer?
/// The stderr for the process.
public var stderr: Writer?
public init() {}
public init(
arguments: [String],
environmentVariables: [String] = ["PATH=\(Self.defaultPath)"],
workingDirectory: String = "/",
user: ContainerizationOCI.User = .init(),
rlimits: [POSIXRlimit] = [],
terminal: Bool = false,
stdin: ReaderStream? = nil,
stdout: Writer? = nil,
stderr: Writer? = nil
) {
self.arguments = arguments
self.environmentVariables = environmentVariables
self.workingDirectory = workingDirectory
self.user = user
self.rlimits = rlimits
self.terminal = terminal
self.stdin = stdin
self.stdout = stdout
self.stderr = stderr
}
public init(from config: ImageConfig) {
self.workingDirectory = config.workingDir ?? "/"
self.environmentVariables = config.env ?? []
self.arguments = (config.entrypoint ?? []) + (config.cmd ?? [])
self.user = {
if let rawString = config.user {
return User(username: rawString)
}
return User()
}()
}
/// Sets up IO to be handled by the passed in Terminal, and edits the
/// process configuration to set the necessary state for using a pty.
mutating public func setTerminalIO(terminal: Terminal) {
self.environmentVariables.append("TERM=xterm")
self.terminal = true
self.stdin = terminal
self.stdout = terminal
}
func toOCI() -> ContainerizationOCI.Process {
ContainerizationOCI.Process(
args: self.arguments,
cwd: self.workingDirectory,
env: self.environmentVariables,
user: self.user,
rlimits: self.rlimits,
terminal: self.terminal
)
}
}
+1 -3
View File
@@ -29,8 +29,6 @@ public struct Vminitd: Sendable {
// Default vsock port that the agent and client use.
public static let port: UInt32 = 1024
private static let defaultPath = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
let client: Client
public init(client: Client) {
@@ -53,7 +51,7 @@ extension Vminitd: VirtualMachineAgent {
public func standardSetup() async throws {
try await up(name: "lo")
try await setenv(key: "PATH", value: Self.defaultPath)
try await setenv(key: "PATH", value: LinuxProcessConfiguration.defaultPath)
let mounts: [ContainerizationOCI.Mount] = [
.init(type: "sysfs", source: "sysfs", destination: "/sys"),
@@ -31,7 +31,7 @@ struct LinuxContainerTests {
workingDir: "/app"
)
let process = LinuxContainer.Configuration.Process(from: imageConfig)
let process = LinuxProcessConfiguration(from: imageConfig)
#expect(process.workingDirectory == "/app")
#expect(process.environmentVariables == ["NODE_ENV=production", "PORT=3000"])
@@ -48,7 +48,7 @@ struct LinuxContainerTests {
workingDir: nil
)
let process = LinuxContainer.Configuration.Process(from: imageConfig)
let process = LinuxProcessConfiguration(from: imageConfig)
#expect(process.workingDirectory == "/")
#expect(process.environmentVariables == [])
@@ -62,7 +62,7 @@ struct LinuxContainerTests {
cmd: ["echo 'hello'", "&&", "sleep 10"]
)
let process = LinuxContainer.Configuration.Process(from: imageConfig)
let process = LinuxProcessConfiguration(from: imageConfig)
#expect(process.arguments == ["/bin/sh", "-c", "echo 'hello'", "&&", "sleep 10"])
}