396 lines
13 KiB
Swift
396 lines
13 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
// 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: <type>=<soft>[:<hard>])",
|
|
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: <name>[,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
|
|
}
|
|
}
|