mirror of
https://github.com/apple/container.git
synced 2026-08-28 19:36:31 +00:00
Add and use container-apiserver start. (#655)
- Part of #653. - Also use same file organization for network helper. ## Motivation and Context Future-proof plugin CLIs.
This commit is contained in:
@@ -66,6 +66,7 @@ extension Application {
|
||||
args.append("--debug")
|
||||
}
|
||||
|
||||
args.append("start")
|
||||
let apiServerDataUrl = appRoot.appending(path: "apiserver")
|
||||
try! FileManager.default.createDirectory(at: apiServerDataUrl, withIntermediateDirectories: true)
|
||||
var env = ProcessInfo.processInfo.environment.filter { key, _ in
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Copyright © 2025 Apple Inc. and the container project authors. All rights reserved.
|
||||
//
|
||||
// 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 ArgumentParser
|
||||
import ContainerAPIService
|
||||
import ContainerClient
|
||||
import ContainerNetworkService
|
||||
import ContainerPlugin
|
||||
import ContainerXPC
|
||||
import DNSServer
|
||||
import Foundation
|
||||
import Logging
|
||||
|
||||
extension APIServer {
|
||||
struct Start: AsyncParsableCommand {
|
||||
static let configuration = CommandConfiguration(
|
||||
commandName: "start",
|
||||
abstract: "Start helper for the API server"
|
||||
)
|
||||
|
||||
static let listenAddress = "127.0.0.1"
|
||||
static let dnsPort = 2053
|
||||
|
||||
@Flag(name: .long, help: "Enable debug logging")
|
||||
var debug = false
|
||||
|
||||
var appRoot = ApplicationRoot.url
|
||||
|
||||
var installRoot = InstallRoot.url
|
||||
|
||||
func run() async throws {
|
||||
let commandName = Self.configuration.commandName ?? "container-apiserver"
|
||||
let log = APIServer.setupLogger(debug: debug)
|
||||
log.info("starting \(commandName)")
|
||||
defer {
|
||||
log.info("stopping \(commandName)")
|
||||
}
|
||||
|
||||
do {
|
||||
log.info("configuring XPC server")
|
||||
var routes = [XPCRoute: XPCServer.RouteHandler]()
|
||||
let pluginLoader = try initializePluginLoader(log: log)
|
||||
try await initializePlugins(pluginLoader: pluginLoader, log: log, routes: &routes)
|
||||
let containersService = try initializeContainerService(
|
||||
pluginLoader: pluginLoader,
|
||||
log: log,
|
||||
routes: &routes
|
||||
)
|
||||
let networkService = try await initializeNetworkService(
|
||||
pluginLoader: pluginLoader,
|
||||
containersService: containersService,
|
||||
log: log,
|
||||
routes: &routes
|
||||
)
|
||||
initializeHealthCheckService(log: log, routes: &routes)
|
||||
try initializeKernelService(log: log, routes: &routes)
|
||||
try initializeVolumeService(containersService: containersService, log: log, routes: &routes)
|
||||
|
||||
let server = XPCServer(
|
||||
identifier: "com.apple.container.apiserver",
|
||||
routes: routes.reduce(
|
||||
into: [String: XPCServer.RouteHandler](),
|
||||
{
|
||||
$0[$1.key.rawValue] = $1.value
|
||||
}), log: log)
|
||||
|
||||
await withThrowingTaskGroup(of: Void.self) { group in
|
||||
group.addTask {
|
||||
log.info("starting XPC server")
|
||||
try await server.listen()
|
||||
}
|
||||
// start up host table DNS
|
||||
group.addTask {
|
||||
let hostsResolver = ContainerDNSHandler(networkService: networkService)
|
||||
let nxDomainResolver = NxDomainResolver()
|
||||
let compositeResolver = CompositeResolver(handlers: [hostsResolver, nxDomainResolver])
|
||||
let hostsQueryValidator = StandardQueryValidator(handler: compositeResolver)
|
||||
let dnsServer: DNSServer = DNSServer(handler: hostsQueryValidator, log: log)
|
||||
log.info(
|
||||
"starting DNS host query resolver",
|
||||
metadata: [
|
||||
"host": "\(Self.listenAddress)",
|
||||
"port": "\(Self.dnsPort)",
|
||||
]
|
||||
)
|
||||
try await dnsServer.run(host: Self.listenAddress, port: Self.dnsPort)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
log.error("\(commandName) failed", metadata: ["error": "\(error)"])
|
||||
APIServer.exit(withError: error)
|
||||
}
|
||||
}
|
||||
|
||||
private func initializePluginLoader(log: Logger) throws -> PluginLoader {
|
||||
log.info(
|
||||
"initializing plugin loader",
|
||||
metadata: [
|
||||
"installRoot": "\(installRoot.path(percentEncoded: false))"
|
||||
])
|
||||
|
||||
let pluginsURL = PluginLoader.userPluginsDir(installRoot: installRoot)
|
||||
log.info("detecting user plugins directory", metadata: ["path": "\(pluginsURL.path(percentEncoded: false))"])
|
||||
var directoryExists: ObjCBool = false
|
||||
_ = FileManager.default.fileExists(atPath: pluginsURL.path, isDirectory: &directoryExists)
|
||||
let userPluginsURL = directoryExists.boolValue ? pluginsURL : nil
|
||||
|
||||
// plugins built into the application installed as a macOS app bundle
|
||||
let appBundlePluginsURL = Bundle.main.resourceURL?.appending(path: "plugins")
|
||||
|
||||
// plugins built into the application installed as a Unix-like application
|
||||
let installRootPluginsURL =
|
||||
installRoot
|
||||
.appendingPathComponent("libexec")
|
||||
.appendingPathComponent("container")
|
||||
.appendingPathComponent("plugins")
|
||||
.standardized
|
||||
|
||||
let pluginDirectories = [
|
||||
userPluginsURL,
|
||||
appBundlePluginsURL,
|
||||
installRootPluginsURL,
|
||||
].compactMap { $0 }
|
||||
|
||||
let pluginFactories: [PluginFactory] = [
|
||||
DefaultPluginFactory(),
|
||||
AppBundlePluginFactory(),
|
||||
]
|
||||
|
||||
for pluginDirectory in pluginDirectories {
|
||||
log.info("discovered plugin directory", metadata: ["path": "\(pluginDirectory.path(percentEncoded: false))"])
|
||||
}
|
||||
|
||||
return try PluginLoader(
|
||||
appRoot: appRoot,
|
||||
installRoot: installRoot,
|
||||
pluginDirectories: pluginDirectories,
|
||||
pluginFactories: pluginFactories,
|
||||
log: log
|
||||
)
|
||||
}
|
||||
|
||||
// First load all of the plugins we can find. Then just expose
|
||||
// the handlers for clients to do whatever they want.
|
||||
private func initializePlugins(
|
||||
pluginLoader: PluginLoader,
|
||||
log: Logger,
|
||||
routes: inout [XPCRoute: XPCServer.RouteHandler]
|
||||
) async throws {
|
||||
log.info("initializing plugins")
|
||||
|
||||
let bootPlugins = pluginLoader.findPlugins().filter { $0.shouldBoot }
|
||||
|
||||
let service = PluginsService(pluginLoader: pluginLoader, log: log)
|
||||
try await service.loadAll(bootPlugins)
|
||||
|
||||
let harness = PluginsHarness(service: service, log: log)
|
||||
routes[XPCRoute.pluginGet] = harness.get
|
||||
routes[XPCRoute.pluginList] = harness.list
|
||||
routes[XPCRoute.pluginLoad] = harness.load
|
||||
routes[XPCRoute.pluginUnload] = harness.unload
|
||||
routes[XPCRoute.pluginRestart] = harness.restart
|
||||
}
|
||||
|
||||
private func initializeHealthCheckService(log: Logger, routes: inout [XPCRoute: XPCServer.RouteHandler]) {
|
||||
log.info("initializing health check service")
|
||||
|
||||
let svc = HealthCheckHarness(appRoot: appRoot, installRoot: installRoot, log: log)
|
||||
routes[XPCRoute.ping] = svc.ping
|
||||
}
|
||||
|
||||
private func initializeKernelService(log: Logger, routes: inout [XPCRoute: XPCServer.RouteHandler]) throws {
|
||||
log.info("initializing kernel service")
|
||||
|
||||
let svc = try KernelService(log: log, appRoot: appRoot)
|
||||
let harness = KernelHarness(service: svc, log: log)
|
||||
routes[XPCRoute.installKernel] = harness.install
|
||||
routes[XPCRoute.getDefaultKernel] = harness.getDefaultKernel
|
||||
}
|
||||
|
||||
private func initializeContainerService(pluginLoader: PluginLoader, log: Logger, routes: inout [XPCRoute: XPCServer.RouteHandler]) throws -> ContainersService {
|
||||
log.info("initializing container service")
|
||||
|
||||
let service = try ContainersService(
|
||||
appRoot: appRoot,
|
||||
pluginLoader: pluginLoader,
|
||||
log: log
|
||||
)
|
||||
let harness = ContainersHarness(service: service, log: log)
|
||||
|
||||
routes[XPCRoute.containerList] = harness.list
|
||||
routes[XPCRoute.containerCreate] = harness.create
|
||||
routes[XPCRoute.containerDelete] = harness.delete
|
||||
routes[XPCRoute.containerLogs] = harness.logs
|
||||
routes[XPCRoute.containerBootstrap] = harness.bootstrap
|
||||
routes[XPCRoute.containerDial] = harness.dial
|
||||
routes[XPCRoute.containerStop] = harness.stop
|
||||
routes[XPCRoute.containerStartProcess] = harness.startProcess
|
||||
routes[XPCRoute.containerCreateProcess] = harness.createProcess
|
||||
routes[XPCRoute.containerResize] = harness.resize
|
||||
routes[XPCRoute.containerWait] = harness.wait
|
||||
routes[XPCRoute.containerKill] = harness.kill
|
||||
|
||||
return service
|
||||
}
|
||||
|
||||
private func initializeNetworkService(
|
||||
pluginLoader: PluginLoader,
|
||||
containersService: ContainersService,
|
||||
log: Logger,
|
||||
routes: inout [XPCRoute: XPCServer.RouteHandler]
|
||||
) async throws -> NetworksService {
|
||||
log.info("initializing network service")
|
||||
|
||||
let resourceRoot = appRoot.appendingPathComponent("networks")
|
||||
let service = try await NetworksService(
|
||||
pluginLoader: pluginLoader,
|
||||
resourceRoot: resourceRoot,
|
||||
containersService: containersService,
|
||||
log: log
|
||||
)
|
||||
|
||||
let defaultNetwork = try await service.list()
|
||||
.filter { $0.id == ClientNetwork.defaultNetworkName }
|
||||
.first
|
||||
if defaultNetwork == nil {
|
||||
let config = try NetworkConfiguration(id: ClientNetwork.defaultNetworkName, mode: .nat)
|
||||
_ = try await service.create(configuration: config)
|
||||
}
|
||||
|
||||
let harness = NetworksHarness(service: service, log: log)
|
||||
|
||||
routes[XPCRoute.networkCreate] = harness.create
|
||||
routes[XPCRoute.networkDelete] = harness.delete
|
||||
routes[XPCRoute.networkList] = harness.list
|
||||
return service
|
||||
}
|
||||
|
||||
private func initializeVolumeService(
|
||||
containersService: ContainersService,
|
||||
log: Logger,
|
||||
routes: inout [XPCRoute: XPCServer.RouteHandler]
|
||||
) throws {
|
||||
log.info("initializing volume service")
|
||||
|
||||
let resourceRoot = appRoot.appendingPathComponent("volumes")
|
||||
let service = try VolumesService(resourceRoot: resourceRoot, containersService: containersService, log: log)
|
||||
let harness = VolumesHarness(service: service, log: log)
|
||||
|
||||
routes[XPCRoute.volumeCreate] = harness.create
|
||||
routes[XPCRoute.volumeDelete] = harness.delete
|
||||
routes[XPCRoute.volumeList] = harness.list
|
||||
routes[XPCRoute.volumeInspect] = harness.inspect
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,104 +15,20 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import ArgumentParser
|
||||
import ContainerAPIService
|
||||
import ContainerClient
|
||||
import ContainerLog
|
||||
import ContainerNetworkService
|
||||
import ContainerPlugin
|
||||
import ContainerVersion
|
||||
import ContainerXPC
|
||||
import ContainerizationError
|
||||
import ContainerizationExtras
|
||||
import ContainerizationOCI
|
||||
import ContainerizationOS
|
||||
import DNSServer
|
||||
import Foundation
|
||||
import Logging
|
||||
|
||||
@main
|
||||
struct APIServer: AsyncParsableCommand {
|
||||
static let listenAddress = "127.0.0.1"
|
||||
static let dnsPort = 2053
|
||||
|
||||
static let configuration = CommandConfiguration(
|
||||
commandName: "container-apiserver",
|
||||
abstract: "Container management API server",
|
||||
version: ReleaseVersion.singleLine(appName: "container-apiserver")
|
||||
version: ReleaseVersion.singleLine(appName: "container-apiserver"),
|
||||
subcommands: [Start.self],
|
||||
)
|
||||
|
||||
@Flag(name: .long, help: "Enable debug logging")
|
||||
var debug = false
|
||||
|
||||
var appRoot = ApplicationRoot.url
|
||||
|
||||
var installRoot = InstallRoot.url
|
||||
|
||||
func run() async throws {
|
||||
let commandName = Self.configuration.commandName ?? "container-apiserver"
|
||||
let log = setupLogger()
|
||||
log.info("starting \(commandName)")
|
||||
defer {
|
||||
log.info("stopping \(commandName)")
|
||||
}
|
||||
|
||||
do {
|
||||
log.info("configuring XPC server")
|
||||
var routes = [XPCRoute: XPCServer.RouteHandler]()
|
||||
let pluginLoader = try initializePluginLoader(log: log)
|
||||
try await initializePlugins(pluginLoader: pluginLoader, log: log, routes: &routes)
|
||||
let containersService = try initializeContainerService(
|
||||
pluginLoader: pluginLoader,
|
||||
log: log,
|
||||
routes: &routes
|
||||
)
|
||||
let networkService = try await initializeNetworkService(
|
||||
pluginLoader: pluginLoader,
|
||||
containersService: containersService,
|
||||
log: log,
|
||||
routes: &routes
|
||||
)
|
||||
initializeHealthCheckService(log: log, routes: &routes)
|
||||
try initializeKernelService(log: log, routes: &routes)
|
||||
try initializeVolumeService(containersService: containersService, log: log, routes: &routes)
|
||||
|
||||
let server = XPCServer(
|
||||
identifier: "com.apple.container.apiserver",
|
||||
routes: routes.reduce(
|
||||
into: [String: XPCServer.RouteHandler](),
|
||||
{
|
||||
$0[$1.key.rawValue] = $1.value
|
||||
}), log: log)
|
||||
|
||||
await withThrowingTaskGroup(of: Void.self) { group in
|
||||
group.addTask {
|
||||
log.info("starting XPC server")
|
||||
try await server.listen()
|
||||
}
|
||||
// start up host table DNS
|
||||
group.addTask {
|
||||
let hostsResolver = ContainerDNSHandler(networkService: networkService)
|
||||
let nxDomainResolver = NxDomainResolver()
|
||||
let compositeResolver = CompositeResolver(handlers: [hostsResolver, nxDomainResolver])
|
||||
let hostsQueryValidator = StandardQueryValidator(handler: compositeResolver)
|
||||
let dnsServer: DNSServer = DNSServer(handler: hostsQueryValidator, log: log)
|
||||
log.info(
|
||||
"starting DNS host query resolver",
|
||||
metadata: [
|
||||
"host": "\(Self.listenAddress)",
|
||||
"port": "\(Self.dnsPort)",
|
||||
]
|
||||
)
|
||||
try await dnsServer.run(host: Self.listenAddress, port: Self.dnsPort)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
log.error("\(commandName) failed", metadata: ["error": "\(error)"])
|
||||
APIServer.exit(withError: error)
|
||||
}
|
||||
}
|
||||
|
||||
private func setupLogger() -> Logger {
|
||||
static func setupLogger(debug: Bool) -> Logger {
|
||||
LoggingSystem.bootstrap { label in
|
||||
OSLogHandler(
|
||||
label: label,
|
||||
@@ -125,165 +41,4 @@ struct APIServer: AsyncParsableCommand {
|
||||
}
|
||||
return log
|
||||
}
|
||||
|
||||
private func initializePluginLoader(log: Logger) throws -> PluginLoader {
|
||||
log.info(
|
||||
"initializing plugin loader",
|
||||
metadata: [
|
||||
"installRoot": "\(installRoot.path(percentEncoded: false))"
|
||||
])
|
||||
|
||||
let pluginsURL = PluginLoader.userPluginsDir(installRoot: installRoot)
|
||||
log.info("detecting user plugins directory", metadata: ["path": "\(pluginsURL.path(percentEncoded: false))"])
|
||||
var directoryExists: ObjCBool = false
|
||||
_ = FileManager.default.fileExists(atPath: pluginsURL.path, isDirectory: &directoryExists)
|
||||
let userPluginsURL = directoryExists.boolValue ? pluginsURL : nil
|
||||
|
||||
// plugins built into the application installed as a macOS app bundle
|
||||
let appBundlePluginsURL = Bundle.main.resourceURL?.appending(path: "plugins")
|
||||
|
||||
// plugins built into the application installed as a Unix-like application
|
||||
let installRootPluginsURL =
|
||||
installRoot
|
||||
.appendingPathComponent("libexec")
|
||||
.appendingPathComponent("container")
|
||||
.appendingPathComponent("plugins")
|
||||
.standardized
|
||||
|
||||
let pluginDirectories = [
|
||||
userPluginsURL,
|
||||
appBundlePluginsURL,
|
||||
installRootPluginsURL,
|
||||
].compactMap { $0 }
|
||||
|
||||
let pluginFactories: [PluginFactory] = [
|
||||
DefaultPluginFactory(),
|
||||
AppBundlePluginFactory(),
|
||||
]
|
||||
|
||||
for pluginDirectory in pluginDirectories {
|
||||
log.info("discovered plugin directory", metadata: ["path": "\(pluginDirectory.path(percentEncoded: false))"])
|
||||
}
|
||||
|
||||
return try PluginLoader(
|
||||
appRoot: appRoot,
|
||||
installRoot: installRoot,
|
||||
pluginDirectories: pluginDirectories,
|
||||
pluginFactories: pluginFactories,
|
||||
log: log
|
||||
)
|
||||
}
|
||||
|
||||
// First load all of the plugins we can find. Then just expose
|
||||
// the handlers for clients to do whatever they want.
|
||||
private func initializePlugins(
|
||||
pluginLoader: PluginLoader,
|
||||
log: Logger,
|
||||
routes: inout [XPCRoute: XPCServer.RouteHandler]
|
||||
) async throws {
|
||||
log.info("initializing plugins")
|
||||
|
||||
let bootPlugins = pluginLoader.findPlugins().filter { $0.shouldBoot }
|
||||
|
||||
let service = PluginsService(pluginLoader: pluginLoader, log: log)
|
||||
try await service.loadAll(bootPlugins)
|
||||
|
||||
let harness = PluginsHarness(service: service, log: log)
|
||||
routes[XPCRoute.pluginGet] = harness.get
|
||||
routes[XPCRoute.pluginList] = harness.list
|
||||
routes[XPCRoute.pluginLoad] = harness.load
|
||||
routes[XPCRoute.pluginUnload] = harness.unload
|
||||
routes[XPCRoute.pluginRestart] = harness.restart
|
||||
}
|
||||
|
||||
private func initializeHealthCheckService(log: Logger, routes: inout [XPCRoute: XPCServer.RouteHandler]) {
|
||||
log.info("initializing health check service")
|
||||
|
||||
let svc = HealthCheckHarness(appRoot: appRoot, installRoot: installRoot, log: log)
|
||||
routes[XPCRoute.ping] = svc.ping
|
||||
}
|
||||
|
||||
private func initializeKernelService(log: Logger, routes: inout [XPCRoute: XPCServer.RouteHandler]) throws {
|
||||
log.info("initializing kernel service")
|
||||
|
||||
let svc = try KernelService(log: log, appRoot: appRoot)
|
||||
let harness = KernelHarness(service: svc, log: log)
|
||||
routes[XPCRoute.installKernel] = harness.install
|
||||
routes[XPCRoute.getDefaultKernel] = harness.getDefaultKernel
|
||||
}
|
||||
|
||||
private func initializeContainerService(pluginLoader: PluginLoader, log: Logger, routes: inout [XPCRoute: XPCServer.RouteHandler]) throws -> ContainersService {
|
||||
log.info("initializing container service")
|
||||
|
||||
let service = try ContainersService(
|
||||
appRoot: appRoot,
|
||||
pluginLoader: pluginLoader,
|
||||
log: log
|
||||
)
|
||||
let harness = ContainersHarness(service: service, log: log)
|
||||
|
||||
routes[XPCRoute.containerList] = harness.list
|
||||
routes[XPCRoute.containerCreate] = harness.create
|
||||
routes[XPCRoute.containerDelete] = harness.delete
|
||||
routes[XPCRoute.containerLogs] = harness.logs
|
||||
routes[XPCRoute.containerBootstrap] = harness.bootstrap
|
||||
routes[XPCRoute.containerDial] = harness.dial
|
||||
routes[XPCRoute.containerStop] = harness.stop
|
||||
routes[XPCRoute.containerStartProcess] = harness.startProcess
|
||||
routes[XPCRoute.containerCreateProcess] = harness.createProcess
|
||||
routes[XPCRoute.containerResize] = harness.resize
|
||||
routes[XPCRoute.containerWait] = harness.wait
|
||||
routes[XPCRoute.containerKill] = harness.kill
|
||||
|
||||
return service
|
||||
}
|
||||
|
||||
private func initializeNetworkService(
|
||||
pluginLoader: PluginLoader,
|
||||
containersService: ContainersService,
|
||||
log: Logger,
|
||||
routes: inout [XPCRoute: XPCServer.RouteHandler]
|
||||
) async throws -> NetworksService {
|
||||
log.info("initializing network service")
|
||||
|
||||
let resourceRoot = appRoot.appendingPathComponent("networks")
|
||||
let service = try await NetworksService(
|
||||
pluginLoader: pluginLoader,
|
||||
resourceRoot: resourceRoot,
|
||||
containersService: containersService,
|
||||
log: log
|
||||
)
|
||||
|
||||
let defaultNetwork = try await service.list()
|
||||
.filter { $0.id == ClientNetwork.defaultNetworkName }
|
||||
.first
|
||||
if defaultNetwork == nil {
|
||||
let config = try NetworkConfiguration(id: ClientNetwork.defaultNetworkName, mode: .nat)
|
||||
_ = try await service.create(configuration: config)
|
||||
}
|
||||
|
||||
let harness = NetworksHarness(service: service, log: log)
|
||||
|
||||
routes[XPCRoute.networkCreate] = harness.create
|
||||
routes[XPCRoute.networkDelete] = harness.delete
|
||||
routes[XPCRoute.networkList] = harness.list
|
||||
return service
|
||||
}
|
||||
|
||||
private func initializeVolumeService(
|
||||
containersService: ContainersService,
|
||||
log: Logger,
|
||||
routes: inout [XPCRoute: XPCServer.RouteHandler]
|
||||
) throws {
|
||||
log.info("initializing volume service")
|
||||
|
||||
let resourceRoot = appRoot.appendingPathComponent("volumes")
|
||||
let service = try VolumesService(resourceRoot: resourceRoot, containersService: containersService, log: log)
|
||||
let harness = VolumesHarness(service: service, log: log)
|
||||
|
||||
routes[XPCRoute.volumeCreate] = harness.create
|
||||
routes[XPCRoute.volumeDelete] = harness.delete
|
||||
routes[XPCRoute.volumeList] = harness.list
|
||||
routes[XPCRoute.volumeInspect] = harness.inspect
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Copyright © 2025 Apple Inc. and the container project authors. All rights reserved.
|
||||
//
|
||||
// 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 ArgumentParser
|
||||
import ContainerNetworkService
|
||||
import ContainerXPC
|
||||
import ContainerizationExtras
|
||||
import Foundation
|
||||
import Logging
|
||||
|
||||
extension NetworkVmnetHelper {
|
||||
struct Start: AsyncParsableCommand {
|
||||
static let configuration = CommandConfiguration(
|
||||
commandName: "start",
|
||||
abstract: "Starts the network plugin"
|
||||
)
|
||||
|
||||
@Flag(name: .long, help: "Enable debug logging")
|
||||
var debug = false
|
||||
|
||||
@Option(name: .long, help: "XPC service identifier")
|
||||
var serviceIdentifier: String
|
||||
|
||||
@Option(name: .shortAndLong, help: "Network identifier")
|
||||
var id: String
|
||||
|
||||
@Option(name: .shortAndLong, help: "CIDR address for the subnet")
|
||||
var subnet: String?
|
||||
|
||||
func run() async throws {
|
||||
let commandName = NetworkVmnetHelper._commandName
|
||||
let log = setupLogger(id: id, debug: debug)
|
||||
log.info("starting \(commandName)")
|
||||
defer {
|
||||
log.info("stopping \(commandName)")
|
||||
}
|
||||
|
||||
do {
|
||||
log.info("configuring XPC server")
|
||||
let subnet = try self.subnet.map { try CIDRAddress($0) }
|
||||
let configuration = try NetworkConfiguration(id: id, mode: .nat, subnet: subnet?.description)
|
||||
let network = try Self.createNetwork(configuration: configuration, log: log)
|
||||
try await network.start()
|
||||
let server = try await NetworkService(network: network, log: log)
|
||||
let xpc = XPCServer(
|
||||
identifier: serviceIdentifier,
|
||||
routes: [
|
||||
NetworkRoutes.state.rawValue: server.state,
|
||||
NetworkRoutes.allocate.rawValue: server.allocate,
|
||||
NetworkRoutes.deallocate.rawValue: server.deallocate,
|
||||
NetworkRoutes.lookup.rawValue: server.lookup,
|
||||
NetworkRoutes.disableAllocator.rawValue: server.disableAllocator,
|
||||
],
|
||||
log: log
|
||||
)
|
||||
|
||||
log.info("starting XPC server")
|
||||
try await xpc.listen()
|
||||
} catch {
|
||||
log.error("\(commandName) failed", metadata: ["error": "\(error)"])
|
||||
NetworkVmnetHelper.exit(withError: error)
|
||||
}
|
||||
}
|
||||
|
||||
private static func createNetwork(configuration: NetworkConfiguration, log: Logger) throws -> Network {
|
||||
guard #available(macOS 26, *) else {
|
||||
return try AllocationOnlyVmnetNetwork(configuration: configuration, log: log)
|
||||
}
|
||||
|
||||
return try ReservedVmnetNetwork(configuration: configuration, log: log)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,11 +16,7 @@
|
||||
|
||||
import ArgumentParser
|
||||
import ContainerLog
|
||||
import ContainerNetworkService
|
||||
import ContainerVersion
|
||||
import ContainerXPC
|
||||
import ContainerizationExtras
|
||||
import Foundation
|
||||
import Logging
|
||||
|
||||
@main
|
||||
@@ -33,83 +29,19 @@ struct NetworkVmnetHelper: AsyncParsableCommand {
|
||||
Start.self
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
extension NetworkVmnetHelper {
|
||||
struct Start: AsyncParsableCommand {
|
||||
static let configuration = CommandConfiguration(
|
||||
commandName: "start",
|
||||
abstract: "Starts the network plugin"
|
||||
)
|
||||
|
||||
@Flag(name: .long, help: "Enable debug logging")
|
||||
var debug = false
|
||||
|
||||
@Option(name: .long, help: "XPC service identifier")
|
||||
var serviceIdentifier: String
|
||||
|
||||
@Option(name: .shortAndLong, help: "Network identifier")
|
||||
var id: String
|
||||
|
||||
@Option(name: .shortAndLong, help: "CIDR address for the subnet")
|
||||
var subnet: String?
|
||||
|
||||
func run() async throws {
|
||||
let commandName = NetworkVmnetHelper._commandName
|
||||
let log = setupLogger()
|
||||
log.info("starting \(commandName)")
|
||||
defer {
|
||||
log.info("stopping \(commandName)")
|
||||
}
|
||||
|
||||
do {
|
||||
log.info("configuring XPC server")
|
||||
let subnet = try self.subnet.map { try CIDRAddress($0) }
|
||||
let configuration = try NetworkConfiguration(id: id, mode: .nat, subnet: subnet?.description)
|
||||
let network = try Self.createNetwork(configuration: configuration, log: log)
|
||||
try await network.start()
|
||||
let server = try await NetworkService(network: network, log: log)
|
||||
let xpc = XPCServer(
|
||||
identifier: serviceIdentifier,
|
||||
routes: [
|
||||
NetworkRoutes.state.rawValue: server.state,
|
||||
NetworkRoutes.allocate.rawValue: server.allocate,
|
||||
NetworkRoutes.deallocate.rawValue: server.deallocate,
|
||||
NetworkRoutes.lookup.rawValue: server.lookup,
|
||||
NetworkRoutes.disableAllocator.rawValue: server.disableAllocator,
|
||||
],
|
||||
log: log
|
||||
)
|
||||
|
||||
log.info("starting XPC server")
|
||||
try await xpc.listen()
|
||||
} catch {
|
||||
log.error("\(commandName) failed", metadata: ["error": "\(error)"])
|
||||
NetworkVmnetHelper.exit(withError: error)
|
||||
}
|
||||
static func setupLogger(id: String, debug: Bool) -> Logger {
|
||||
LoggingSystem.bootstrap { label in
|
||||
OSLogHandler(
|
||||
label: label,
|
||||
category: "NetworkVmnetHelper"
|
||||
)
|
||||
}
|
||||
|
||||
private func setupLogger() -> Logger {
|
||||
LoggingSystem.bootstrap { label in
|
||||
OSLogHandler(
|
||||
label: label,
|
||||
category: "NetworkVmnetHelper"
|
||||
)
|
||||
}
|
||||
var log = Logger(label: "com.apple.container")
|
||||
if debug {
|
||||
log.logLevel = .debug
|
||||
}
|
||||
log[metadataKey: "id"] = "\(id)"
|
||||
return log
|
||||
}
|
||||
|
||||
private static func createNetwork(configuration: NetworkConfiguration, log: Logger) throws -> Network {
|
||||
guard #available(macOS 26, *) else {
|
||||
return try AllocationOnlyVmnetNetwork(configuration: configuration, log: log)
|
||||
}
|
||||
|
||||
return try ReservedVmnetNetwork(configuration: configuration, log: log)
|
||||
var log = Logger(label: "com.apple.container")
|
||||
if debug {
|
||||
log.logLevel = .debug
|
||||
}
|
||||
log[metadataKey: "id"] = "\(id)"
|
||||
return log
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user