Files
container/Sources/ContainerClient/Flags.swift
T
Santosh Bhavani f7bcb687fd Add --max-concurrent-downloads flag for parallel layer downloads (#716)
Adds `--max-concurrent-downloads` flag to `container image pull` for
configurable concurrent layer downloads.

Fixes #715
Depends on apple/containerization#311

**Usage**:
```bash
container image pull nginx:latest --max-concurrent-downloads 6
```

**Changes**:
- Add CLI flag (default: 3)
- Thread parameter through XPC stack
- Update to use forked containerization with configurable concurrency

**Performance**: ~1.2-1.3x faster pulls for multi-layer images with
higher concurrency

**Tests**: Included standalone tests verify concurrency behavior and
parameter flow

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-07 15:56:50 -03:00

226 lines
7.7 KiB
Swift

//===----------------------------------------------------------------------===//
// Copyright © 2025 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 Global: ParsableArguments {
public init() {}
@Flag(name: .long, help: "Enable debug output [environment: CONTAINER_DEBUG]")
public var debug = false
}
public struct Process: ParsableArguments {
public init() {}
@Option(name: .shortAndLong, help: "Set environment variables (format: key=value)")
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?
}
public struct Resource: ParsableArguments {
public init() {}
@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 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() {}
@Option(name: .shortAndLong, help: "Set arch if image can target multiple architectures")
public var arch: String = Arch.hostArchitecture().rawValue
@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
@Option(
name: .customLong("dns"),
help: .init("DNS nameserver IP address", valueName: "ip")
)
public var dnsNameservers: [String] = []
@Option(
name: .long,
help: .init("Default DNS domain", valueName: "domain")
)
public var dnsDomain: String? = nil
@Option(
name: .customLong("dns-option"),
help: .init("DNS options", valueName: "option")
)
public var dnsOptions: [String] = []
@Option(
name: .customLong("dns-search"),
help: .init("DNS search domains", valueName: "domain")
)
public var dnsSearchDomains: [String] = []
@Option(
name: .long,
help: .init(
"Override the entrypoint of the image",
valueName: "cmd"
)
)
public var entrypoint: 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])")
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")
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: [.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
@Flag(name: .long, help: "Forward SSH agent socket to container")
public var ssh = false
@Option(name: .customLong("tmpfs"), help: "Add a tmpfs mount to the container at the given path")
public var tmpFs: [String] = []
@Option(name: [.customLong("volume"), .short], help: "Bind mount a volume into the container")
public var volumes: [String] = []
@Flag(
name: .long,
help:
"Expose virtualization capabilities to the container (requires host and guest support)"
)
public var virtualization: Bool = false
}
public struct Progress: ParsableArguments {
public init() {}
public enum ProgressType: String, ExpressibleByArgument {
case none
case ansi
}
@Option(name: .long, help: ArgumentHelp("Progress type (format: none|ansi)", valueName: "type"))
public var progress: ProgressType = .ansi
}
public struct ImageFetch: ParsableArguments {
public init() {}
@Option(name: .long, help: "Maximum number of concurrent downloads (default: 3)")
public var maxConcurrentDownloads: Int = 3
}
}