Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions Sources/Containerization/LinuxContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ public final class LinuxContainer: Container, Sendable {
public var virtualization: Bool = false
/// Optional destination for serial boot logs.
public var bootLog: BootLog?
/// EXPERIMENTAL: Path in the root filesystem for the virtual
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the semantics such that if defined, this overrides using the default runtime offered by the VMM? It might be good to spell this out in this docc.

/// machine where the OCI runtime used to spawn the container lives.
public var ociRuntimePath: String?

public init() {}

Expand All @@ -77,7 +80,8 @@ public final class LinuxContainer: Container, Sendable {
dns: DNS? = nil,
hosts: Hosts? = nil,
virtualization: Bool = false,
bootLog: BootLog? = nil
bootLog: BootLog? = nil,
ociRuntimePath: String? = nil
) {
self.process = process
self.cpus = cpus
Expand All @@ -91,6 +95,7 @@ public final class LinuxContainer: Container, Sendable {
self.hosts = hosts
self.virtualization = virtualization
self.bootLog = bootLog
self.ociRuntimePath = ociRuntimePath
}
}

Expand Down Expand Up @@ -317,19 +322,42 @@ public final class LinuxContainer: Container, Sendable {
)
)

spec.linux?.namespaces = [
LinuxNamespace(type: .cgroup),
LinuxNamespace(type: .ipc),
LinuxNamespace(type: .mount),
LinuxNamespace(type: .pid),
LinuxNamespace(type: .uts),
]

return spec
}

/// The default set of mounts for a LinuxContainer.
public static func defaultMounts() -> [Mount] {
let defaultOptions = ["nosuid", "noexec", "nodev"]
return [
.any(type: "proc", source: "proc", destination: "/proc", options: defaultOptions),
.any(type: "proc", source: "proc", destination: "/proc"),
.any(type: "sysfs", source: "sysfs", destination: "/sys", options: defaultOptions),
.any(type: "devtmpfs", source: "none", destination: "/dev", options: ["nosuid", "mode=755"]),
.any(type: "mqueue", source: "mqueue", destination: "/dev/mqueue", options: defaultOptions),
.any(type: "tmpfs", source: "tmpfs", destination: "/dev/shm", options: defaultOptions + ["mode=1777", "size=65536k"]),
.any(type: "cgroup2", source: "none", destination: "/sys/fs/cgroup", options: defaultOptions),
.any(type: "devpts", source: "devpts", destination: "/dev/pts", options: ["nosuid", "noexec", "gid=5", "mode=620", "ptmxmode=666"]),
.any(type: "devpts", source: "devpts", destination: "/dev/pts", options: ["nosuid", "noexec", "newinstance", "gid=5", "mode=0620", "ptmxmode=0666"]),
]
}

/// A more traditional default set of mounts that OCI runtimes typically employ.
public static func defaultOCIMounts() -> [Mount] {
let defaultOptions = ["nosuid", "noexec", "nodev"]
return [
.any(type: "proc", source: "proc", destination: "/proc"),
.any(type: "tmpfs", source: "tmpfs", destination: "/dev", options: ["nosuid", "mode=755", "size=65536k"]),
.any(type: "devpts", source: "devpts", destination: "/dev/pts", options: ["nosuid", "noexec", "newinstance", "gid=5", "mode=0620", "ptmxmode=0666"]),
.any(type: "sysfs", source: "sysfs", destination: "/sys", options: defaultOptions),
.any(type: "mqueue", source: "mqueue", destination: "/dev/mqueue", options: defaultOptions),
.any(type: "tmpfs", source: "tmpfs", destination: "/dev/shm", options: defaultOptions + ["mode=1777", "size=65536k"]),
.any(type: "cgroup2", source: "none", destination: "/sys/fs/cgroup", options: defaultOptions),
]
}

Expand Down Expand Up @@ -456,6 +484,7 @@ extension LinuxContainer {
containerID: self.id,
spec: spec,
io: stdio,
ociRuntimePath: self.config.ociRuntimePath,
agent: agent,
vm: createdState.vm,
logger: self.logger
Expand Down Expand Up @@ -657,6 +686,7 @@ extension LinuxContainer {
containerID: self.id,
spec: spec,
io: stdio,
ociRuntimePath: self.config.ociRuntimePath,
agent: agent,
vm: startedState.vm,
logger: self.logger,
Expand Down Expand Up @@ -693,6 +723,7 @@ extension LinuxContainer {
containerID: self.id,
spec: spec,
io: stdio,
ociRuntimePath: self.config.ociRuntimePath,
agent: agent,
vm: state.vm,
logger: self.logger,
Expand Down
2 changes: 2 additions & 0 deletions Sources/Containerization/LinuxPod.swift
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,7 @@ extension LinuxPod {
containerID: containerID,
spec: spec,
io: stdio,
ociRuntimePath: nil,
agent: agent,
vm: createdState.vm,
logger: self.logger
Expand Down Expand Up @@ -613,6 +614,7 @@ extension LinuxPod {
containerID: containerID,
spec: spec,
io: stdio,
ociRuntimePath: nil,
agent: agent,
vm: createdState.vm,
logger: self.logger
Expand Down
4 changes: 4 additions & 0 deletions Sources/Containerization/LinuxProcess.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ public final class LinuxProcess: Sendable {
private let ioSetup: Stdio
private let agent: any VirtualMachineAgent
private let vm: any VirtualMachineInstance
private let ociRuntimePath: String?
private let logger: Logger?
private let onDelete: (@Sendable () async -> Void)?

Expand All @@ -104,6 +105,7 @@ public final class LinuxProcess: Sendable {
containerID: String? = nil,
spec: Spec,
io: Stdio,
ociRuntimePath: String?,
agent: any VirtualMachineAgent,
vm: any VirtualMachineInstance,
logger: Logger?,
Expand All @@ -114,6 +116,7 @@ public final class LinuxProcess: Sendable {
self.state = Mutex<State>(.init(spec: spec, pid: -1, stdio: StdioHandles()))
self.ioSetup = io
self.agent = agent
self.ociRuntimePath = ociRuntimePath
self.vm = vm
self.logger = logger
self.onDelete = onDelete
Expand Down Expand Up @@ -260,6 +263,7 @@ extension LinuxProcess {
stdinPort: self.ioSetup.stdin?.port,
stdoutPort: self.ioSetup.stdout?.port,
stderrPort: self.ioSetup.stderr?.port,
ociRuntimePath: self.ociRuntimePath,
configuration: spec,
options: nil
)
Expand Down
28 changes: 22 additions & 6 deletions Sources/Containerization/SandboxContext/SandboxContext.pb.swift
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,15 @@ public struct Com_Apple_Containerization_Sandbox_V3_CreateProcessRequest: @unche
/// Clears the value of `stderr`. Subsequent reads from it will return its default value.
public mutating func clearStderr() {self._stderr = nil}

public var ociRuntimePath: String {
get {return _ociRuntimePath ?? String()}
set {_ociRuntimePath = newValue}
}
/// Returns true if `ociRuntimePath` has been explicitly set.
public var hasOciRuntimePath: Bool {return self._ociRuntimePath != nil}
/// Clears the value of `ociRuntimePath`. Subsequent reads from it will return its default value.
public mutating func clearOciRuntimePath() {self._ociRuntimePath = nil}

public var configuration: Data = Data()

public var options: Data {
Expand All @@ -433,6 +442,7 @@ public struct Com_Apple_Containerization_Sandbox_V3_CreateProcessRequest: @unche
fileprivate var _stdin: UInt32? = nil
fileprivate var _stdout: UInt32? = nil
fileprivate var _stderr: UInt32? = nil
fileprivate var _ociRuntimePath: String? = nil
fileprivate var _options: Data? = nil
}

Expand Down Expand Up @@ -1853,8 +1863,9 @@ extension Com_Apple_Containerization_Sandbox_V3_CreateProcessRequest: SwiftProto
3: .same(proto: "stdin"),
4: .same(proto: "stdout"),
5: .same(proto: "stderr"),
6: .same(proto: "configuration"),
7: .same(proto: "options"),
6: .same(proto: "ociRuntimePath"),
7: .same(proto: "configuration"),
8: .same(proto: "options"),
]

public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
Expand All @@ -1868,8 +1879,9 @@ extension Com_Apple_Containerization_Sandbox_V3_CreateProcessRequest: SwiftProto
case 3: try { try decoder.decodeSingularUInt32Field(value: &self._stdin) }()
case 4: try { try decoder.decodeSingularUInt32Field(value: &self._stdout) }()
case 5: try { try decoder.decodeSingularUInt32Field(value: &self._stderr) }()
case 6: try { try decoder.decodeSingularBytesField(value: &self.configuration) }()
case 7: try { try decoder.decodeSingularBytesField(value: &self._options) }()
case 6: try { try decoder.decodeSingularStringField(value: &self._ociRuntimePath) }()
case 7: try { try decoder.decodeSingularBytesField(value: &self.configuration) }()
case 8: try { try decoder.decodeSingularBytesField(value: &self._options) }()
default: break
}
}
Expand All @@ -1895,11 +1907,14 @@ extension Com_Apple_Containerization_Sandbox_V3_CreateProcessRequest: SwiftProto
try { if let v = self._stderr {
try visitor.visitSingularUInt32Field(value: v, fieldNumber: 5)
} }()
try { if let v = self._ociRuntimePath {
try visitor.visitSingularStringField(value: v, fieldNumber: 6)
} }()
if !self.configuration.isEmpty {
try visitor.visitSingularBytesField(value: self.configuration, fieldNumber: 6)
try visitor.visitSingularBytesField(value: self.configuration, fieldNumber: 7)
}
try { if let v = self._options {
try visitor.visitSingularBytesField(value: v, fieldNumber: 7)
try visitor.visitSingularBytesField(value: v, fieldNumber: 8)
} }()
try unknownFields.traverse(visitor: &visitor)
}
Expand All @@ -1910,6 +1925,7 @@ extension Com_Apple_Containerization_Sandbox_V3_CreateProcessRequest: SwiftProto
if lhs._stdin != rhs._stdin {return false}
if lhs._stdout != rhs._stdout {return false}
if lhs._stderr != rhs._stderr {return false}
if lhs._ociRuntimePath != rhs._ociRuntimePath {return false}
if lhs.configuration != rhs.configuration {return false}
if lhs._options != rhs._options {return false}
if lhs.unknownFields != rhs.unknownFields {return false}
Expand Down
5 changes: 3 additions & 2 deletions Sources/Containerization/SandboxContext/SandboxContext.proto
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,9 @@ message CreateProcessRequest {
optional uint32 stdin = 3;
optional uint32 stdout = 4;
optional uint32 stderr = 5;
bytes configuration = 6;
optional bytes options = 7;
optional string ociRuntimePath = 6;
bytes configuration = 7;
optional bytes options = 8;
}

message CreateProcessResponse {}
Expand Down
1 change: 1 addition & 0 deletions Sources/Containerization/VirtualMachineAgent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public protocol VirtualMachineAgent: Sendable {
stdinPort: UInt32?,
stdoutPort: UInt32?,
stderrPort: UInt32?,
ociRuntimePath: String?,
configuration: ContainerizationOCI.Spec,
options: Data?
) async throws
Expand Down
4 changes: 4 additions & 0 deletions Sources/Containerization/Vminitd.swift
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ extension Vminitd: VirtualMachineAgent {
stdinPort: UInt32?,
stdoutPort: UInt32?,
stderrPort: UInt32?,
ociRuntimePath: String?,
configuration: ContainerizationOCI.Spec,
options: Data?
) async throws {
Expand All @@ -204,6 +205,9 @@ extension Vminitd: VirtualMachineAgent {
if let containerID {
$0.containerID = containerID
}
if let ociRuntimePath {
$0.ociRuntimePath = ociRuntimePath
}
$0.configuration = try enc.encode(configuration)
})
}
Expand Down
22 changes: 16 additions & 6 deletions Sources/ContainerizationOCI/Spec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -296,8 +296,8 @@ public struct Mount: Codable, Sendable {
public var destination: String
public var options: [String]

public var uidMappings: [LinuxIDMapping]
public var gidMappings: [LinuxIDMapping]
public var uidMappings: [LinuxIDMapping]?
public var gidMappings: [LinuxIDMapping]?

public enum CodingKeys: String, CodingKey {
case type
Expand All @@ -313,8 +313,8 @@ public struct Mount: Codable, Sendable {
source: String = "",
destination: String,
options: [String] = [],
uidMappings: [LinuxIDMapping] = [],
gidMappings: [LinuxIDMapping] = []
uidMappings: [LinuxIDMapping]? = nil,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side note: In case we ever want to take a crack at generating Swift from spec, https://github.pie.apple.com/vessel/swift-docker-api demonstrates an approach for converting Open API/Swagger to Swift bindings with some stuff to handle things that are optional in practice but required according to what the spec says.

gidMappings: [LinuxIDMapping]? = nil
) {
self.destination = destination
self.type = type
Expand All @@ -330,8 +330,18 @@ public struct Mount: Codable, Sendable {
self.source = try container.decodeIfPresent(String.self, forKey: .source) ?? ""
self.destination = try container.decode(String.self, forKey: .destination)
self.options = try container.decodeIfPresent([String].self, forKey: .options) ?? []
self.uidMappings = try container.decodeIfPresent([LinuxIDMapping].self, forKey: .uidMappings) ?? []
self.gidMappings = try container.decodeIfPresent([LinuxIDMapping].self, forKey: .gidMappings) ?? []
self.uidMappings = try container.decodeIfPresent([LinuxIDMapping].self, forKey: .uidMappings)
self.gidMappings = try container.decodeIfPresent([LinuxIDMapping].self, forKey: .gidMappings)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(type, forKey: .type)
try container.encode(source, forKey: .source)
try container.encode(destination, forKey: .destination)
try container.encode(options, forKey: .options)
try container.encodeIfPresent(uidMappings, forKey: .uidMappings)
try container.encodeIfPresent(gidMappings, forKey: .gidMappings)
}
}

Expand Down
3 changes: 2 additions & 1 deletion Sources/ContainerizationOS/Socket/Socket.swift
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,8 @@ extension Socket {
return Socket(
fd: clientFD,
type: newSocketType,
closeOnDeinit: closeOnDeinit
closeOnDeinit: closeOnDeinit,
connected: true
)
}

Expand Down
12 changes: 12 additions & 0 deletions Sources/cctl/RootfsCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ extension Application {
@Option(name: .long, help: "Path to vminitd")
var vminitd: String

@Option(name: .long, help: "Path to OCI runtime")
var ociRuntime: String?

// The path where the intermediate tar archive is created.
@Argument var tarPath: String

Expand Down Expand Up @@ -144,6 +147,15 @@ extension Application {
entry.size = Int64(data.count)
try writer.writeEntry(entry: entry, data: data)

if let ociRuntimePath = self.ociRuntime {
src = URL(fileURLWithPath: ociRuntimePath)
let fileName = src.lastPathComponent
data = try Data(contentsOf: src)
entry.path = "sbin/\(fileName)"
entry.size = Int64(data.count)
try writer.writeEntry(entry: entry, data: data)
}

for addFile in addFiles {
let paths = addFile.components(separatedBy: ":")
guard paths.count == 2 else {
Expand Down
7 changes: 7 additions & 0 deletions Sources/cctl/RunCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ extension Application {
@Option(name: .customLong("ns"), help: "Nameserver addresses")
var nameservers: [String] = []

@Option(name: .long, help: "Path to OCI runtime to use for spawning the container")
var ociRuntimePath: String?

@Option(
name: [.customLong("kernel"), .customShort("k")], help: "Kernel binary path", completion: .file(),
transform: { str in
Expand Down Expand Up @@ -132,6 +135,10 @@ extension Application {
))
}
config.hosts = hosts
if let ociRuntimePath {
config.ociRuntimePath = ociRuntimePath
config.mounts = LinuxContainer.defaultOCIMounts()
}
}

defer {
Expand Down
4 changes: 2 additions & 2 deletions Tests/ContainerizationOCITests/OCISpecTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ struct OCISpecTests {
#expect(decodedSpec.source == "")
#expect(decodedSpec.destination == destination)
#expect(decodedSpec.options.isEmpty)
#expect(decodedSpec.uidMappings.isEmpty)
#expect(decodedSpec.gidMappings.isEmpty)
#expect(decodedSpec.uidMappings == nil)
#expect(decodedSpec.gidMappings == nil)
}
}
Loading