Add variant Support (#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.
This commit is contained in:
AJ Emory
2026-05-12 18:44:56 -07:00
committed by GitHub
parent b466959644
commit 521df518e2
10 changed files with 99 additions and 15 deletions
+3 -3
View File
@@ -1,5 +1,5 @@
{
"originHash" : "b606e718aa690122c644e34349c372f37b67ae1641c1f4e6216fad7aad555dc9",
"originHash" : "22a1e1b6903f45f7d9b7dc7940fd328f427c4123c73d6985eab99ae6c21523c4",
"pins" : [
{
"identity" : "async-http-client",
@@ -105,8 +105,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-collections.git",
"state" : {
"revision" : "6675bc0ff86e61436e615df6fc5174e043e57924",
"version" : "1.4.1"
"revision" : "03cc312c2c933ed87abace34044a5dff7a3117c1",
"version" : "1.5.0"
}
},
{
+9 -1
View File
@@ -108,6 +108,7 @@ let package = Package(
"ContainerPersistence",
"ContainerPlugin",
"ContainerResource",
"ContainerRuntimeLinuxTypes",
"ContainerVersion",
"ContainerXPC",
"TerminalProgress",
@@ -199,6 +200,7 @@ let package = Package(
dependencies: [
.product(name: "Containerization", package: "containerization"),
"ContainerResource",
"ContainerRuntimeLinuxTypes",
"ContainerSandboxServiceClient",
]
),
@@ -333,6 +335,11 @@ let package = Package(
],
path: "Sources/Services/ContainerNetworkService/Client"
),
.target(
name: "ContainerRuntimeLinuxTypes",
dependencies: [],
path: "Sources/Plugins/RuntimeLinux/Types"
),
.executableTarget(
name: "container-runtime-linux",
dependencies: [
@@ -342,13 +349,14 @@ let package = Package(
"ContainerLog",
"ContainerPlugin",
"ContainerResource",
"ContainerRuntimeLinuxTypes",
"ContainerSandboxService",
"ContainerSandboxServiceClient",
"ContainerVersion",
"ContainerXPC",
],
path: "Sources/Plugins/RuntimeLinux",
exclude: ["config.toml"]
exclude: ["config.toml", "Types"]
),
.target(
name: "ContainerSandboxService",
@@ -0,0 +1,27 @@
//===----------------------------------------------------------------------===//
// Copyright © 2026 Apple Inc. and the container 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 Foundation
/// Linux-specific runtime data passed through the opaque runtimeData field
/// in RuntimeConfiguration. Encoded by the CLI, decoded by the Linux runtime.
public struct LinuxRuntimeData: Codable, Sendable {
public let variant: String?
public init(variant: String? = nil) {
self.variant = variant
}
}
@@ -49,7 +49,8 @@ public struct ContainerClient: Sendable {
configuration: ContainerConfiguration,
options: ContainerCreateOptions = .default,
kernel: Kernel,
initImage: String? = nil
initImage: String? = nil,
runtimeData: Data? = nil
) async throws {
do {
let request = XPCMessage(route: .containerCreate)
@@ -65,6 +66,10 @@ public struct ContainerClient: Sendable {
request.set(key: .initImage, value: initImage)
}
if let runtimeData {
request.set(key: .runtimeData, value: runtimeData)
}
try await xpcSend(message: request)
} catch {
throw ContainerizationError(
@@ -32,6 +32,8 @@ public enum XPCKeys: String {
case containerConfig
/// Container options key.
case containerOptions
/// Opaque runtime-specific data.
case runtimeData
/// Vsock port number key.
case port
/// Exit code for a process
@@ -196,8 +196,9 @@ public struct ContainersHarness: Sendable {
let kernel = try JSONDecoder().decode(Kernel.self, from: kdata)
let initImage = message.string(key: .initImage)
let runtimeData = message.dataNoCopy(key: .runtimeData)
try await service.create(configuration: config, kernel: kernel, options: options, initImage: initImage)
try await service.create(configuration: config, kernel: kernel, options: options, initImage: initImage, runtimeData: runtimeData)
return message.reply()
}
@@ -277,7 +277,7 @@ public actor ContainersService {
}
/// Create a new container from the provided id and configuration.
public func create(configuration: ContainerConfiguration, kernel: Kernel, options: ContainerCreateOptions, initImage: String? = nil) async throws {
public func create(configuration: ContainerConfiguration, kernel: Kernel, options: ContainerCreateOptions, initImage: String? = nil, runtimeData: Data? = nil) async throws {
log.debug(
"ContainersService: enter",
metadata: [
@@ -381,7 +381,8 @@ public actor ContainersService {
kernel: kernel,
containerConfiguration: configuration,
containerRootFilesystem: imageFs,
options: options
options: options,
runtimeData: runtimeData
)
try runtimeConfig.writeRuntimeConfiguration()
@@ -28,6 +28,7 @@ public struct RuntimeConfiguration: Codable, Sendable {
public let containerConfiguration: ContainerConfiguration?
public let containerRootFilesystem: Filesystem?
public let options: ContainerCreateOptions?
public let runtimeData: Data?
public init(
path: URL,
@@ -35,7 +36,8 @@ public struct RuntimeConfiguration: Codable, Sendable {
kernel: Kernel,
containerConfiguration: ContainerConfiguration? = nil,
containerRootFilesystem: Filesystem? = nil,
options: ContainerCreateOptions? = nil
options: ContainerCreateOptions? = nil,
runtimeData: Data? = nil
) {
self.path = path
self.initialFilesystem = initialFilesystem
@@ -43,6 +45,7 @@ public struct RuntimeConfiguration: Codable, Sendable {
self.containerConfiguration = containerConfiguration
self.containerRootFilesystem = containerRootFilesystem
self.options = options
self.runtimeData = runtimeData
}
public var runtimeConfigurationPath: URL {
@@ -139,6 +139,8 @@ public actor SandboxService {
self.log.debug("enter", metadata: ["func": "\(#function)"])
defer { self.log.debug("exit", metadata: ["func": "\(#function)"]) }
// Create the bundle if it doesn't exist yet
// Create the bundle if it doesn't exist yet
if !self.bundleExists(at: self.root) {
try self.createBundle()
@@ -14,11 +14,10 @@
// limitations under the License.
//===----------------------------------------------------------------------===//
// import ContainerAPIService
import ContainerResource
import ContainerRuntimeLinuxTypes
import ContainerSandboxServiceClient
import Containerization
// import ContainerizationOCI
import Foundation
import Testing
@@ -72,10 +71,6 @@ struct RuntimeConfigurationTests {
try runtimeConfig.writeRuntimeConfiguration()
defer {
try? FileManager.default.removeItem(at: runtimeConfig.runtimeConfigurationPath)
}
let readRuntimeConfig = try RuntimeConfiguration.readRuntimeConfiguration(from: bundlePath)
#expect(
@@ -97,4 +92,44 @@ struct RuntimeConfigurationTests {
readRuntimeConfig.options == nil,
"Options should be nil")
}
@Test
func testRuntimeConfigurationWithVariant() throws {
let tempDir = FileManager.default.temporaryDirectory
let bundlePath = tempDir.appendingPathComponent("test-bundle-\(UUID())")
defer {
try? FileManager.default.removeItem(at: bundlePath)
}
let initFs = Filesystem.virtiofs(
source: "/path/to/initfs",
destination: "/",
options: ["ro"]
)
let kernel = Kernel(
path: URL(fileURLWithPath: "/path/to/kernel"),
platform: .linuxArm
)
let linuxData = LinuxRuntimeData(variant: "test-variant")
let encodedData = try JSONEncoder().encode(linuxData)
let runtimeConfig = RuntimeConfiguration(
path: bundlePath,
initialFilesystem: initFs,
kernel: kernel,
runtimeData: encodedData
)
try runtimeConfig.writeRuntimeConfiguration()
let readRuntimeConfig = try RuntimeConfiguration.readRuntimeConfiguration(from: bundlePath)
#expect(readRuntimeConfig.runtimeData != nil, "runtimeData should be persisted")
let decodedData = try JSONDecoder().decode(LinuxRuntimeData.self, from: readRuntimeConfig.runtimeData!)
#expect(decodedData.variant == "test-variant", "Variant should round-trip through RuntimeConfiguration")
}
}