Compare commits

..
4 Commits
Author SHA1 Message Date
Nikolay Edigaryev ba4f00fa78 Fix ArgumentParser's exception printing (#367) 2022-12-21 18:06:01 +04:00
Nikolay Edigaryev 1380ece108 Use separate exception codes for better Sentry grouping (#363) 2022-12-19 17:40:55 +00:00
Fedor Korotkov 9cdb7087f2 Fixed --help (#361)
* Fixed --help

Apparently `--help` works via exceptions 🤷‍♂️

Fixes #360

* lint issue
2022-12-17 20:57:17 +04:00
Nikolay Edigaryev c3e37854c3 .cirrus.yml: install Sentry CLI (#356) 2022-12-15 22:28:21 +00:00
11 changed files with 79 additions and 28 deletions
+1 -1
View File
@@ -74,7 +74,7 @@ task:
- security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k password101 build.keychain - security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k password101 build.keychain
- xcrun notarytool store-credentials "notarytool" --apple-id "hello@cirruslabs.org" --team-id "9M2P8L4D89" --password $AC_PASSWORD - xcrun notarytool store-credentials "notarytool" --apple-id "hello@cirruslabs.org" --team-id "9M2P8L4D89" --password $AC_PASSWORD
install_script: install_script:
- brew install go goreleaser/tap/goreleaser-pro - brew install go goreleaser/tap/goreleaser-pro getsentry/tools/sentry-cli
- brew install mitchellh/gon/gon - brew install mitchellh/gon/gon
info_script: info_script:
- security find-identity -v - security find-identity -v
+1 -1
View File
@@ -19,7 +19,7 @@ struct IP: AsyncParsableCommand {
let vmMACAddress = MACAddress(fromString: vmConfig.macAddress.string)! let vmMACAddress = MACAddress(fromString: vmConfig.macAddress.string)!
guard let ipViaDHCP = try await IP.resolveIP(vmMACAddress, secondsToWait: wait) else { guard let ipViaDHCP = try await IP.resolveIP(vmMACAddress, secondsToWait: wait) else {
throw RuntimeError("no IP address found, is your VM running?") throw RuntimeError.NoIPAddressFound("no IP address found, is your VM running?")
} }
let arpCache = try ARPCache() let arpCache = try ARPCache()
+1 -1
View File
@@ -47,7 +47,7 @@ struct Login: AsyncParsableCommand {
credentialsProviders: [credentialsProvider]) credentialsProviders: [credentialsProvider])
try await registry.ping() try await registry.ping()
} catch { } catch {
throw RuntimeError("invalid credentials: \(error)") throw RuntimeError.InvalidCredentials("invalid credentials: \(error)")
} }
try KeychainCredentialsProvider().store(host: host, user: user, password: password) try KeychainCredentialsProvider().store(host: host, user: user, password: password)
+2 -2
View File
@@ -117,7 +117,7 @@ struct Run: AsyncParsableCommand {
} }
if try !FileLock(lockURL: additionalDiskAttachment.url).trylock() { if try !FileLock(lockURL: additionalDiskAttachment.url).trylock() {
throw RuntimeError("disk \(additionalDiskAttachment.url.path) seems to be already in use, " throw RuntimeError.DiskAlreadyInUse("disk \(additionalDiskAttachment.url.path) seems to be already in use, "
+ "unmount it first in Finder") + "unmount it first in Finder")
} }
} }
@@ -154,7 +154,7 @@ struct Run: AsyncParsableCommand {
// [1]: https://man.openbsd.org/fcntl // [1]: https://man.openbsd.org/fcntl
let lock = try PIDLock(lockURL: vmDir.configURL) let lock = try PIDLock(lockURL: vmDir.configURL)
if try !lock.trylock() { if try !lock.trylock() {
throw RuntimeError("Virtual machine \"\(name)\" is already running!", exitCode: 2) throw RuntimeError.VMAlreadyRunning("VM \"\(name)\" is already running!")
} }
let task = Task { let task = Task {
+2 -2
View File
@@ -19,7 +19,7 @@ struct Stop: AsyncParsableCommand {
// Find the VM's PID // Find the VM's PID
var pid = try lock.pid() var pid = try lock.pid()
if pid == 0 { if pid == 0 {
throw RuntimeError("VM \(name) is not running", exitCode: 2) throw RuntimeError.VMNotRunning("VM \"\(name)\" is not running")
} }
// Try to gracefully terminate the VM // Try to gracefully terminate the VM
@@ -54,7 +54,7 @@ struct Stop: AsyncParsableCommand {
if ret != 0 { if ret != 0 {
let details = Errno(rawValue: CInt(errno)) let details = Errno(rawValue: CInt(errno))
throw RuntimeError("failed to forcefully terminate the VM \(name): \(details)") throw RuntimeError.VMTerminationFailed("failed to forcefully terminate the VM \"\(name)\": \(details)")
} }
} }
} }
+2 -2
View File
@@ -106,7 +106,7 @@ struct RemoteName: Comparable, Hashable, CustomStringConvertible {
try ParseTreeWalker().walk(referenceCollector, try parser.root()) try ParseTreeWalker().walk(referenceCollector, try parser.root())
if let error = errorCollector.error { if let error = errorCollector.error {
throw RuntimeError("failed to parse remote name: \(error)") throw RuntimeError.FailedToParseRemoteName("\(error)")
} }
host = referenceCollector.host! host = referenceCollector.host!
@@ -120,7 +120,7 @@ struct RemoteName: Comparable, Hashable, CustomStringConvertible {
} else if reference.starts(with: ":") { } else if reference.starts(with: ":") {
self.reference = Reference(tag: String(reference.dropFirst(1))) self.reference = Reference(tag: String(reference.dropFirst(1)))
} else { } else {
throw RuntimeError("failed to parse remote name: unknown reference format") throw RuntimeError.FailedToParseRemoteName("unknown reference format")
} }
} else { } else {
self.reference = Reference(tag: "latest") self.reference = Reference(tag: "latest")
+1 -1
View File
@@ -44,7 +44,7 @@ class PIDLock {
let details = Errno(rawValue: CInt(errno)) let details = Errno(rawValue: CInt(errno))
throw RuntimeError("\(message): \(details)") throw RuntimeError.PIDLockFailed("\(message): \(details)")
} }
return (true, result) return (true, result)
+6 -4
View File
@@ -100,13 +100,15 @@ struct Root: AsyncParsableCommand {
SentrySDK.capture(error: error) SentrySDK.capture(error: error)
SentrySDK.flush(timeout: 2.seconds.timeInterval) SentrySDK.flush(timeout: 2.seconds.timeInterval)
print(error) // Handle a non-ArgumentParser's exception that requires a specific exit code to be set
if let errorWithExitCode = error as? HasExitCode {
print(error)
if let runtimeError = error as? RuntimeError { Foundation.exit(errorWithExitCode.exitCode)
Foundation.exit(runtimeError.exitCode)
} }
Foundation.exit(1) // Handle any other exception, including ArgumentParser's ones
exit(withError: error)
} }
} }
+1 -1
View File
@@ -13,7 +13,7 @@ extension URL {
let times = [accessDate.asTimeval(), modificationDate.asTimeval()] let times = [accessDate.asTimeval(), modificationDate.asTimeval()]
let ret = utimes(path, times) let ret = utimes(path, times)
if ret != 0 { if ret != 0 {
throw RuntimeError("utimes(2) failed: \(ret.explanation())") throw RuntimeError.FailedToUpdateAccessDate("utimes(2) failed: \(ret.explanation())")
} }
} }
} }
+3 -3
View File
@@ -41,7 +41,7 @@ struct VMDirectory: Prunable {
func initialize(overwrite: Bool = false) throws { func initialize(overwrite: Bool = false) throws {
if !overwrite && initialized { if !overwrite && initialized {
throw RuntimeError("VM directory is already initialized, preventing overwrite") throw RuntimeError.VMDirectoryAlreadyInitialized("VM directory is already initialized, preventing overwrite")
} }
try FileManager.default.createDirectory(at: baseURL, withIntermediateDirectories: true, attributes: nil) try FileManager.default.createDirectory(at: baseURL, withIntermediateDirectories: true, attributes: nil)
@@ -53,11 +53,11 @@ struct VMDirectory: Prunable {
func validate() throws { func validate() throws {
if !FileManager.default.fileExists(atPath: baseURL.path) { if !FileManager.default.fileExists(atPath: baseURL.path) {
throw RuntimeError("the specified VM does not exist") throw RuntimeError.VMDoesNotExist(name: baseURL.lastPathComponent)
} }
if !initialized { if !initialized {
throw RuntimeError("VM is missing some of its files (\(configURL.lastPathComponent)," throw RuntimeError.VMMissingFiles("VM is missing some of its files (\(configURL.lastPathComponent),"
+ " \(diskURL.lastPathComponent) or \(nvramURL.lastPathComponent))") + " \(diskURL.lastPathComponent) or \(nvramURL.lastPathComponent))")
} }
} }
+59 -10
View File
@@ -26,7 +26,7 @@ class VMStorageHelper {
return try closure() return try closure()
} catch { } catch {
if error.isFileNotFound() { if error.isFileNotFound() {
throw RuntimeError("source VM \"\(name)\" not found, is it listed in \"tart list\"?") throw RuntimeError.VMDoesNotExist(name: name)
} }
throw error throw error
@@ -40,17 +40,66 @@ extension Error {
} }
} }
class RuntimeError: Error, CustomStringConvertible { enum RuntimeError : Error {
let message: String case VMDoesNotExist(name: String)
let exitCode: Int32 case VMMissingFiles(_ message: String)
case VMNotRunning(_ message: String)
case VMAlreadyRunning(_ message: String)
case NoIPAddressFound(_ message: String)
case DiskAlreadyInUse(_ message: String)
case FailedToUpdateAccessDate(_ message: String)
case PIDLockFailed(_ message: String)
case FailedToParseRemoteName(_ message: String)
case VMTerminationFailed(_ message: String)
case InvalidCredentials(_ message: String)
case VMDirectoryAlreadyInitialized(_ message: String)
}
init(_ message: String, exitCode: Int32 = 1) { protocol HasExitCode {
self.message = message var exitCode: Int32 { get }
self.exitCode = exitCode }
extension RuntimeError : CustomStringConvertible {
public var description: String {
switch self {
case .VMDoesNotExist(let name):
return "the specified VM \"\(name)\" does not exist"
case .VMMissingFiles(let message):
return message
case .VMNotRunning(let message):
return message
case .VMAlreadyRunning(let message):
return message
case .NoIPAddressFound(let message):
return message
case .DiskAlreadyInUse(let message):
return message
case .FailedToUpdateAccessDate(let message):
return message
case .PIDLockFailed(let message):
return message
case .FailedToParseRemoteName(let cause):
return "failed to parse remote name: \(cause)"
case .VMTerminationFailed(let message):
return message
case .InvalidCredentials(let message):
return message
case .VMDirectoryAlreadyInitialized(let message):
return message
}
} }
}
var description: String { extension RuntimeError : HasExitCode {
message var exitCode: Int32 {
switch self {
case .VMNotRunning:
return 2
case .VMAlreadyRunning:
return 2
default:
return 1
}
} }
} }
@@ -60,7 +109,7 @@ class RuntimeError: Error, CustomStringConvertible {
extension RuntimeError : CustomNSError { extension RuntimeError : CustomNSError {
var errorUserInfo: [String : Any] { var errorUserInfo: [String : Any] {
[ [
NSDebugDescriptionErrorKey: message, NSDebugDescriptionErrorKey: description,
] ]
} }
} }