//===----------------------------------------------------------------------===// // 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 ArgumentParser import ContainerizationError import Foundation public struct Flags { public struct Logging: ParsableArguments { public init() {} public init(debug: Bool) { self.debug = debug } @Flag(name: .long, help: "Enable debug output [environment: CONTAINER_DEBUG]") public var debug = false } public struct Process: ParsableArguments { public init() {} public init( cwd: String?, env: [String], envFile: [String], gid: UInt32?, interactive: Bool, tty: Bool, uid: UInt32?, ulimits: [String], user: String? ) { self.cwd = cwd self.env = env self.envFile = envFile self.gid = gid self.interactive = interactive self.tty = tty self.uid = uid self.ulimits = ulimits self.user = user } @Option(name: .shortAndLong, help: "Set environment variables (key=value, or just key to inherit from host)") public var env: [String] = [] @Option( name: .long, help: "Read in a file of environment variables (key=value format, ignores # comments and blank lines)" ) public var envFile: [String] = [] @Option(name: .long, help: "Set the group ID for the process") public var gid: UInt32? @Flag(name: .shortAndLong, help: "Keep the standard input open even if not attached") public var interactive = false @Flag(name: .shortAndLong, help: "Open a TTY with the process") public var tty = false @Option(name: .shortAndLong, help: "Set the user for the process (format: name|uid[:gid])") public var user: String? @Option(name: .long, help: "Set the user ID for the process") public var uid: UInt32? @Option( name: [.customShort("w"), .customLong("workdir"), .long], help: .init( "Set the initial working directory inside the container", valueName: "dir" ) ) public var cwd: String? @Option( name: .customLong("ulimit"), help: .init( "Set resource limits (format: =[:])", valueName: "limit" ) ) public var ulimits: [String] = [] } public struct Resource: ParsableArguments { public init() {} public init(cpus: Int64?, memory: String?) { self.cpus = cpus self.memory = memory } @Option(name: .shortAndLong, help: "Number of CPUs to allocate to the container") public var cpus: Int64? @Option( name: .shortAndLong, help: "Amount of memory (1MiByte granularity), with optional K, M, G, T, or P suffix" ) public var memory: String? } public struct DNS: ParsableArguments { public init() {} public init(domain: String?, nameservers: [String], options: [String], searchDomains: [String]) { self.domain = domain self.nameservers = nameservers self.options = options self.searchDomains = searchDomains } @Option( name: .customLong("dns"), help: .init("DNS nameserver IP address", valueName: "ip") ) public var nameservers: [String] = [] @Option( name: .customLong("dns-domain"), help: .init("Default DNS domain", valueName: "domain") ) public var domain: String? = nil @Option( name: .customLong("dns-option"), help: .init("DNS options", valueName: "option") ) public var options: [String] = [] @Option( name: .customLong("dns-search"), help: .init("DNS search domains", valueName: "domain") ) public var searchDomains: [String] = [] } public struct Registry: ParsableArguments { public init() {} public init(scheme: String) { self.scheme = scheme } @Option(help: "Scheme to use when connecting to the container registry. One of (http, https, auto)") public var scheme: String = "auto" } public struct Management: ParsableArguments { public init() {} public init( arch: String, capAdd: [String], capDrop: [String], cidfile: String, detach: Bool, dns: Flags.DNS, dnsDisabled: Bool, entrypoint: String?, initImage: String?, kernel: String?, labels: [String], mounts: [String], name: String?, networks: [String], os: String, platform: String?, publishPorts: [String], publishSockets: [String], readOnly: Bool, remove: Bool, rosetta: Bool, runtime: String?, ssh: Bool, shmSize: String?, tmpFs: [String], useInit: Bool, virtualization: Bool, volumes: [String] ) { self.arch = arch self.capAdd = capAdd self.capDrop = capDrop self.cidfile = cidfile self.detach = detach self.dns = dns self.dnsDisabled = dnsDisabled self.entrypoint = entrypoint self.initImage = initImage self.kernel = kernel self.labels = labels self.mounts = mounts self.name = name self.networks = networks self.os = os self.platform = platform self.publishPorts = publishPorts self.publishSockets = publishSockets self.readOnly = readOnly self.remove = remove self.rosetta = rosetta self.runtime = runtime self.ssh = ssh self.shmSize = shmSize self.tmpFs = tmpFs self.useInit = useInit self.virtualization = virtualization self.volumes = volumes } @Option(name: .shortAndLong, help: "Set arch if image can target multiple architectures") public var arch: String = Arch.hostArchitecture().rawValue @Option( name: .customLong("cap-add"), help: .init("Add a Linux capability (e.g. CAP_NET_RAW, or ALL)", valueName: "cap") ) public var capAdd: [String] = [] @Option( name: .customLong("cap-drop"), help: .init("Drop a Linux capability (e.g. CAP_NET_RAW, or ALL)", valueName: "cap") ) public var capDrop: [String] = [] @Option(name: .long, help: "Write the container ID to the path provided") public var cidfile = "" @Flag(name: .shortAndLong, help: "Run the container and detach from the process") public var detach = false @OptionGroup public var dns: Flags.DNS @Option( name: .long, help: .init( "Override the entrypoint of the image", valueName: "cmd" ) ) public var entrypoint: String? @Flag(name: .customLong("init"), help: "Run an init process inside the container that forwards signals and reaps processes") public var useInit = false @Option( name: .long, help: .init("Use a custom init image instead of the default", valueName: "image") ) public var initImage: String? @Option( name: .shortAndLong, help: .init("Set a custom kernel path", valueName: "path"), completion: .file(), transform: { str in URL(fileURLWithPath: str, relativeTo: .currentDirectory()).absoluteURL.path(percentEncoded: false) } ) public var kernel: String? @Option(name: [.short, .customLong("label")], help: "Add a key=value label to the container") public var labels: [String] = [] @Option(name: .customLong("mount"), help: "Add a mount to the container (format: type=<>,source=<>,target=<>,readonly)") public var mounts: [String] = [] @Option(name: .long, help: "Use the specified name as the container ID") public var name: String? @Option(name: [.customLong("network")], help: "Attach the container to a network (format: [,mac=XX:XX:XX:XX:XX:XX][,mtu=VALUE])") public var networks: [String] = [] @Flag(name: [.customLong("no-dns")], help: "Do not configure DNS in the container") public var dnsDisabled = false @Option(name: .long, help: "Set OS if image can target multiple operating systems") public var os = "linux" @Option( name: [.customShort("p"), .customLong("publish")], help: .init( "Publish a port from container to host (format: [host-ip:]host-port:container-port[/protocol])", valueName: "spec" ) ) public var publishPorts: [String] = [] @Option(name: .long, help: "Platform for the image if it's multi-platform. This takes precedence over --os and --arch [environment: CONTAINER_DEFAULT_PLATFORM]") public var platform: String? @Option( name: .customLong("publish-socket"), help: .init( "Publish a socket from container to host (format: host_path:container_path)", valueName: "spec" ) ) public var publishSockets: [String] = [] @Flag(name: .long, help: "Mount the container's root filesystem as read-only") public var readOnly = false @Flag(name: [.customLong("rm"), .long], help: "Remove the container after it stops") public var remove = false @Flag(name: .long, help: "Enable Rosetta in the container") public var rosetta = false @Option(name: .long, help: "Set the runtime handler for the container (default: container-runtime-linux)") public var runtime: String? @Flag(name: .long, help: "Forward SSH agent socket to container") public var ssh = false @Option(name: .customLong("shm-size"), help: "Size of /dev/shm (e.g. 64M, 1G)") public var shmSize: String? @Option(name: .customLong("tmpfs"), help: "Add a tmpfs mount to the container at the given path") public var tmpFs: [String] = [] @Flag( name: .long, help: "Expose virtualization capabilities to the container (requires host and guest support)" ) public var virtualization: Bool = false @Option(name: [.customLong("volume"), .short], help: "Bind mount a volume into the container") public var volumes: [String] = [] public func validate() throws { if dnsDisabled { let hasDNSConfig = !dns.nameservers.isEmpty || dns.domain != nil || !dns.options.isEmpty || !dns.searchDomains.isEmpty if hasDNSConfig { throw ValidationError( "`--no-dns` cannot be used with DNS configuration flags (`--dns`, `--dns-domain`, `--dns-option`, `--dns-search`)" ) } } } } public struct Progress: ParsableArguments { public init() {} public init(progress: ProgressType) { self.progress = progress } public enum ProgressType: String, ExpressibleByArgument { case auto case none case ansi case plain case color } @Option(name: .long, help: ArgumentHelp("Progress type (format: auto|none|ansi|plain|color)", valueName: "type")) public var progress: ProgressType = .auto } public struct ImageFetch: ParsableArguments { public init() {} public init(maxConcurrentDownloads: Int) { self.maxConcurrentDownloads = maxConcurrentDownloads } @Option(name: .long, help: "Maximum number of concurrent downloads") public var maxConcurrentDownloads: Int = 3 } }