Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
99c5fbd
add configuration and expose top level application struct
Mcrich23 Sep 11, 2025
ac56aaf
expose builder commands
Mcrich23 Sep 11, 2025
4a3556b
expose Container commands
Mcrich23 Sep 11, 2025
d94738d
expose Image commands
Mcrich23 Sep 11, 2025
63e21f6
expose Network commands
Mcrich23 Sep 11, 2025
521929e
expose Registry commands
Mcrich23 Sep 11, 2025
a0dd8a8
expose System commands
Mcrich23 Sep 11, 2025
e44472a
expose ContainerCLI as an package
Mcrich23 Sep 11, 2025
92a1e14
expose Volume commands
Mcrich23 Sep 11, 2025
f8759a5
expose BuildCommand
Mcrich23 Sep 11, 2025
a674306
expose RunCommand, ProcessIO, and OSFile
Mcrich23 Sep 11, 2025
f26ff6a
add missing public inits
Mcrich23 Sep 11, 2025
1129c41
fix BuildCommand and RunCommand swift concurrency errors
Mcrich23 Sep 11, 2025
e993279
Update Application.swift
Mcrich23 Sep 11, 2025
fe358ac
Merge branch 'main' into expose-command-structs-for-plugins
Mcrich23 Sep 12, 2025
db5315d
Merge branch 'main' into expose-command-structs-for-plugins
Mcrich23 Sep 13, 2025
eb09db8
revert making properties and functions of commands public
Mcrich23 Sep 14, 2025
7b4de96
made run and validate functions public due to protocol requirements
Mcrich23 Sep 14, 2025
dfe9710
make CLI option groups public properties
Mcrich23 Sep 14, 2025
b22bc7f
Merge branch 'main' into expose-command-structs-for-plugins
Mcrich23 Sep 15, 2025
8e3dd44
Revert "make CLI option groups public properties"
Mcrich23 Sep 17, 2025
b1d2020
Squashed commit of the following:
Mcrich23 Sep 17, 2025
cf391d0
Merge branch 'main' into expose-command-structs-for-plugins
Mcrich23 Sep 17, 2025
bc425b8
make Application.global private
Mcrich23 Sep 17, 2025
aea87ca
git artifacting fix
Mcrich23 Sep 17, 2025
96a064b
make fmt fixes
Mcrich23 Sep 17, 2025
a58de6f
rename ContainerCLI target to ContainerCommands
Mcrich23 Sep 17, 2025
347ccc5
formatting fixes
Mcrich23 Sep 17, 2025
4e72193
Merge branch 'main' into expose-command-structs-for-plugins
Mcrich23 Sep 17, 2025
011e071
Merge branch 'main' into expose-command-structs-for-plugins
Mcrich23 Sep 17, 2025
4323aa3
access control fixes
Mcrich23 Sep 17, 2025
3dce3b1
Update SystemProperty.swift
Mcrich23 Sep 17, 2025
a06e18d
formatting fixes
Mcrich23 Sep 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ let package = Package(
.library(name: "ContainerSandboxService", targets: ["ContainerSandboxService"]),
.library(name: "ContainerNetworkService", targets: ["ContainerNetworkService"]),
.library(name: "ContainerImagesService", targets: ["ContainerImagesService", "ContainerImagesServiceClient"]),
.library(name: "ContainerCommands", targets: ["ContainerCommands"]),
.library(name: "ContainerClient", targets: ["ContainerClient"]),
.library(name: "ContainerBuild", targets: ["ContainerBuild"]),
.library(name: "ContainerLog", targets: ["ContainerLog"]),
Expand Down Expand Up @@ -65,6 +66,15 @@ let package = Package(
targets: [
.executableTarget(
name: "container",
dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser"),
"ContainerClient",
"ContainerCommands",
],
path: "Sources/ExecutableCLI"
),
.target(
name: "ContainerCommands",
dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser"),
.product(name: "Logging", package: "swift-log"),
Expand Down
15 changes: 8 additions & 7 deletions Sources/CLI/Application.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,13 @@ nonisolated(unsafe) var log = {
return log
}()

@main
struct Application: AsyncParsableCommand {
public struct Application: AsyncParsableCommand {
@OptionGroup
var global: Flags.Global

static let configuration = CommandConfiguration(
public init() {}

public static let configuration = CommandConfiguration(
commandName: "container",
abstract: "A container platform for macOS",
version: ReleaseVersion.singleLine(appName: "container CLI"),
Expand Down Expand Up @@ -126,7 +127,7 @@ struct Application: AsyncParsableCommand {
}
}

static func createPluginLoader() async throws -> PluginLoader {
public static func createPluginLoader() async throws -> PluginLoader {
let installRoot = CommandLine.executablePathUrl
.deletingLastPathComponent()
.appendingPathComponent("..")
Expand Down Expand Up @@ -170,7 +171,7 @@ struct Application: AsyncParsableCommand {
)
}

static func handleProcess(io: ProcessIO, process: ClientProcess) async throws -> Int32 {
public static func handleProcess(io: ProcessIO, process: ClientProcess) async throws -> Int32 {
let signals = AsyncSignalHandler.create(notify: Application.signalSet)
return try await withThrowingTaskGroup(of: Int32?.self, returning: Int32.self) { group in
let waitAdded = group.addTaskUnlessCancelled {
Expand Down Expand Up @@ -242,7 +243,7 @@ struct Application: AsyncParsableCommand {
}
}

func validate() throws {
public func validate() throws {
// Not really a "validation", but a cheat to run this before
// any of the commands do their business.
let debugEnvVar = ProcessInfo.processInfo.environment["CONTAINER_DEBUG"]
Expand Down Expand Up @@ -308,7 +309,7 @@ extension Application {
print(altered)
}

enum ListFormat: String, CaseIterable, ExpressibleByArgument {
public enum ListFormat: String, CaseIterable, ExpressibleByArgument {
case json
case table
}
Expand Down
21 changes: 11 additions & 10 deletions Sources/CLI/BuildCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ import NIO
import TerminalProgress

extension Application {
struct BuildCommand: AsyncParsableCommand {
public struct BuildCommand: AsyncParsableCommand {
public init() {}
public static var configuration: CommandConfiguration {
var config = CommandConfiguration()
config.commandName = "build"
Expand All @@ -38,7 +39,7 @@ extension Application {
}

@Option(name: [.customLong("cpus"), .customShort("c")], help: "Number of CPUs to allocate to the container")
public var cpus: Int64 = 2
var cpus: Int64 = 2

@Option(
name: [.customLong("memory"), .customShort("m")],
Expand Down Expand Up @@ -117,7 +118,7 @@ extension Application {
@Flag(name: .shortAndLong, help: "Suppress build output")
var quiet: Bool = false

func run() async throws {
public func run() async throws {
do {
let timeout: Duration = .seconds(300)
let progressConfig = try ProgressConfig(
Expand All @@ -132,16 +133,16 @@ extension Application {

progress.set(description: "Dialing builder")

let builder: Builder? = try await withThrowingTaskGroup(of: Builder.self) { group in
let builder: Builder? = try await withThrowingTaskGroup(of: Builder.self) { [vsockPort, cpus, memory] group in
defer {
group.cancelAll()
}

group.addTask {
group.addTask { [vsockPort, cpus, memory] in
while true {
do {
let container = try await ClientContainer.get(id: "buildkit")
let fh = try await container.dial(self.vsockPort)
let fh = try await container.dial(vsockPort)

let threadGroup: MultiThreadedEventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
let b = try Builder(socket: fh, group: threadGroup)
Expand All @@ -156,8 +157,8 @@ extension Application {
progress.set(totalTasks: 3)

try await BuilderStart.start(
cpus: self.cpus,
memory: self.memory,
cpus: cpus,
memory: memory,
progressUpdate: progress.handler
)

Expand Down Expand Up @@ -255,7 +256,7 @@ extension Application {
}
return results
}()
group.addTask { [terminal] in
group.addTask { [terminal, buildArg, contextDir, label, noCache, target, quiet, cacheIn, cacheOut] in
let config = ContainerBuild.Builder.BuildConfig(
buildID: buildID,
contentStore: RemoteContentStoreClient(),
Expand Down Expand Up @@ -341,7 +342,7 @@ extension Application {
}
}

func validate() throws {
public func validate() throws {
guard FileManager.default.fileExists(atPath: file) else {
throw ValidationError("Dockerfile does not exist at path: \(file)")
}
Expand Down
6 changes: 4 additions & 2 deletions Sources/CLI/Builder/Builder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
import ArgumentParser

extension Application {
struct BuilderCommand: AsyncParsableCommand {
static let configuration = CommandConfiguration(
public struct BuilderCommand: AsyncParsableCommand {
public init() {}

public static let configuration = CommandConfiguration(
commandName: "builder",
abstract: "Manage an image builder instance",
subcommands: [
Expand Down
6 changes: 4 additions & 2 deletions Sources/CLI/Builder/BuilderDelete.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ import ContainerizationError
import Foundation

extension Application {
struct BuilderDelete: AsyncParsableCommand {
public struct BuilderDelete: AsyncParsableCommand {
public init() {}

public static var configuration: CommandConfiguration {
var config = CommandConfiguration()
config.commandName = "delete"
Expand All @@ -35,7 +37,7 @@ extension Application {
@Flag(name: .shortAndLong, help: "Force delete builder even if it is running")
var force = false

func run() async throws {
public func run() async throws {
do {
let container = try await ClientContainer.get(id: "buildkit")
if container.status != .stopped {
Expand Down
10 changes: 6 additions & 4 deletions Sources/CLI/Builder/BuilderStart.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ import Foundation
import TerminalProgress

extension Application {
struct BuilderStart: AsyncParsableCommand {
public struct BuilderStart: AsyncParsableCommand {
public init() {}

public static var configuration: CommandConfiguration {
var config = CommandConfiguration()
config.commandName = "start"
Expand All @@ -39,16 +41,16 @@ extension Application {
}

@Option(name: [.customLong("cpus"), .customShort("c")], help: "Number of CPUs to allocate to the container")
public var cpus: Int64 = 2
var cpus: Int64 = 2

@Option(
name: [.customLong("memory"), .customShort("m")],
help:
"Amount of memory in bytes, kilobytes (K), megabytes (M), or gigabytes (G) for the container, with MB granularity (for example, 1024K will result in 1MB being allocated for the container)"
)
public var memory: String = "2048MB"
var memory: String = "2048MB"

func run() async throws {
public func run() async throws {
let progressConfig = try ProgressConfig(
showTasks: true,
showItems: true,
Expand Down
6 changes: 4 additions & 2 deletions Sources/CLI/Builder/BuilderStatus.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ import ContainerizationError
import Foundation

extension Application {
struct BuilderStatus: AsyncParsableCommand {
public struct BuilderStatus: AsyncParsableCommand {
public init() {}

public static var configuration: CommandConfiguration {
var config = CommandConfiguration()
config.commandName = "status"
Expand All @@ -34,7 +36,7 @@ extension Application {
@Flag(name: .long, help: ArgumentHelp("Display detailed status in json format"))
var json: Bool = false

func run() async throws {
public func run() async throws {
do {
let container = try await ClientContainer.get(id: "buildkit")
if json {
Expand Down
6 changes: 4 additions & 2 deletions Sources/CLI/Builder/BuilderStop.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ import ContainerizationError
import Foundation

extension Application {
struct BuilderStop: AsyncParsableCommand {
public struct BuilderStop: AsyncParsableCommand {
public init() {}

public static var configuration: CommandConfiguration {
var config = CommandConfiguration()
config.commandName = "stop"
Expand All @@ -31,7 +33,7 @@ extension Application {
return config
}

func run() async throws {
public func run() async throws {
do {
let container = try await ClientContainer.get(id: "buildkit")
try await container.stop()
Expand Down
8 changes: 5 additions & 3 deletions Sources/CLI/Container/ContainerCreate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ import Foundation
import TerminalProgress

extension Application {
struct ContainerCreate: AsyncParsableCommand {
static let configuration = CommandConfiguration(
public struct ContainerCreate: AsyncParsableCommand {
public init() {}

public static let configuration = CommandConfiguration(
commandName: "create",
abstract: "Create a new container")

Expand All @@ -47,7 +49,7 @@ extension Application {
@OptionGroup
var global: Flags.Global

func run() async throws {
public func run() async throws {
let progressConfig = try ProgressConfig(
showTasks: true,
showItems: true,
Expand Down
10 changes: 6 additions & 4 deletions Sources/CLI/Container/ContainerDelete.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ import ContainerizationError
import Foundation

extension Application {
struct ContainerDelete: AsyncParsableCommand {
static let configuration = CommandConfiguration(
public struct ContainerDelete: AsyncParsableCommand {
public init() {}

public static let configuration = CommandConfiguration(
commandName: "delete",
abstract: "Delete one or more containers",
aliases: ["rm"])
Expand All @@ -38,7 +40,7 @@ extension Application {
@Argument(help: "Container IDs/names")
var containerIDs: [String] = []

func validate() throws {
public func validate() throws {
if containerIDs.count == 0 && !all {
throw ContainerizationError(.invalidArgument, message: "no containers specified and --all not supplied")
}
Expand All @@ -50,7 +52,7 @@ extension Application {
}
}

mutating func run() async throws {
public mutating func run() async throws {
let set = Set<String>(containerIDs)
var containers = [ClientContainer]()

Expand Down
8 changes: 5 additions & 3 deletions Sources/CLI/Container/ContainerExec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ import ContainerizationOS
import Foundation

extension Application {
struct ContainerExec: AsyncParsableCommand {
static let configuration = CommandConfiguration(
public struct ContainerExec: AsyncParsableCommand {
public init() {}

public static let configuration = CommandConfiguration(
commandName: "exec",
abstract: "Run a new command in a running container")

Expand All @@ -38,7 +40,7 @@ extension Application {
@Argument(parsing: .captureForPassthrough, help: "New process arguments")
var arguments: [String]

func run() async throws {
public func run() async throws {
var exitCode: Int32 = 127
let container = try await ClientContainer.get(id: containerID)
try ensureRunning(container: container)
Expand Down
8 changes: 5 additions & 3 deletions Sources/CLI/Container/ContainerInspect.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ import Foundation
import SwiftProtobuf

extension Application {
struct ContainerInspect: AsyncParsableCommand {
static let configuration = CommandConfiguration(
public struct ContainerInspect: AsyncParsableCommand {
public init() {}

public static let configuration = CommandConfiguration(
commandName: "inspect",
abstract: "Display information about one or more containers")

Expand All @@ -31,7 +33,7 @@ extension Application {
@Argument(help: "Containers to inspect")
var containers: [String]

func run() async throws {
public func run() async throws {
let objects: [any Codable] = try await ClientContainer.list().filter {
containers.contains($0.id)
}.map {
Expand Down
10 changes: 6 additions & 4 deletions Sources/CLI/Container/ContainerKill.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ import ContainerizationOS
import Darwin

extension Application {
struct ContainerKill: AsyncParsableCommand {
static let configuration = CommandConfiguration(
public struct ContainerKill: AsyncParsableCommand {
public init() {}

public static let configuration = CommandConfiguration(
commandName: "kill",
abstract: "Kill one or more running containers")

Expand All @@ -38,7 +40,7 @@ extension Application {
@OptionGroup
var global: Flags.Global

func validate() throws {
public func validate() throws {
if containerIDs.count == 0 && !all {
throw ContainerizationError(.invalidArgument, message: "no containers specified and --all not supplied")
}
Expand All @@ -47,7 +49,7 @@ extension Application {
}
}

mutating func run() async throws {
public mutating func run() async throws {
let set = Set<String>(containerIDs)

var containers = try await ClientContainer.list().filter { c in
Expand Down
Loading