Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
979794f
Give a container the swap it asks for
MayCXC Aug 2, 2026
bab4965
Back the swap device with a sparse file
MayCXC Aug 6, 2026
e8112a5
config: let a swap default sit beside the memory default
MayCXC Aug 7, 2026
bd86048
Describe a pod, and what a sandbox is to the runtime
MayCXC Aug 9, 2026
1178136
Run every container in a pod
MayCXC Aug 9, 2026
0a49daa
Give the command line a pod
MayCXC Aug 9, 2026
2e4acaa
Take away the anonymous resources a container was given
MayCXC Aug 9, 2026
f1af4c8
Adopt the machines a restarted control plane finds running
MayCXC Aug 10, 2026
fc3fa75
Ask the containers service, not its lock, for who references what
MayCXC Aug 10, 2026
c137838
Give containers, pods, and networks to a core plugin
MayCXC Aug 10, 2026
73bf8e8
Give the command line's pod and run to CLI plugins
MayCXC Aug 10, 2026
69034eb
Carry the caller's agent through pod start to every member
MayCXC Aug 14, 2026
8599e6f
Stop a container's dedicated machine when its last container stops
MayCXC Aug 14, 2026
78d4db1
Await the machine's own held flag from the exit closure
MayCXC Aug 14, 2026
00eddfd
Leave the guest without a resolver when DNS is declined
MayCXC Aug 14, 2026
09e7747
Fail a pod start whose machine does not answer, keeping the client
MayCXC Aug 14, 2026
42e1099
Keep the default network on the address range it was given
MayCXC Aug 14, 2026
031efa6
Give a machine someone named the network's resolver
MayCXC Aug 18, 2026
9599c63
Give back what a network holds, and come up without it if it is gone
MayCXC Aug 18, 2026
fa6e404
Balance the retain the framework hands back
MayCXC Aug 18, 2026
00f10d7
Hold the machine's capabilities to the pod that runs it
MayCXC Aug 18, 2026
5a451e4
Leave kernel parameters where the kernel is
MayCXC Aug 18, 2026
37d7783
Give a host back the address and hardware address it had
MayCXC Aug 18, 2026
7ea79bc
Keep what a host was given where the plugin keeps its state
MayCXC Aug 18, 2026
fc6d265
Give the addresses back however the helper leaves
MayCXC Aug 19, 2026
ef3201d
Have a store make somewhere to keep what it holds
MayCXC Aug 20, 2026
3598a35
Record the init image a pod's machine boots
MayCXC Aug 20, 2026
7d100d8
Return a container's freed filesystem blocks to the host
MayCXC Aug 10, 2026
0268432
Give the builder a prune verb that returns the space it frees
MayCXC Aug 13, 2026
3a04069
Test the prune verb against the shared builder
MayCXC Aug 13, 2026
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
14 changes: 14 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,13 @@ $(STAGING_DIR):
@mkdir -p "$(join $(STAGING_DIR), libexec/container/plugins/container-runtime-linux/bin)"
@mkdir -p "$(join $(STAGING_DIR), libexec/container/plugins/container-network-vmnet/bin)"
@mkdir -p "$(join $(STAGING_DIR), libexec/container/plugins/container-core-images/bin)"
@mkdir -p "$(join $(STAGING_DIR), libexec/container/plugins/container-core-containers/bin)"
@mkdir -p "$(join $(STAGING_DIR), libexec/container/plugins/machine-apiserver/bin)"
@mkdir -p "$(join $(STAGING_DIR), libexec/container/plugins/machine-apiserver/resources)"
@mkdir -p "$(join $(STAGING_DIR), libexec/container/plugins/k8s/bin)"
@mkdir -p "$(join $(STAGING_DIR), libexec/container/plugins/k8s/resources)"
@mkdir -p "$(join $(STAGING_DIR), libexec/container/plugins/pod/bin)"
@mkdir -p "$(join $(STAGING_DIR), libexec/container/plugins/run/bin)"

@install "$(BUILD_BIN_DIR)/container" "$(join $(STAGING_DIR), bin/container)"
@install "$(BUILD_BIN_DIR)/container-apiserver" "$(join $(STAGING_DIR), bin/container-apiserver)"
Expand All @@ -144,13 +147,19 @@ $(STAGING_DIR):
@install Sources/Plugins/NetworkVmnet/config.toml "$(join $(STAGING_DIR), libexec/container/plugins/container-network-vmnet/config.toml)"
@install "$(BUILD_BIN_DIR)/container-core-images" "$(join $(STAGING_DIR), libexec/container/plugins/container-core-images/bin/container-core-images)"
@install Sources/Plugins/CoreImages/config.toml "$(join $(STAGING_DIR), libexec/container/plugins/container-core-images/config.toml)"
@install "$(BUILD_BIN_DIR)/container-core-containers" "$(join $(STAGING_DIR), libexec/container/plugins/container-core-containers/bin/container-core-containers)"
@install Sources/Plugins/CoreContainers/config.toml "$(join $(STAGING_DIR), libexec/container/plugins/container-core-containers/config.toml)"
@install "$(BUILD_BIN_DIR)/machine-apiserver" "$(join $(STAGING_DIR), libexec/container/plugins/machine-apiserver/bin/machine-apiserver)"
@install Sources/Plugins/MachineAPIServer/config.toml "$(join $(STAGING_DIR), libexec/container/plugins/machine-apiserver/config.toml)"
@install Sources/Plugins/MachineAPIServer/Resources/init "$(join $(STAGING_DIR), libexec/container/plugins/machine-apiserver/resources/init)"
@install Sources/Plugins/MachineAPIServer/Resources/create-user.sh "$(join $(STAGING_DIR), libexec/container/plugins/machine-apiserver/resources/create-user.sh)"
@install "$(BUILD_BIN_DIR)/k8s" "$(join $(STAGING_DIR), libexec/container/plugins/k8s/bin/k8s)"
@install Sources/Plugins/K8s/config.toml "$(join $(STAGING_DIR), libexec/container/plugins/k8s/config.toml)"
@install Sources/Plugins/K8s/Resources/kindnet.yaml "$(join $(STAGING_DIR), libexec/container/plugins/k8s/resources/kindnet.yaml)"
@install "$(BUILD_BIN_DIR)/pod" "$(join $(STAGING_DIR), libexec/container/plugins/pod/bin/pod)"
@install Sources/Plugins/PodCLI/config.toml "$(join $(STAGING_DIR), libexec/container/plugins/pod/config.toml)"
@install "$(BUILD_BIN_DIR)/run" "$(join $(STAGING_DIR), libexec/container/plugins/run/bin/run)"
@install Sources/Plugins/RunCLI/config.toml "$(join $(STAGING_DIR), libexec/container/plugins/run/config.toml)"

@echo Install update script
@install scripts/update-container.sh "$(join $(STAGING_DIR), bin/update-container.sh)"
Expand All @@ -163,10 +172,13 @@ installer-pkg: $(STAGING_DIR)
@codesign $(CODESIGN_OPTS) --identifier com.apple.container.cli "$(join $(STAGING_DIR), bin/container)"
@codesign $(CODESIGN_OPTS) --identifier com.apple.container.apiserver "$(join $(STAGING_DIR), bin/container-apiserver)"
@codesign $(CODESIGN_OPTS) --prefix=com.apple.container. "$(join $(STAGING_DIR), libexec/container/plugins/container-core-images/bin/container-core-images)"
@codesign $(CODESIGN_OPTS) --prefix=com.apple.container. "$(join $(STAGING_DIR), libexec/container/plugins/container-core-containers/bin/container-core-containers)"
@codesign $(CODESIGN_OPTS) --prefix=com.apple.container. --entitlements=signing/container-runtime-linux.entitlements "$(join $(STAGING_DIR), libexec/container/plugins/container-runtime-linux/bin/container-runtime-linux)"
@codesign $(CODESIGN_OPTS) --prefix=com.apple.container. --entitlements=signing/container-network-vmnet.entitlements "$(join $(STAGING_DIR), libexec/container/plugins/container-network-vmnet/bin/container-network-vmnet)"
@codesign $(CODESIGN_OPTS) --prefix=com.apple.container. "$(join $(STAGING_DIR), libexec/container/plugins/machine-apiserver/bin/machine-apiserver)"
@codesign $(CODESIGN_OPTS) --prefix=com.apple.container. "$(join $(STAGING_DIR), libexec/container/plugins/k8s/bin/k8s)"
@codesign $(CODESIGN_OPTS) --prefix=com.apple.container. "$(join $(STAGING_DIR), libexec/container/plugins/pod/bin/pod)"
@codesign $(CODESIGN_OPTS) --prefix=com.apple.container. "$(join $(STAGING_DIR), libexec/container/plugins/run/bin/run)"

@echo Creating application installer
@pkgbuild --root "$(STAGING_DIR)" --identifier com.apple.container-installer --install-location /usr/local --version ${RELEASE_VERSION} $(PKG_PATH)
Expand All @@ -180,6 +192,7 @@ dsym:
@cp -a "$(BUILD_BIN_DIR)/container-runtime-linux.dSYM" "$(DSYM_DIR)"
@cp -a "$(BUILD_BIN_DIR)/container-network-vmnet.dSYM" "$(DSYM_DIR)"
@cp -a "$(BUILD_BIN_DIR)/container-core-images.dSYM" "$(DSYM_DIR)"
@cp -a "$(BUILD_BIN_DIR)/container-core-containers.dSYM" "$(DSYM_DIR)"
@cp -a "$(BUILD_BIN_DIR)/container-apiserver.dSYM" "$(DSYM_DIR)"
@cp -a "$(BUILD_BIN_DIR)/container.dSYM" "$(DSYM_DIR)"

Expand Down Expand Up @@ -212,6 +225,7 @@ COV_BINARIES := \
$(BUILD_BIN_DIR)/container-runtime-linux \
$(BUILD_BIN_DIR)/container-network-vmnet \
$(BUILD_BIN_DIR)/container-core-images \
$(BUILD_BIN_DIR)/container-core-containers \
$(BUILD_BIN_DIR)/machine-apiserver
COV_OBJECT_FLAGS := $(patsubst %,-object %,$(COV_BINARIES))
# Set of files we do not want to get caught in the coverage generation
Expand Down
7 changes: 3 additions & 4 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 36 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,42 @@ let package = Package(
path: "Sources/Plugins/CoreImages",
exclude: ["config.toml"]
),
.executableTarget(
name: "container-core-containers",
dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "Logging", package: "swift-log"),
.product(name: "Containerization", package: "containerization"),
.product(name: "SystemPackage", package: "swift-system"),
"ContainerAPIClient",
"ContainerAPIService",
"ContainerLog",
"ContainerPersistence",
"ContainerPlugin",
"ContainerVersion",
"ContainerXPC",
],
path: "Sources/Plugins/CoreContainers",
exclude: ["config.toml"]
),
.executableTarget(
name: "pod",
dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser"),
"ContainerCommands",
],
path: "Sources/Plugins/PodCLI",
exclude: ["config.toml"]
),
.executableTarget(
name: "run",
dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser"),
"ContainerCommands",
],
path: "Sources/Plugins/RunCLI",
exclude: ["config.toml"]
),
.target(
name: "ContainerImagesService",
dependencies: [
Expand Down
111 changes: 3 additions & 108 deletions Sources/APIServer/APIServer+Start.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ extension APIServer {
var logRoot = LogRoot.path

func run() async throws {
let containerSystemConfig: ContainerSystemConfig = try await ConfigurationLoader.load()
let commandName = APIServer._commandName
let logPath = logRoot.map { $0.appending(FilePath.Component("\(commandName).log") ?? "unknown") }
let log = ServiceLogger.bootstrap(category: "APIServer", debug: debug, logPath: logPath)
Expand All @@ -64,25 +63,10 @@ extension APIServer {
let pluginLoader = try initializePluginLoader(log: log)

try await initializePlugins(pluginLoader: pluginLoader, log: log, routes: &routes, debug: debug)
let containersService = try initializeContainersService(
pluginLoader: pluginLoader,
containerSystemConfig: containerSystemConfig,
log: log,
routes: &routes
)
let networkService = try await initializeNetworksService(
pluginLoader: pluginLoader,
containersService: containersService,
containerSystemConfig: containerSystemConfig,
log: log,
routes: &routes
)
await containersService.setNetworksService(networkService)
initializeHealthCheckService(log: log, routes: &routes)
try initializeKernelService(log: log, routes: &routes)
let volumesService = try await initializeVolumeService(containersService: containersService, log: log, routes: &routes)
let volumesService = try await initializeVolumeService(log: log, routes: &routes)
try initializeDiskUsageService(
containersService: containersService,
volumesService: volumesService,
log: log,
routes: &routes
Expand All @@ -109,7 +93,7 @@ extension APIServer {

// start up host table DNS
group.addTask {
let hostsResolver = ContainerDNSHandler(networkService: networkService)
let hostsResolver = ContainerDNSHandler(networks: NetworkClient())
let nxDomainResolver = NxDomainResolver()
let compositeResolver = CompositeResolver(handlers: [hostsResolver, nxDomainResolver])
let hostsQueryValidator = StandardQueryValidator(handler: compositeResolver)
Expand Down Expand Up @@ -271,101 +255,14 @@ extension APIServer {
routes[XPCRoute.getDefaultKernel] = XPCServer.route(harness.getDefaultKernel)
}

private func initializeContainersService(
pluginLoader: PluginLoader,
containerSystemConfig: ContainerSystemConfig,
log: Logger,
routes: inout [XPCRoute: XPCServer.RouteHandler]
) throws -> ContainersService {
log.info("initializing containers service")

// TODO: Remove when we convert ContainersService to FilePath
let appRootURL = URL(fileURLWithPath: appRoot.string)
let service = try ContainersService(
appRoot: appRootURL,
pluginLoader: pluginLoader,
containerSystemConfig: containerSystemConfig,
log: log,
debugHelpers: debug
)
let harness = ContainersHarness(service: service, log: log)

routes[XPCRoute.containerList] = XPCServer.route(harness.list)
routes[XPCRoute.containerCreate] = XPCServer.route(harness.create)
routes[XPCRoute.containerDelete] = XPCServer.route(harness.delete)
routes[XPCRoute.containerLogs] = XPCServer.route(harness.logs)
routes[XPCRoute.containerBootstrap] = XPCServer.route(harness.bootstrap)
routes[XPCRoute.containerDial] = XPCServer.route(harness.dial)
routes[XPCRoute.containerStop] = XPCServer.route(harness.stop)
routes[XPCRoute.containerStartProcess] = XPCServer.route(harness.startProcess)
routes[XPCRoute.containerCreateProcess] = XPCServer.route(harness.createProcess)
routes[XPCRoute.containerResize] = XPCServer.route(harness.resize)
routes[XPCRoute.containerWait] = XPCServer.route(harness.wait)
routes[XPCRoute.containerKill] = XPCServer.route(harness.kill)
routes[XPCRoute.containerStats] = XPCServer.route(harness.stats)
routes[XPCRoute.containerDiskUsage] = XPCServer.route(harness.diskUsage)
routes[XPCRoute.containerCopyIn] = XPCServer.route(harness.copyIn)
routes[XPCRoute.containerCopyOut] = XPCServer.route(harness.copyOut)
routes[XPCRoute.containerExport] = XPCServer.route(harness.export)

return service
}

private func initializeNetworksService(
pluginLoader: PluginLoader,
containersService: ContainersService,
containerSystemConfig: ContainerSystemConfig,
log: Logger,
routes: inout [XPCRoute: XPCServer.RouteHandler]
) async throws -> NetworksService {
log.info("initializing networks service")

let resourceRoot = appRoot.appending(FilePath.Component("networks"))
let defaultNetworkConfig = try NetworkConfiguration(
name: NetworkClient.defaultNetworkName,
mode: .nat,
ipv4Subnet: containerSystemConfig.network.subnet,
ipv6Subnet: containerSystemConfig.network.subnetv6,
labels: try .init([ResourceLabelKeys.role: ResourceRoleValues.builtin]),
plugin: "container-network-vmnet"
)
let service = try await NetworksService(
pluginLoader: pluginLoader,
resourceRoot: resourceRoot,
containersService: containersService,
defaultNetworkConfiguration: defaultNetworkConfig,
log: log,
debugHelpers: debug
)

let defaultNetwork = try await service.list()
.filter { $0.isBuiltin }
.first
if defaultNetwork == nil {
// FIXME: default network should be configurable elsewhere
_ = try await service.create(configuration: defaultNetworkConfig)
}

let harness = NetworksHarness(service: service, log: log)

if #available(macOS 26, *) {
routes[XPCRoute.networkCreate] = XPCServer.route(harness.create)
}
routes[XPCRoute.networkList] = XPCServer.route(harness.list)
routes[XPCRoute.networkDelete] = XPCServer.route(harness.delete)

return service
}

private func initializeVolumeService(
containersService: ContainersService,
log: Logger,
routes: inout [XPCRoute: XPCServer.RouteHandler]
) async throws -> VolumesService {
log.info("initializing volume service")

let resourceRoot = appRoot.appending(FilePath.Component("volumes"))
let service = try await VolumesService(resourceRoot: resourceRoot, containersService: containersService, log: log)
let service = try await VolumesService(resourceRoot: resourceRoot, log: log)
let harness = VolumesHarness(service: service, log: log)

routes[XPCRoute.volumeCreate] = XPCServer.route(harness.create)
Expand All @@ -378,15 +275,13 @@ extension APIServer {
}

private func initializeDiskUsageService(
containersService: ContainersService,
volumesService: VolumesService,
log: Logger,
routes: inout [XPCRoute: XPCServer.RouteHandler]
) throws {
log.info("initializing disk usage service")

let service = DiskUsageService(
containersService: containersService,
volumesService: volumesService,
log: log
)
Expand Down
11 changes: 6 additions & 5 deletions Sources/APIServer/ContainerDNSHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,18 @@
// limitations under the License.
//===----------------------------------------------------------------------===//

import ContainerAPIClient
import ContainerAPIService
import ContainerizationExtras
import DNSServer

/// Handler that uses table lookup to resolve hostnames.
struct ContainerDNSHandler: DNSHandler {
private let networkService: NetworksService
private let networks: NetworkClient
private let ttl: UInt32

public init(networkService: NetworksService, ttl: UInt32 = 5) {
self.networkService = networkService
public init(networks: NetworkClient, ttl: UInt32 = 5) {
self.networks = networks
self.ttl = ttl
}

Expand Down Expand Up @@ -76,7 +77,7 @@ struct ContainerDNSHandler: DNSHandler {
}

private func answerHost(question: Question) async throws -> ResourceRecord? {
guard let ipAllocation = try await networkService.lookup(hostname: question.name) else {
guard let ipAllocation = try await networks.lookup(hostname: question.name) else {
return nil
}
let ipv4 = ipAllocation.ipv4Address.address.description
Expand All @@ -88,7 +89,7 @@ struct ContainerDNSHandler: DNSHandler {
}

private func answerHost6(question: Question) async throws -> (record: ResourceRecord?, hostnameExists: Bool) {
guard let ipAllocation = try await networkService.lookup(hostname: question.name) else {
guard let ipAllocation = try await networks.lookup(hostname: question.name) else {
return (nil, false)
}
guard let ipv6Address = ipAllocation.ipv6Address else {
Expand Down
2 changes: 1 addition & 1 deletion Sources/ContainerCommands/Application.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ public struct Application: AsyncLoggableCommand {
ContainerKill.self,
ContainerList.self,
ContainerLogs.self,
ContainerRun.self,
ContainerStart.self,
ContainerStats.self,
ContainerStop.self,
ContainerTrim.self,
ContainerPrune.self,
]
),
Expand Down
12 changes: 12 additions & 0 deletions Sources/ContainerCommands/BuildCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,18 @@ extension Application {
} catch {
throw NSError(domain: "Build", code: 1, userInfo: [NSLocalizedDescriptionKey: "\(error)"])
}

// The build churned the builder's cache: it re-transferred the
// context and unpacked every layer, then discarded what it did not
// keep. The builder freed those blocks inside its filesystem; trim
// returns them to the host, so the sparse backing file tracks what
// the cache is using rather than the high water mark of every build.
// Best effort: a build that succeeded is not failed by a reclaim.
do {
try await ContainerClient().trim(id: "buildkit")
} catch {
log.warning("failed to reclaim builder space after build", metadata: ["error": "\(error)"])
}
}

public mutating func validate() throws {
Expand Down
1 change: 1 addition & 0 deletions Sources/ContainerCommands/Builder/Builder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ extension Application {
BuilderStart.self,
BuilderStatus.self,
BuilderStop.self,
BuilderPrune.self,
BuilderDelete.self,
])

Expand Down
Loading