Skip to content

Commit 521df51

Browse files
authored
Add variant Support (apple#1548)
- This change adds two types to support opaque runtime data passing through the APIServer: - RuntimeConfiguration `data` - RuntimeLinuxData type - LinuxRuntimeData defines runtime specific information. With this change, it will support `variant` only. The optional RuntimeConfiguration `data` field encodes runtime specific data to pass through the APIServer. It is decoded as needed by the runtime. - The idea is to eventually move all runtime specific data into the `data` field so that the APIServer is only aware of generic container information.
1 parent b466959 commit 521df51

10 files changed

Lines changed: 99 additions & 15 deletions

File tree

Package.resolved

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ let package = Package(
108108
"ContainerPersistence",
109109
"ContainerPlugin",
110110
"ContainerResource",
111+
"ContainerRuntimeLinuxTypes",
111112
"ContainerVersion",
112113
"ContainerXPC",
113114
"TerminalProgress",
@@ -199,6 +200,7 @@ let package = Package(
199200
dependencies: [
200201
.product(name: "Containerization", package: "containerization"),
201202
"ContainerResource",
203+
"ContainerRuntimeLinuxTypes",
202204
"ContainerSandboxServiceClient",
203205
]
204206
),
@@ -333,6 +335,11 @@ let package = Package(
333335
],
334336
path: "Sources/Services/ContainerNetworkService/Client"
335337
),
338+
.target(
339+
name: "ContainerRuntimeLinuxTypes",
340+
dependencies: [],
341+
path: "Sources/Plugins/RuntimeLinux/Types"
342+
),
336343
.executableTarget(
337344
name: "container-runtime-linux",
338345
dependencies: [
@@ -342,13 +349,14 @@ let package = Package(
342349
"ContainerLog",
343350
"ContainerPlugin",
344351
"ContainerResource",
352+
"ContainerRuntimeLinuxTypes",
345353
"ContainerSandboxService",
346354
"ContainerSandboxServiceClient",
347355
"ContainerVersion",
348356
"ContainerXPC",
349357
],
350358
path: "Sources/Plugins/RuntimeLinux",
351-
exclude: ["config.toml"]
359+
exclude: ["config.toml", "Types"]
352360
),
353361
.target(
354362
name: "ContainerSandboxService",
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//===----------------------------------------------------------------------===//
2+
// Copyright © 2026 Apple Inc. and the container project authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// https://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//===----------------------------------------------------------------------===//
16+
17+
import Foundation
18+
19+
/// Linux-specific runtime data passed through the opaque runtimeData field
20+
/// in RuntimeConfiguration. Encoded by the CLI, decoded by the Linux runtime.
21+
public struct LinuxRuntimeData: Codable, Sendable {
22+
public let variant: String?
23+
24+
public init(variant: String? = nil) {
25+
self.variant = variant
26+
}
27+
}

Sources/Services/ContainerAPIService/Client/ContainerClient.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ public struct ContainerClient: Sendable {
4949
configuration: ContainerConfiguration,
5050
options: ContainerCreateOptions = .default,
5151
kernel: Kernel,
52-
initImage: String? = nil
52+
initImage: String? = nil,
53+
runtimeData: Data? = nil
5354
) async throws {
5455
do {
5556
let request = XPCMessage(route: .containerCreate)
@@ -65,6 +66,10 @@ public struct ContainerClient: Sendable {
6566
request.set(key: .initImage, value: initImage)
6667
}
6768

69+
if let runtimeData {
70+
request.set(key: .runtimeData, value: runtimeData)
71+
}
72+
6873
try await xpcSend(message: request)
6974
} catch {
7075
throw ContainerizationError(

Sources/Services/ContainerAPIService/Client/XPC+.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ public enum XPCKeys: String {
3232
case containerConfig
3333
/// Container options key.
3434
case containerOptions
35+
/// Opaque runtime-specific data.
36+
case runtimeData
3537
/// Vsock port number key.
3638
case port
3739
/// Exit code for a process

Sources/Services/ContainerAPIService/Server/Containers/ContainersHarness.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,9 @@ public struct ContainersHarness: Sendable {
196196
let kernel = try JSONDecoder().decode(Kernel.self, from: kdata)
197197

198198
let initImage = message.string(key: .initImage)
199+
let runtimeData = message.dataNoCopy(key: .runtimeData)
199200

200-
try await service.create(configuration: config, kernel: kernel, options: options, initImage: initImage)
201+
try await service.create(configuration: config, kernel: kernel, options: options, initImage: initImage, runtimeData: runtimeData)
201202
return message.reply()
202203
}
203204

Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ public actor ContainersService {
277277
}
278278

279279
/// Create a new container from the provided id and configuration.
280-
public func create(configuration: ContainerConfiguration, kernel: Kernel, options: ContainerCreateOptions, initImage: String? = nil) async throws {
280+
public func create(configuration: ContainerConfiguration, kernel: Kernel, options: ContainerCreateOptions, initImage: String? = nil, runtimeData: Data? = nil) async throws {
281281
log.debug(
282282
"ContainersService: enter",
283283
metadata: [
@@ -381,7 +381,8 @@ public actor ContainersService {
381381
kernel: kernel,
382382
containerConfiguration: configuration,
383383
containerRootFilesystem: imageFs,
384-
options: options
384+
options: options,
385+
runtimeData: runtimeData
385386
)
386387

387388
try runtimeConfig.writeRuntimeConfiguration()

Sources/Services/ContainerSandboxService/Client/SandboxRuntimeConfiguration.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,24 @@ public struct RuntimeConfiguration: Codable, Sendable {
2828
public let containerConfiguration: ContainerConfiguration?
2929
public let containerRootFilesystem: Filesystem?
3030
public let options: ContainerCreateOptions?
31+
public let runtimeData: Data?
3132

3233
public init(
3334
path: URL,
3435
initialFilesystem: Filesystem,
3536
kernel: Kernel,
3637
containerConfiguration: ContainerConfiguration? = nil,
3738
containerRootFilesystem: Filesystem? = nil,
38-
options: ContainerCreateOptions? = nil
39+
options: ContainerCreateOptions? = nil,
40+
runtimeData: Data? = nil
3941
) {
4042
self.path = path
4143
self.initialFilesystem = initialFilesystem
4244
self.kernel = kernel
4345
self.containerConfiguration = containerConfiguration
4446
self.containerRootFilesystem = containerRootFilesystem
4547
self.options = options
48+
self.runtimeData = runtimeData
4649
}
4750

4851
public var runtimeConfigurationPath: URL {

Sources/Services/ContainerSandboxService/Server/SandboxService.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ public actor SandboxService {
139139
self.log.debug("enter", metadata: ["func": "\(#function)"])
140140
defer { self.log.debug("exit", metadata: ["func": "\(#function)"]) }
141141

142+
// Create the bundle if it doesn't exist yet
143+
142144
// Create the bundle if it doesn't exist yet
143145
if !self.bundleExists(at: self.root) {
144146
try self.createBundle()

Tests/ContainerAPIServiceTests/RuntimeConfigurationTests.swift

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,10 @@
1414
// limitations under the License.
1515
//===----------------------------------------------------------------------===//
1616

17-
// import ContainerAPIService
1817
import ContainerResource
18+
import ContainerRuntimeLinuxTypes
1919
import ContainerSandboxServiceClient
2020
import Containerization
21-
// import ContainerizationOCI
2221
import Foundation
2322
import Testing
2423

@@ -72,10 +71,6 @@ struct RuntimeConfigurationTests {
7271

7372
try runtimeConfig.writeRuntimeConfiguration()
7473

75-
defer {
76-
try? FileManager.default.removeItem(at: runtimeConfig.runtimeConfigurationPath)
77-
}
78-
7974
let readRuntimeConfig = try RuntimeConfiguration.readRuntimeConfiguration(from: bundlePath)
8075

8176
#expect(
@@ -97,4 +92,44 @@ struct RuntimeConfigurationTests {
9792
readRuntimeConfig.options == nil,
9893
"Options should be nil")
9994
}
95+
96+
@Test
97+
func testRuntimeConfigurationWithVariant() throws {
98+
let tempDir = FileManager.default.temporaryDirectory
99+
let bundlePath = tempDir.appendingPathComponent("test-bundle-\(UUID())")
100+
101+
defer {
102+
try? FileManager.default.removeItem(at: bundlePath)
103+
}
104+
105+
let initFs = Filesystem.virtiofs(
106+
source: "/path/to/initfs",
107+
destination: "/",
108+
options: ["ro"]
109+
)
110+
111+
let kernel = Kernel(
112+
path: URL(fileURLWithPath: "/path/to/kernel"),
113+
platform: .linuxArm
114+
)
115+
116+
let linuxData = LinuxRuntimeData(variant: "test-variant")
117+
let encodedData = try JSONEncoder().encode(linuxData)
118+
119+
let runtimeConfig = RuntimeConfiguration(
120+
path: bundlePath,
121+
initialFilesystem: initFs,
122+
kernel: kernel,
123+
runtimeData: encodedData
124+
)
125+
126+
try runtimeConfig.writeRuntimeConfiguration()
127+
128+
let readRuntimeConfig = try RuntimeConfiguration.readRuntimeConfiguration(from: bundlePath)
129+
130+
#expect(readRuntimeConfig.runtimeData != nil, "runtimeData should be persisted")
131+
132+
let decodedData = try JSONDecoder().decode(LinuxRuntimeData.self, from: readRuntimeConfig.runtimeData!)
133+
#expect(decodedData.variant == "test-variant", "Variant should round-trip through RuntimeConfiguration")
134+
}
100135
}

0 commit comments

Comments
 (0)