Skip to content

Commit bb59bac

Browse files
committed
containertool: Arrange options into related groups
1 parent de7bef0 commit bb59bac

File tree

1 file changed

+91
-61
lines changed

1 file changed

+91
-61
lines changed

Sources/containertool/containertool.swift

Lines changed: 91 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -28,85 +28,111 @@ enum AllowHTTP: String, ExpressibleByArgument, CaseIterable { case source, desti
2828
abstract: "Build and publish a container image"
2929
)
3030

31-
@Option(help: "Default registry for references which do not specify a registry")
32-
private var defaultRegistry: String?
33-
34-
@Option(help: "Repository path")
35-
private var repository: String?
36-
3731
@Argument(help: "Executable to package")
3832
private var executable: String
3933

40-
@Option(help: "Resource bundle directory")
41-
private var resources: [String] = []
34+
/// Options controlling how the source and destination repositories
35+
struct RepositoryOptions: ParsableArguments {
36+
@Option(help: "Default registry for image references which do not specify one")
37+
var defaultRegistry: String?
4238

43-
@Option(
44-
help: ArgumentHelp(
45-
"[DEPRECATED] Default username, used if there are no matching entries in .netrc. Use --default-username instead.",
46-
visibility: .private
47-
)
48-
)
49-
private var username: String?
39+
@Option(help: "Destination image reference")
40+
var repository: String?
41+
42+
@Option(help: "Destination image tag")
43+
var tag: String?
44+
45+
@Option(help: "Base image reference")
46+
var from: String?
47+
}
48+
49+
/// Options controlling how the destination image is built
50+
struct ImageBuildOptions: ParsableArguments {
51+
@Option(help: "Directory of resources to include in the image")
52+
var resources: [String] = []
53+
}
54+
55+
@OptionGroup(title: "Source and destination repository options")
56+
var repositoryOptions: RepositoryOptions
57+
58+
// Image configuration options
59+
struct ImageConfigurationOptions: ParsableArguments {
60+
@Option(help: "CPU architecture")
61+
var architecture: String?
62+
63+
@Option(help: "Operating system")
64+
var os: String?
65+
}
66+
67+
@OptionGroup(title: "Image build options")
68+
var imageBuildOptions: ImageBuildOptions
5069

51-
@Option(help: "Default username, used if there are no matching entries in .netrc")
52-
private var defaultUsername: String?
70+
@OptionGroup(title: "Image configuration options")
71+
var imageConfigurationOptions: ImageConfigurationOptions
5372

54-
@Option(
55-
help: ArgumentHelp(
56-
"[DEPRECATED] Default password, used if there are no matching entries in .netrc. Use --default-password instead.",
57-
visibility: .private
73+
/// Options controlling how containertool authenticates to the registry
74+
struct AuthenticationOptions: ParsableArguments {
75+
@Option(
76+
help: ArgumentHelp(
77+
"[DEPRECATED] Default username, used if there are no matching entries in .netrc. Use --default-username instead.",
78+
visibility: .private
79+
)
5880
)
59-
)
60-
private var password: String?
81+
var username: String?
6182

62-
@Option(help: "Default password, used if there are no matching entries in .netrc")
63-
private var defaultPassword: String?
83+
@Option(help: "Default username, used if there are no matching entries in .netrc")
84+
var defaultUsername: String?
6485

65-
@Flag(name: .shortAndLong, help: "Verbose output")
66-
private var verbose: Bool = false
86+
@Option(
87+
help: ArgumentHelp(
88+
"[DEPRECATED] Default password, used if there are no matching entries in .netrc. Use --default-password instead.",
89+
visibility: .private
90+
)
91+
)
92+
var password: String?
6793

68-
@Option(help: "Connect to the container registry using plaintext HTTP")
69-
private var allowInsecureHttp: AllowHTTP?
94+
@Option(help: "Default password, used if there are no matching entries in .netrc")
95+
var defaultPassword: String?
7096

71-
@Option(help: "CPU architecture")
72-
private var architecture: String?
97+
@Flag(inversion: .prefixedEnableDisable, exclusivity: .exclusive, help: "Load credentials from a netrc file")
98+
var netrc: Bool = true
7399

74-
@Option(help: "Base image reference")
75-
private var from: String?
100+
@Option(help: "Specify the netrc file path")
101+
var netrcFile: String?
76102

77-
@Option(help: "Operating system")
78-
private var os: String?
103+
@Option(help: "Connect to the registry using plaintext HTTP")
104+
var allowInsecureHttp: AllowHTTP?
105+
}
79106

80-
@Option(help: "Tag for this manifest")
81-
private var tag: String?
107+
@OptionGroup(title: "Authentication options")
108+
var authenticationOptions: AuthenticationOptions
82109

83-
@Flag(inversion: .prefixedEnableDisable, exclusivity: .exclusive, help: "Load credentials from a netrc file")
84-
private var netrc: Bool = true
110+
// General options
85111

86-
@Option(help: "Specify the netrc file path")
87-
private var netrcFile: String?
112+
@Flag(name: .shortAndLong, help: "Verbose output")
113+
private var verbose: Bool = false
88114

89115
mutating func validate() throws {
90-
if username != nil {
91-
guard defaultUsername == nil else {
116+
if authenticationOptions.username != nil {
117+
guard authenticationOptions.defaultUsername == nil else {
92118
throw ValidationError(
93119
"--default-username and --username cannot be specified together. Please use --default-username only."
94120
)
95121
}
96122

97123
log("Deprecation warning: --username is deprecated, please use --default-username instead.")
98-
defaultUsername = username
124+
authenticationOptions.defaultUsername = authenticationOptions.username
99125
}
100126

101-
if password != nil {
102-
guard defaultPassword == nil else {
127+
if authenticationOptions.password != nil {
128+
guard authenticationOptions.defaultPassword == nil else {
103129
throw ValidationError(
104130
"--default-password and --password cannot be specified together. Please use --default-password only."
105131
)
106132
}
107133

108134
log("Deprecation warning: --password is deprecated, please use --default-password instead.")
109-
defaultPassword = password
135+
authenticationOptions.defaultPassword = authenticationOptions.password
110136
}
111137
}
112138

@@ -115,25 +141,25 @@ enum AllowHTTP: String, ExpressibleByArgument, CaseIterable { case source, desti
115141

116142
let env = ProcessInfo.processInfo.environment
117143

118-
let defaultRegistry = defaultRegistry ?? env["CONTAINERTOOL_DEFAULT_REGISTRY"] ?? "docker.io"
119-
guard let repository = repository ?? env["CONTAINERTOOL_REPOSITORY"] else {
144+
let defaultRegistry = repositoryOptions.defaultRegistry ?? env["CONTAINERTOOL_DEFAULT_REGISTRY"] ?? "docker.io"
145+
guard let repository = repositoryOptions.repository ?? env["CONTAINERTOOL_REPOSITORY"] else {
120146
throw ValidationError(
121147
"Please specify the destination repository using --repository or CONTAINERTOOL_REPOSITORY"
122148
)
123149
}
124150

125-
let username = defaultUsername ?? env["CONTAINERTOOL_DEFAULT_USERNAME"]
126-
let password = defaultPassword ?? env["CONTAINERTOOL_DEFAULT_PASSWORD"]
127-
let from = from ?? env["CONTAINERTOOL_BASE_IMAGE"] ?? "swift:slim"
128-
let os = os ?? env["CONTAINERTOOL_OS"] ?? "linux"
151+
let username = authenticationOptions.defaultUsername ?? env["CONTAINERTOOL_DEFAULT_USERNAME"]
152+
let password = authenticationOptions.defaultPassword ?? env["CONTAINERTOOL_DEFAULT_PASSWORD"]
153+
let from = repositoryOptions.from ?? env["CONTAINERTOOL_BASE_IMAGE"] ?? "swift:slim"
154+
let os = imageConfigurationOptions.os ?? env["CONTAINERTOOL_OS"] ?? "linux"
129155

130156
// Try to detect the architecture of the application executable so a suitable base image can be selected.
131157
// This reduces the risk of accidentally creating an image which stacks an aarch64 executable on top of an x86_64 base image.
132158
let executableURL = URL(fileURLWithPath: executable)
133159
let elfheader = try ELF.read(at: executableURL)
134160

135161
let architecture =
136-
architecture
162+
imageConfigurationOptions.architecture
137163
?? env["CONTAINERTOOL_ARCHITECTURE"]
138164
?? elfheader?.ISA.containerArchitecture
139165
?? "amd64"
@@ -142,10 +168,12 @@ enum AllowHTTP: String, ExpressibleByArgument, CaseIterable { case source, desti
142168
// MARK: Load netrc
143169

144170
let authProvider: AuthorizationProvider?
145-
if !netrc {
171+
if !authenticationOptions.netrc {
146172
authProvider = nil
147-
} else if let netrcFile {
148-
guard FileManager.default.fileExists(atPath: netrcFile) else { throw "\(netrcFile) not found" }
173+
} else if let netrcFile = authenticationOptions.netrcFile {
174+
guard FileManager.default.fileExists(atPath: netrcFile) else {
175+
throw "\(netrcFile) not found"
176+
}
149177
let customNetrc = URL(fileURLWithPath: netrcFile)
150178
authProvider = try NetrcAuthorizationProvider(customNetrc)
151179
} else {
@@ -166,15 +194,17 @@ enum AllowHTTP: String, ExpressibleByArgument, CaseIterable { case source, desti
166194
} else {
167195
source = try await RegistryClient(
168196
registry: baseImage.registry,
169-
insecure: allowInsecureHttp == .source || allowInsecureHttp == .both,
197+
insecure: authenticationOptions.allowInsecureHttp == .source
198+
|| authenticationOptions.allowInsecureHttp == .both,
170199
auth: .init(username: username, password: password, auth: authProvider)
171200
)
172201
if verbose { log("Connected to source registry: \(baseImage.registry)") }
173202
}
174203

175204
let destination = try await RegistryClient(
176205
registry: destinationImage.registry,
177-
insecure: allowInsecureHttp == .destination || allowInsecureHttp == .both,
206+
insecure: authenticationOptions.allowInsecureHttp == .destination
207+
|| authenticationOptions.allowInsecureHttp == .both,
178208
auth: .init(username: username, password: password, auth: authProvider)
179209
)
180210

@@ -189,8 +219,8 @@ enum AllowHTTP: String, ExpressibleByArgument, CaseIterable { case source, desti
189219
source: source,
190220
architecture: architecture,
191221
os: os,
192-
resources: resources,
193-
tag: tag,
222+
resources: imageBuildOptions.resources,
223+
tag: repositoryOptions.tag,
194224
verbose: verbose,
195225
executableURL: executableURL
196226
)

0 commit comments

Comments
 (0)