diff --git a/Sources/ContainerCommands/System/SystemStart.swift b/Sources/ContainerCommands/System/SystemStart.swift index 77c9a9d3..65974f27 100644 --- a/Sources/ContainerCommands/System/SystemStart.swift +++ b/Sources/ContainerCommands/System/SystemStart.swift @@ -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 diff --git a/Sources/Helpers/APIServer/APIServer+Start.swift b/Sources/Helpers/APIServer/APIServer+Start.swift new file mode 100644 index 00000000..cc1a527c --- /dev/null +++ b/Sources/Helpers/APIServer/APIServer+Start.swift @@ -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 + } + } +} diff --git a/Sources/Helpers/APIServer/APIServer.swift b/Sources/Helpers/APIServer/APIServer.swift index b40221b4..39d5a62d 100644 --- a/Sources/Helpers/APIServer/APIServer.swift +++ b/Sources/Helpers/APIServer/APIServer.swift @@ -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 - } } diff --git a/Sources/Helpers/NetworkVmnet/NetworkVmnetHelper+Start.swift b/Sources/Helpers/NetworkVmnet/NetworkVmnetHelper+Start.swift new file mode 100644 index 00000000..baba3e32 --- /dev/null +++ b/Sources/Helpers/NetworkVmnet/NetworkVmnetHelper+Start.swift @@ -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) + } + } +} diff --git a/Sources/Helpers/NetworkVmnet/NetworkVmnetHelper.swift b/Sources/Helpers/NetworkVmnet/NetworkVmnetHelper.swift index 27a3b664..92132226 100644 --- a/Sources/Helpers/NetworkVmnet/NetworkVmnetHelper.swift +++ b/Sources/Helpers/NetworkVmnet/NetworkVmnetHelper.swift @@ -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 } }