Skip to content

Commit 5f57703

Browse files
committed
Wire up experimental OCI runtime support
This allows you to pass a path to an OCI runtime to use to launch a container instead of the default vmexec.
1 parent 747ea99 commit 5f57703

File tree

19 files changed

+779
-71
lines changed

19 files changed

+779
-71
lines changed

Sources/Containerization/LinuxContainer.swift

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ public final class LinuxContainer: Container, Sendable {
6262
public var virtualization: Bool = false
6363
/// Optional destination for serial boot logs.
6464
public var bootLog: BootLog?
65+
/// EXPERIMENTAL: Path in the root filesystem for the virtual
66+
/// machine where the OCI runtime used to spawn the container lives.
67+
public var ociRuntimePath: String?
6568

6669
public init() {}
6770

@@ -77,7 +80,8 @@ public final class LinuxContainer: Container, Sendable {
7780
dns: DNS? = nil,
7881
hosts: Hosts? = nil,
7982
virtualization: Bool = false,
80-
bootLog: BootLog? = nil
83+
bootLog: BootLog? = nil,
84+
ociRuntimePath: String? = nil
8185
) {
8286
self.process = process
8387
self.cpus = cpus
@@ -91,6 +95,7 @@ public final class LinuxContainer: Container, Sendable {
9195
self.hosts = hosts
9296
self.virtualization = virtualization
9397
self.bootLog = bootLog
98+
self.ociRuntimePath = ociRuntimePath
9499
}
95100
}
96101

@@ -317,19 +322,42 @@ public final class LinuxContainer: Container, Sendable {
317322
)
318323
)
319324

325+
spec.linux?.namespaces = [
326+
LinuxNamespace(type: .cgroup),
327+
LinuxNamespace(type: .ipc),
328+
LinuxNamespace(type: .mount),
329+
LinuxNamespace(type: .pid),
330+
LinuxNamespace(type: .uts),
331+
]
332+
320333
return spec
321334
}
322335

336+
/// The default set of mounts for a LinuxContainer.
323337
public static func defaultMounts() -> [Mount] {
324338
let defaultOptions = ["nosuid", "noexec", "nodev"]
325339
return [
326-
.any(type: "proc", source: "proc", destination: "/proc", options: defaultOptions),
340+
.any(type: "proc", source: "proc", destination: "/proc"),
327341
.any(type: "sysfs", source: "sysfs", destination: "/sys", options: defaultOptions),
328342
.any(type: "devtmpfs", source: "none", destination: "/dev", options: ["nosuid", "mode=755"]),
329343
.any(type: "mqueue", source: "mqueue", destination: "/dev/mqueue", options: defaultOptions),
330344
.any(type: "tmpfs", source: "tmpfs", destination: "/dev/shm", options: defaultOptions + ["mode=1777", "size=65536k"]),
331345
.any(type: "cgroup2", source: "none", destination: "/sys/fs/cgroup", options: defaultOptions),
332-
.any(type: "devpts", source: "devpts", destination: "/dev/pts", options: ["nosuid", "noexec", "gid=5", "mode=620", "ptmxmode=666"]),
346+
.any(type: "devpts", source: "devpts", destination: "/dev/pts", options: ["nosuid", "noexec", "newinstance", "gid=5", "mode=0620", "ptmxmode=0666"]),
347+
]
348+
}
349+
350+
/// A more traditional default set of mounts that OCI runtimes typically employ.
351+
public static func defaultOCIMounts() -> [Mount] {
352+
let defaultOptions = ["nosuid", "noexec", "nodev"]
353+
return [
354+
.any(type: "proc", source: "proc", destination: "/proc"),
355+
.any(type: "tmpfs", source: "tmpfs", destination: "/dev", options: ["nosuid", "mode=755", "size=65536k"]),
356+
.any(type: "devpts", source: "devpts", destination: "/dev/pts", options: ["nosuid", "noexec", "newinstance", "gid=5", "mode=0620", "ptmxmode=0666"]),
357+
.any(type: "sysfs", source: "sysfs", destination: "/sys", options: defaultOptions),
358+
.any(type: "mqueue", source: "mqueue", destination: "/dev/mqueue", options: defaultOptions),
359+
.any(type: "tmpfs", source: "tmpfs", destination: "/dev/shm", options: defaultOptions + ["mode=1777", "size=65536k"]),
360+
.any(type: "cgroup2", source: "none", destination: "/sys/fs/cgroup", options: defaultOptions),
333361
]
334362
}
335363

@@ -456,6 +484,7 @@ extension LinuxContainer {
456484
containerID: self.id,
457485
spec: spec,
458486
io: stdio,
487+
ociRuntimePath: self.config.ociRuntimePath,
459488
agent: agent,
460489
vm: createdState.vm,
461490
logger: self.logger
@@ -657,6 +686,7 @@ extension LinuxContainer {
657686
containerID: self.id,
658687
spec: spec,
659688
io: stdio,
689+
ociRuntimePath: self.config.ociRuntimePath,
660690
agent: agent,
661691
vm: startedState.vm,
662692
logger: self.logger,
@@ -693,6 +723,7 @@ extension LinuxContainer {
693723
containerID: self.id,
694724
spec: spec,
695725
io: stdio,
726+
ociRuntimePath: self.config.ociRuntimePath,
696727
agent: agent,
697728
vm: state.vm,
698729
logger: self.logger,

Sources/Containerization/LinuxPod.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,7 @@ extension LinuxPod {
406406
containerID: containerID,
407407
spec: spec,
408408
io: stdio,
409+
ociRuntimePath: nil,
409410
agent: agent,
410411
vm: createdState.vm,
411412
logger: self.logger
@@ -613,6 +614,7 @@ extension LinuxPod {
613614
containerID: containerID,
614615
spec: spec,
615616
io: stdio,
617+
ociRuntimePath: nil,
616618
agent: agent,
617619
vm: createdState.vm,
618620
logger: self.logger

Sources/Containerization/LinuxProcess.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ public final class LinuxProcess: Sendable {
9696
private let ioSetup: Stdio
9797
private let agent: any VirtualMachineAgent
9898
private let vm: any VirtualMachineInstance
99+
private let ociRuntimePath: String?
99100
private let logger: Logger?
100101
private let onDelete: (@Sendable () async -> Void)?
101102

@@ -104,6 +105,7 @@ public final class LinuxProcess: Sendable {
104105
containerID: String? = nil,
105106
spec: Spec,
106107
io: Stdio,
108+
ociRuntimePath: String?,
107109
agent: any VirtualMachineAgent,
108110
vm: any VirtualMachineInstance,
109111
logger: Logger?,
@@ -114,6 +116,7 @@ public final class LinuxProcess: Sendable {
114116
self.state = Mutex<State>(.init(spec: spec, pid: -1, stdio: StdioHandles()))
115117
self.ioSetup = io
116118
self.agent = agent
119+
self.ociRuntimePath = ociRuntimePath
117120
self.vm = vm
118121
self.logger = logger
119122
self.onDelete = onDelete
@@ -260,6 +263,7 @@ extension LinuxProcess {
260263
stdinPort: self.ioSetup.stdin?.port,
261264
stdoutPort: self.ioSetup.stdout?.port,
262265
stderrPort: self.ioSetup.stderr?.port,
266+
ociRuntimePath: self.ociRuntimePath,
263267
configuration: spec,
264268
options: nil
265269
)

Sources/Containerization/SandboxContext/SandboxContext.pb.swift

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,15 @@ public struct Com_Apple_Containerization_Sandbox_V3_CreateProcessRequest: @unche
414414
/// Clears the value of `stderr`. Subsequent reads from it will return its default value.
415415
public mutating func clearStderr() {self._stderr = nil}
416416

417+
public var ociRuntimePath: String {
418+
get {return _ociRuntimePath ?? String()}
419+
set {_ociRuntimePath = newValue}
420+
}
421+
/// Returns true if `ociRuntimePath` has been explicitly set.
422+
public var hasOciRuntimePath: Bool {return self._ociRuntimePath != nil}
423+
/// Clears the value of `ociRuntimePath`. Subsequent reads from it will return its default value.
424+
public mutating func clearOciRuntimePath() {self._ociRuntimePath = nil}
425+
417426
public var configuration: Data = Data()
418427

419428
public var options: Data {
@@ -433,6 +442,7 @@ public struct Com_Apple_Containerization_Sandbox_V3_CreateProcessRequest: @unche
433442
fileprivate var _stdin: UInt32? = nil
434443
fileprivate var _stdout: UInt32? = nil
435444
fileprivate var _stderr: UInt32? = nil
445+
fileprivate var _ociRuntimePath: String? = nil
436446
fileprivate var _options: Data? = nil
437447
}
438448

@@ -1853,8 +1863,9 @@ extension Com_Apple_Containerization_Sandbox_V3_CreateProcessRequest: SwiftProto
18531863
3: .same(proto: "stdin"),
18541864
4: .same(proto: "stdout"),
18551865
5: .same(proto: "stderr"),
1856-
6: .same(proto: "configuration"),
1857-
7: .same(proto: "options"),
1866+
6: .same(proto: "ociRuntimePath"),
1867+
7: .same(proto: "configuration"),
1868+
8: .same(proto: "options"),
18581869
]
18591870

18601871
public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
@@ -1868,8 +1879,9 @@ extension Com_Apple_Containerization_Sandbox_V3_CreateProcessRequest: SwiftProto
18681879
case 3: try { try decoder.decodeSingularUInt32Field(value: &self._stdin) }()
18691880
case 4: try { try decoder.decodeSingularUInt32Field(value: &self._stdout) }()
18701881
case 5: try { try decoder.decodeSingularUInt32Field(value: &self._stderr) }()
1871-
case 6: try { try decoder.decodeSingularBytesField(value: &self.configuration) }()
1872-
case 7: try { try decoder.decodeSingularBytesField(value: &self._options) }()
1882+
case 6: try { try decoder.decodeSingularStringField(value: &self._ociRuntimePath) }()
1883+
case 7: try { try decoder.decodeSingularBytesField(value: &self.configuration) }()
1884+
case 8: try { try decoder.decodeSingularBytesField(value: &self._options) }()
18731885
default: break
18741886
}
18751887
}
@@ -1895,11 +1907,14 @@ extension Com_Apple_Containerization_Sandbox_V3_CreateProcessRequest: SwiftProto
18951907
try { if let v = self._stderr {
18961908
try visitor.visitSingularUInt32Field(value: v, fieldNumber: 5)
18971909
} }()
1910+
try { if let v = self._ociRuntimePath {
1911+
try visitor.visitSingularStringField(value: v, fieldNumber: 6)
1912+
} }()
18981913
if !self.configuration.isEmpty {
1899-
try visitor.visitSingularBytesField(value: self.configuration, fieldNumber: 6)
1914+
try visitor.visitSingularBytesField(value: self.configuration, fieldNumber: 7)
19001915
}
19011916
try { if let v = self._options {
1902-
try visitor.visitSingularBytesField(value: v, fieldNumber: 7)
1917+
try visitor.visitSingularBytesField(value: v, fieldNumber: 8)
19031918
} }()
19041919
try unknownFields.traverse(visitor: &visitor)
19051920
}
@@ -1910,6 +1925,7 @@ extension Com_Apple_Containerization_Sandbox_V3_CreateProcessRequest: SwiftProto
19101925
if lhs._stdin != rhs._stdin {return false}
19111926
if lhs._stdout != rhs._stdout {return false}
19121927
if lhs._stderr != rhs._stderr {return false}
1928+
if lhs._ociRuntimePath != rhs._ociRuntimePath {return false}
19131929
if lhs.configuration != rhs.configuration {return false}
19141930
if lhs._options != rhs._options {return false}
19151931
if lhs.unknownFields != rhs.unknownFields {return false}

Sources/Containerization/SandboxContext/SandboxContext.proto

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,9 @@ message CreateProcessRequest {
148148
optional uint32 stdin = 3;
149149
optional uint32 stdout = 4;
150150
optional uint32 stderr = 5;
151-
bytes configuration = 6;
152-
optional bytes options = 7;
151+
optional string ociRuntimePath = 6;
152+
bytes configuration = 7;
153+
optional bytes options = 8;
153154
}
154155

155156
message CreateProcessResponse {}

Sources/Containerization/VirtualMachineAgent.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public protocol VirtualMachineAgent: Sendable {
5252
stdinPort: UInt32?,
5353
stdoutPort: UInt32?,
5454
stderrPort: UInt32?,
55+
ociRuntimePath: String?,
5556
configuration: ContainerizationOCI.Spec,
5657
options: Data?
5758
) async throws

Sources/Containerization/Vminitd.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ extension Vminitd: VirtualMachineAgent {
185185
stdinPort: UInt32?,
186186
stdoutPort: UInt32?,
187187
stderrPort: UInt32?,
188+
ociRuntimePath: String?,
188189
configuration: ContainerizationOCI.Spec,
189190
options: Data?
190191
) async throws {
@@ -204,6 +205,9 @@ extension Vminitd: VirtualMachineAgent {
204205
if let containerID {
205206
$0.containerID = containerID
206207
}
208+
if let ociRuntimePath {
209+
$0.ociRuntimePath = ociRuntimePath
210+
}
207211
$0.configuration = try enc.encode(configuration)
208212
})
209213
}

Sources/ContainerizationOCI/Spec.swift

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -296,8 +296,8 @@ public struct Mount: Codable, Sendable {
296296
public var destination: String
297297
public var options: [String]
298298

299-
public var uidMappings: [LinuxIDMapping]
300-
public var gidMappings: [LinuxIDMapping]
299+
public var uidMappings: [LinuxIDMapping]?
300+
public var gidMappings: [LinuxIDMapping]?
301301

302302
public enum CodingKeys: String, CodingKey {
303303
case type
@@ -313,8 +313,8 @@ public struct Mount: Codable, Sendable {
313313
source: String = "",
314314
destination: String,
315315
options: [String] = [],
316-
uidMappings: [LinuxIDMapping] = [],
317-
gidMappings: [LinuxIDMapping] = []
316+
uidMappings: [LinuxIDMapping]? = nil,
317+
gidMappings: [LinuxIDMapping]? = nil
318318
) {
319319
self.destination = destination
320320
self.type = type
@@ -330,8 +330,18 @@ public struct Mount: Codable, Sendable {
330330
self.source = try container.decodeIfPresent(String.self, forKey: .source) ?? ""
331331
self.destination = try container.decode(String.self, forKey: .destination)
332332
self.options = try container.decodeIfPresent([String].self, forKey: .options) ?? []
333-
self.uidMappings = try container.decodeIfPresent([LinuxIDMapping].self, forKey: .uidMappings) ?? []
334-
self.gidMappings = try container.decodeIfPresent([LinuxIDMapping].self, forKey: .gidMappings) ?? []
333+
self.uidMappings = try container.decodeIfPresent([LinuxIDMapping].self, forKey: .uidMappings)
334+
self.gidMappings = try container.decodeIfPresent([LinuxIDMapping].self, forKey: .gidMappings)
335+
}
336+
337+
public func encode(to encoder: Encoder) throws {
338+
var container = encoder.container(keyedBy: CodingKeys.self)
339+
try container.encode(type, forKey: .type)
340+
try container.encode(source, forKey: .source)
341+
try container.encode(destination, forKey: .destination)
342+
try container.encode(options, forKey: .options)
343+
try container.encodeIfPresent(uidMappings, forKey: .uidMappings)
344+
try container.encodeIfPresent(gidMappings, forKey: .gidMappings)
335345
}
336346
}
337347

Sources/ContainerizationOS/Socket/Socket.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,8 @@ extension Socket {
300300
return Socket(
301301
fd: clientFD,
302302
type: newSocketType,
303-
closeOnDeinit: closeOnDeinit
303+
closeOnDeinit: closeOnDeinit,
304+
connected: true
304305
)
305306
}
306307

Sources/cctl/RootfsCommand.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ extension Application {
5555
@Option(name: .long, help: "Path to vminitd")
5656
var vminitd: String
5757

58+
@Option(name: .long, help: "Path to OCI runtime")
59+
var ociRuntime: String?
60+
5861
// The path where the intermediate tar archive is created.
5962
@Argument var tarPath: String
6063

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

150+
if let ociRuntimePath = self.ociRuntime {
151+
src = URL(fileURLWithPath: ociRuntimePath)
152+
let fileName = src.lastPathComponent
153+
data = try Data(contentsOf: src)
154+
entry.path = "sbin/\(fileName)"
155+
entry.size = Int64(data.count)
156+
try writer.writeEntry(entry: entry, data: data)
157+
}
158+
147159
for addFile in addFiles {
148160
let paths = addFile.components(separatedBy: ":")
149161
guard paths.count == 2 else {

0 commit comments

Comments
 (0)