Skip to content

Commit dd6bdc2

Browse files
authored
Expose Command Structs for Plugins (#603)
## Type of Change - [ ] Bug fix - [x] New feature - [ ] Breaking change - [ ] Documentation update ## Motivation and Context Plugins technically exist, but to add shortcuts or to do existing things with functions in `container` requires calling a compiled binary. This pull request aims to remove that hurdle and instability by exposing commands as a new `ContainerCommands ` target. Simply import `ContainerCommands` and you can access almost any command as if it were a native part of the binary. This makes plugin development significantly easier. Closes #609.
1 parent 3e52a3c commit dd6bdc2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+314
-190
lines changed

Package.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ let package = Package(
3333
.library(name: "ContainerSandboxService", targets: ["ContainerSandboxService"]),
3434
.library(name: "ContainerNetworkService", targets: ["ContainerNetworkService"]),
3535
.library(name: "ContainerImagesService", targets: ["ContainerImagesService", "ContainerImagesServiceClient"]),
36+
.library(name: "ContainerCommands", targets: ["ContainerCommands"]),
3637
.library(name: "ContainerClient", targets: ["ContainerClient"]),
3738
.library(name: "ContainerBuild", targets: ["ContainerBuild"]),
3839
.library(name: "ContainerLog", targets: ["ContainerLog"]),
@@ -65,6 +66,15 @@ let package = Package(
6566
targets: [
6667
.executableTarget(
6768
name: "container",
69+
dependencies: [
70+
.product(name: "ArgumentParser", package: "swift-argument-parser"),
71+
"ContainerClient",
72+
"ContainerCommands",
73+
],
74+
path: "Sources/ExecutableCLI"
75+
),
76+
.target(
77+
name: "ContainerCommands",
6878
dependencies: [
6979
.product(name: "ArgumentParser", package: "swift-argument-parser"),
7080
.product(name: "Logging", package: "swift-log"),

Sources/CLI/Application.swift

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,13 @@ nonisolated(unsafe) var log = {
3535
return log
3636
}()
3737

38-
@main
39-
struct Application: AsyncParsableCommand {
38+
public struct Application: AsyncParsableCommand {
4039
@OptionGroup
4140
var global: Flags.Global
4241

43-
static let configuration = CommandConfiguration(
42+
public init() {}
43+
44+
public static let configuration = CommandConfiguration(
4445
commandName: "container",
4546
abstract: "A container platform for macOS",
4647
version: ReleaseVersion.singleLine(appName: "container CLI"),
@@ -126,7 +127,7 @@ struct Application: AsyncParsableCommand {
126127
}
127128
}
128129

129-
static func createPluginLoader() async throws -> PluginLoader {
130+
public static func createPluginLoader() async throws -> PluginLoader {
130131
let installRoot = CommandLine.executablePathUrl
131132
.deletingLastPathComponent()
132133
.appendingPathComponent("..")
@@ -170,7 +171,7 @@ struct Application: AsyncParsableCommand {
170171
)
171172
}
172173

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

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

311-
enum ListFormat: String, CaseIterable, ExpressibleByArgument {
312+
public enum ListFormat: String, CaseIterable, ExpressibleByArgument {
312313
case json
313314
case table
314315
}

Sources/CLI/BuildCommand.swift

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ import NIO
2727
import TerminalProgress
2828

2929
extension Application {
30-
struct BuildCommand: AsyncParsableCommand {
30+
public struct BuildCommand: AsyncParsableCommand {
31+
public init() {}
3132
public static var configuration: CommandConfiguration {
3233
var config = CommandConfiguration()
3334
config.commandName = "build"
@@ -38,7 +39,7 @@ extension Application {
3839
}
3940

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

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

120-
func run() async throws {
121+
public func run() async throws {
121122
do {
122123
let timeout: Duration = .seconds(300)
123124
let progressConfig = try ProgressConfig(
@@ -132,16 +133,16 @@ extension Application {
132133

133134
progress.set(description: "Dialing builder")
134135

135-
let builder: Builder? = try await withThrowingTaskGroup(of: Builder.self) { group in
136+
let builder: Builder? = try await withThrowingTaskGroup(of: Builder.self) { [vsockPort, cpus, memory] group in
136137
defer {
137138
group.cancelAll()
138139
}
139140

140-
group.addTask {
141+
group.addTask { [vsockPort, cpus, memory] in
141142
while true {
142143
do {
143144
let container = try await ClientContainer.get(id: "buildkit")
144-
let fh = try await container.dial(self.vsockPort)
145+
let fh = try await container.dial(vsockPort)
145146

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

158159
try await BuilderStart.start(
159-
cpus: self.cpus,
160-
memory: self.memory,
160+
cpus: cpus,
161+
memory: memory,
161162
progressUpdate: progress.handler
162163
)
163164

@@ -255,7 +256,7 @@ extension Application {
255256
}
256257
return results
257258
}()
258-
group.addTask { [terminal] in
259+
group.addTask { [terminal, buildArg, contextDir, label, noCache, target, quiet, cacheIn, cacheOut] in
259260
let config = ContainerBuild.Builder.BuildConfig(
260261
buildID: buildID,
261262
contentStore: RemoteContentStoreClient(),
@@ -341,7 +342,7 @@ extension Application {
341342
}
342343
}
343344

344-
func validate() throws {
345+
public func validate() throws {
345346
guard FileManager.default.fileExists(atPath: file) else {
346347
throw ValidationError("Dockerfile does not exist at path: \(file)")
347348
}

Sources/CLI/Builder/Builder.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
import ArgumentParser
1818

1919
extension Application {
20-
struct BuilderCommand: AsyncParsableCommand {
21-
static let configuration = CommandConfiguration(
20+
public struct BuilderCommand: AsyncParsableCommand {
21+
public init() {}
22+
23+
public static let configuration = CommandConfiguration(
2224
commandName: "builder",
2325
abstract: "Manage an image builder instance",
2426
subcommands: [

Sources/CLI/Builder/BuilderDelete.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ import ContainerizationError
2020
import Foundation
2121

2222
extension Application {
23-
struct BuilderDelete: AsyncParsableCommand {
23+
public struct BuilderDelete: AsyncParsableCommand {
24+
public init() {}
25+
2426
public static var configuration: CommandConfiguration {
2527
var config = CommandConfiguration()
2628
config.commandName = "delete"
@@ -35,7 +37,7 @@ extension Application {
3537
@Flag(name: .shortAndLong, help: "Force delete builder even if it is running")
3638
var force = false
3739

38-
func run() async throws {
40+
public func run() async throws {
3941
do {
4042
let container = try await ClientContainer.get(id: "buildkit")
4143
if container.status != .stopped {

Sources/CLI/Builder/BuilderStart.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ import Foundation
2727
import TerminalProgress
2828

2929
extension Application {
30-
struct BuilderStart: AsyncParsableCommand {
30+
public struct BuilderStart: AsyncParsableCommand {
31+
public init() {}
32+
3133
public static var configuration: CommandConfiguration {
3234
var config = CommandConfiguration()
3335
config.commandName = "start"
@@ -39,16 +41,16 @@ extension Application {
3941
}
4042

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

4446
@Option(
4547
name: [.customLong("memory"), .customShort("m")],
4648
help:
4749
"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)"
4850
)
49-
public var memory: String = "2048MB"
51+
var memory: String = "2048MB"
5052

51-
func run() async throws {
53+
public func run() async throws {
5254
let progressConfig = try ProgressConfig(
5355
showTasks: true,
5456
showItems: true,

Sources/CLI/Builder/BuilderStatus.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ import ContainerizationError
2020
import Foundation
2121

2222
extension Application {
23-
struct BuilderStatus: AsyncParsableCommand {
23+
public struct BuilderStatus: AsyncParsableCommand {
24+
public init() {}
25+
2426
public static var configuration: CommandConfiguration {
2527
var config = CommandConfiguration()
2628
config.commandName = "status"
@@ -34,7 +36,7 @@ extension Application {
3436
@Flag(name: .long, help: ArgumentHelp("Display detailed status in json format"))
3537
var json: Bool = false
3638

37-
func run() async throws {
39+
public func run() async throws {
3840
do {
3941
let container = try await ClientContainer.get(id: "buildkit")
4042
if json {

Sources/CLI/Builder/BuilderStop.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ import ContainerizationError
2020
import Foundation
2121

2222
extension Application {
23-
struct BuilderStop: AsyncParsableCommand {
23+
public struct BuilderStop: AsyncParsableCommand {
24+
public init() {}
25+
2426
public static var configuration: CommandConfiguration {
2527
var config = CommandConfiguration()
2628
config.commandName = "stop"
@@ -31,7 +33,7 @@ extension Application {
3133
return config
3234
}
3335

34-
func run() async throws {
36+
public func run() async throws {
3537
do {
3638
let container = try await ClientContainer.get(id: "buildkit")
3739
try await container.stop()

Sources/CLI/Container/ContainerCreate.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ import Foundation
2121
import TerminalProgress
2222

2323
extension Application {
24-
struct ContainerCreate: AsyncParsableCommand {
25-
static let configuration = CommandConfiguration(
24+
public struct ContainerCreate: AsyncParsableCommand {
25+
public init() {}
26+
27+
public static let configuration = CommandConfiguration(
2628
commandName: "create",
2729
abstract: "Create a new container")
2830

@@ -47,7 +49,7 @@ extension Application {
4749
@OptionGroup
4850
var global: Flags.Global
4951

50-
func run() async throws {
52+
public func run() async throws {
5153
let progressConfig = try ProgressConfig(
5254
showTasks: true,
5355
showItems: true,

Sources/CLI/Container/ContainerDelete.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ import ContainerizationError
2020
import Foundation
2121

2222
extension Application {
23-
struct ContainerDelete: AsyncParsableCommand {
24-
static let configuration = CommandConfiguration(
23+
public struct ContainerDelete: AsyncParsableCommand {
24+
public init() {}
25+
26+
public static let configuration = CommandConfiguration(
2527
commandName: "delete",
2628
abstract: "Delete one or more containers",
2729
aliases: ["rm"])
@@ -38,7 +40,7 @@ extension Application {
3840
@Argument(help: "Container IDs/names")
3941
var containerIDs: [String] = []
4042

41-
func validate() throws {
43+
public func validate() throws {
4244
if containerIDs.count == 0 && !all {
4345
throw ContainerizationError(.invalidArgument, message: "no containers specified and --all not supplied")
4446
}
@@ -50,7 +52,7 @@ extension Application {
5052
}
5153
}
5254

53-
mutating func run() async throws {
55+
public mutating func run() async throws {
5456
let set = Set<String>(containerIDs)
5557
var containers = [ClientContainer]()
5658

0 commit comments

Comments
 (0)