Files
tart/Sources/tart/Root.swift
T
Nikolay EdigaryevandFedor Korotkov 2014de7dac Suspend/resume support (#527)
* Suspend/resume support

* Use RuntimeError.SuspendFailed for consistency's sake

* Add a comment about "Running" field deprecation

* Use compute credits

* Use Mac-specific input devices and remove --no-{audio,entropy}

* Suspend the VM when closing window and running with --suspendable

* Snapshotting Improvements (#539)

* Don't use static field for arguments

It throws a runtime error

* Fixed suspendability

* Lazy generation of new MAC addresses

To support cloning on suspended VMs

* Refactored

* formatted

* Configurable signal for window closing

* reformatted

* Don't generate MAC only for suspended VMs

* Removed misleading comment

* Reverted

* Lock while a suspendable VM is starting

* Lock on TART_HOME

---------

Co-authored-by: Fedor Korotkov <fedor.korotkov@gmail.com>
2023-07-06 18:04:39 +00:00

121 lines
3.4 KiB
Swift

import ArgumentParser
import Darwin
import Foundation
import Sentry
@main
struct Root: AsyncParsableCommand {
static var configuration = CommandConfiguration(
commandName: "tart",
version: CI.version,
subcommands: [
Create.self,
Clone.self,
Run.self,
Set.self,
Get.self,
List.self,
Login.self,
IP.self,
Pull.self,
Push.self,
Import.self,
Export.self,
Prune.self,
Rename.self,
Stop.self,
Delete.self,
])
public static func main() async throws {
// Initialize Sentry
if let dsn = ProcessInfo.processInfo.environment["SENTRY_DSN"] {
SentrySDK.start { options in
options.dsn = dsn
options.releaseName = CI.release
options.tracesSampleRate = Float(
ProcessInfo.processInfo.environment["SENTRY_TRACES_SAMPLE_RATE"] ?? "1.0"
) as NSNumber?
// By default only 5XX are captured
// Let's capture everything but 401 (unauthorized)
options.enableCaptureFailedRequests = true
options.failedRequestStatusCodes = [
HttpStatusCodeRange(min: 400, max: 400),
HttpStatusCodeRange(min: 402, max: 599)
]
}
}
defer { SentrySDK.flush(timeout: 2.seconds.timeInterval) }
// Enrich future events with Cirrus CI-specific tags
if let tags = ProcessInfo.processInfo.environment["CIRRUS_SENTRY_TAGS"] {
SentrySDK.configureScope { scope in
for (key, value) in tags.split(separator: ",").compactMap({ parseCirrusSentryTag($0) }) {
scope.setTag(value: value, key: key)
}
}
}
// Add commands that are only available on specific macOS versions
if #available(macOS 14, *) {
configuration.subcommands.append(Suspend.self)
}
// Ensure the default SIGINT handled is disabled,
// otherwise there's a race between two handlers
signal(SIGINT, SIG_IGN);
// Handle cancellation by Ctrl+C ourselves
let task = withUnsafeCurrentTask { $0 }!
let sigintSrc = DispatchSource.makeSignalSource(signal: SIGINT)
sigintSrc.setEventHandler {
task.cancel()
}
sigintSrc.activate()
// Set line-buffered output for stdout
setlinebuf(stdout)
// Parse and run command
do {
var command = try parseAsRoot()
// Run garbage-collection before each command (shouldn't take too long)
do {
try Config().gc()
} catch {
fputs("Failed to perform garbage collection!\n\(error)\n", stderr)
}
if var asyncCommand = command as? AsyncParsableCommand {
try await asyncCommand.run()
} else {
try command.run()
}
} catch {
// Capture the error into Sentry
SentrySDK.capture(error: error)
SentrySDK.flush(timeout: 2.seconds.timeInterval)
// Handle a non-ArgumentParser's exception that requires a specific exit code to be set
if let errorWithExitCode = error as? HasExitCode {
fputs("\(error)\n", stderr)
Foundation.exit(errorWithExitCode.exitCode)
}
// Handle any other exception, including ArgumentParser's ones
exit(withError: error)
}
}
private static func parseCirrusSentryTag(_ tag: String.SubSequence) -> (String, String)? {
let splits = tag.split(separator: "=", maxSplits: 1)
if splits.count != 2 {
return nil
}
return (String(splits[0]), String(splits[1]))
}
}