mirror of
https://github.com/cirruslabs/tart.git
synced 2026-10-09 07:25:38 +02:00
* feat: Add disk image format selection with ASIF support * fixed goreleaser-pro * Fix ASIF disk format compatibility issues - Use .uncached caching mode for ASIF disks to avoid Virtualization framework compatibility issues - Improve caching mode selection logic for better maintainability - Fix compiler warning by changing var to let for attachment variable This resolves VM startup failures when using ASIF disk format by ensuring proper disk attachment configuration. * Update goreleaser installation to use tap-specific formula Change from 'brew install --cask goreleaser-pro' to 'brew install --cask goreleaser/tap/goreleaser-pro' for proper installation from the official goreleaser tap. * Remove VS Code configuration and add to gitignore - Remove .vscode/launch.json from repository - Add .vscode/ to .gitignore to prevent VS Code settings from being tracked * Implement ASIF disk resize using diskutil - Add support for resizing ASIF disk images using diskutil image resize - Detect disk format from VM config and route to appropriate resize method - Use diskutil image info to get current ASIF disk size and validate resize - Remove restriction that prevented ASIF disk resizing in Set command - Add FailedToResizeDisk error case for proper error handling - Maintain backward compatibility with raw disk resizing - Add comprehensive size validation to prevent data loss * Update Sources/tart/Commands/Create.swift Co-authored-by: Nikolay Edigaryev <edigaryev@gmail.com> * Update Sources/tart/DiskImageFormat.swift Co-authored-by: Nikolay Edigaryev <edigaryev@gmail.com> * Update Sources/tart/DiskImageFormat.swift Co-authored-by: Nikolay Edigaryev <edigaryev@gmail.com> * Fix test naming and remove redundant test cases - Rename testFormatArgument to testCaseInsensitivity for clarity - Remove redundant 'raw' and 'invalid' test cases already covered in testFormatFromString - Remove testFormatDescriptions test as it's not very useful Addresses review comment: https://github.com/cirruslabs/tart/pull/1094#discussion_r2152093510 * Remove canCreate property and simplify DiskImageFormat - Remove canCreate property since it's the same as isSupported - Remove description property entirely as it's not used - Fix displayName for RAW format (remove UDIF reference) - Remove checkDiskutilASIFSupport helper function Addresses review comments: - https://github.com/cirruslabs/tart/pull/1094#discussion_r2152109450 - https://github.com/cirruslabs/tart/pull/1094#discussion_r2152115610 - https://github.com/cirruslabs/tart/pull/1094#discussion_r2152124330 * Update Create command validation and help text - Simplify ArgumentParser help text to let it show possible values automatically - Remove canCreate validation since property was removed - Simplify error message for unsupported disk formats Addresses review comment: https://github.com/cirruslabs/tart/pull/1094#discussion_r2152113480 * Add disk format validation to Run command - Add validation to ensure ASIF disk format is supported on current system - Check disk format compatibility before attempting to run VM Addresses review comment: https://github.com/cirruslabs/tart/pull/1094#discussion_r2152109450 * Use proper namespaced constant for OCI label - Add diskFormatLabelAnnotation constant in Manifest.swift - Use org.cirruslabs.tart.disk.format namespace for consistency - Use variable shadowing instead of new variable name for labels Addresses review comment: https://github.com/cirruslabs/tart/pull/1094#discussion_r2152163515 * Remove special ASIF caching mode - Remove .uncached caching mode for ASIF disks - Use default caching logic for all disk formats - Testing shows .cached mode works fine on macOS 26.0 Addresses review comment: https://github.com/cirruslabs/tart/pull/1094#discussion_r2152133589 * Improve code structure in VMDirectory - Use guard let instead of nested if let for better readability - Reduce nesting in resizeASIFDisk function - Improve error handling flow Addresses review comment: https://github.com/cirruslabs/tart/pull/1094#discussion_r2152141916 * diskFormatLabel * reverted caching mode * Use PropertyListDecoder --------- Co-authored-by: Nikolay Edigaryev <edigaryev@gmail.com>
183 lines
5.7 KiB
Swift
183 lines
5.7 KiB
Swift
import Foundation
|
|
|
|
class VMStorageHelper {
|
|
static func open(_ name: String) throws -> VMDirectory {
|
|
try missingVMWrap(name) {
|
|
if let remoteName = try? RemoteName(name) {
|
|
return try VMStorageOCI().open(remoteName)
|
|
} else {
|
|
return try VMStorageLocal().open(name)
|
|
}
|
|
}
|
|
}
|
|
|
|
static func delete(_ name: String) throws {
|
|
try missingVMWrap(name) {
|
|
if let remoteName = try? RemoteName(name) {
|
|
try VMStorageOCI().delete(remoteName)
|
|
} else {
|
|
try VMStorageLocal().delete(name)
|
|
}
|
|
}
|
|
}
|
|
|
|
private static func missingVMWrap<R: Any>(_ name: String, closure: () throws -> R) throws -> R {
|
|
do {
|
|
return try closure()
|
|
} catch RuntimeError.PIDLockMissing {
|
|
throw RuntimeError.VMDoesNotExist(name: name)
|
|
} catch {
|
|
if error.isFileNotFound() {
|
|
throw RuntimeError.VMDoesNotExist(name: name)
|
|
}
|
|
|
|
throw error
|
|
}
|
|
}
|
|
}
|
|
|
|
extension NSError {
|
|
func isFileNotFound() -> Bool {
|
|
return self.code == NSFileNoSuchFileError || self.code == NSFileReadNoSuchFileError
|
|
}
|
|
}
|
|
|
|
extension Error {
|
|
func isFileNotFound() -> Bool {
|
|
(self as NSError).isFileNotFound() || (self as NSError).underlyingErrors.contains(where: { $0.isFileNotFound() })
|
|
}
|
|
}
|
|
|
|
enum RuntimeError : Error {
|
|
case Generic(_ message: String)
|
|
case VMConfigurationError(_ message: String)
|
|
case VMDoesNotExist(name: String)
|
|
case VMMissingFiles(_ message: String)
|
|
case VMIsRunning(_ name: String)
|
|
case VMNotRunning(_ name: String)
|
|
case VMAlreadyRunning(_ message: String)
|
|
case NoIPAddressFound(_ message: String)
|
|
case DiskAlreadyInUse(_ message: String)
|
|
case FailedToOpenBlockDevice(_ path: String, _ explanation: String)
|
|
case InvalidDiskSize(_ message: String)
|
|
case FailedToCreateDisk(_ message: String)
|
|
case FailedToResizeDisk(_ message: String)
|
|
case FailedToUpdateAccessDate(_ message: String)
|
|
case PIDLockFailed(_ message: String)
|
|
case PIDLockMissing(_ message: String)
|
|
case FailedToParseRemoteName(_ message: String)
|
|
case VMTerminationFailed(_ message: String)
|
|
case ImproperlyFormattedHost(_ host: String, _ hint: String)
|
|
case InvalidCredentials(_ message: String)
|
|
case VMDirectoryAlreadyInitialized(_ message: String)
|
|
case ExportFailed(_ message: String)
|
|
case ImportFailed(_ message: String)
|
|
case SoftnetFailed(_ message: String)
|
|
case OCIStorageError(_ message: String)
|
|
case OCIUnsupportedDiskFormat(_ format: String)
|
|
case SuspendFailed(_ message: String)
|
|
case PullFailed(_ message: String)
|
|
case VirtualMachineLimitExceeded(_ hint: String)
|
|
case VMSocketFailed(_ port: UInt32, _ explanation: String)
|
|
case TerminalOperationFailed(_ message: String)
|
|
}
|
|
|
|
protocol HasExitCode {
|
|
var exitCode: Int32 { get }
|
|
}
|
|
|
|
extension RuntimeError : CustomStringConvertible {
|
|
public var description: String {
|
|
switch self {
|
|
case .Generic(let message):
|
|
return message
|
|
case .VMConfigurationError(let message):
|
|
return message
|
|
case .VMDoesNotExist(let name):
|
|
return "the specified VM \"\(name)\" does not exist"
|
|
case .VMMissingFiles(let message):
|
|
return message
|
|
case .VMIsRunning(let name):
|
|
return "VM \"\(name)\" is running"
|
|
case .VMNotRunning(let name):
|
|
return "VM \"\(name)\" is not running"
|
|
case .VMAlreadyRunning(let message):
|
|
return message
|
|
case .NoIPAddressFound(let message):
|
|
return message
|
|
case .DiskAlreadyInUse(let message):
|
|
return message
|
|
case .FailedToOpenBlockDevice(let path, let explanation):
|
|
return "failed to open block device \(path): \(explanation)"
|
|
case .InvalidDiskSize(let message):
|
|
return message
|
|
case .FailedToCreateDisk(let message):
|
|
return message
|
|
case .FailedToResizeDisk(let message):
|
|
return message
|
|
case .FailedToUpdateAccessDate(let message):
|
|
return message
|
|
case .PIDLockFailed(let message):
|
|
return message
|
|
case .PIDLockMissing(let message):
|
|
return message
|
|
case .FailedToParseRemoteName(let cause):
|
|
return "failed to parse remote name: \(cause)"
|
|
case .VMTerminationFailed(let message):
|
|
return message
|
|
case .ImproperlyFormattedHost(let host, let hint):
|
|
return "improperly formatted host \"\(host)\" was provided\(hint)"
|
|
case .InvalidCredentials(let message):
|
|
return message
|
|
case .VMDirectoryAlreadyInitialized(let message):
|
|
return message
|
|
case .ExportFailed(let message):
|
|
return "VM export failed: \(message)"
|
|
case .ImportFailed(let message):
|
|
return "VM import failed: \(message)"
|
|
case .SoftnetFailed(let message):
|
|
return "Softnet failed: \(message)"
|
|
case .OCIStorageError(let message):
|
|
return "OCI storage error: \(message)"
|
|
case .OCIUnsupportedDiskFormat(let format):
|
|
return "OCI disk format \(format) is not supported by this version of Tart"
|
|
case .SuspendFailed(let message):
|
|
return "Failed to suspend the VM: \(message)"
|
|
case .PullFailed(let message):
|
|
return message
|
|
case .VirtualMachineLimitExceeded(let hint):
|
|
return "The number of VMs exceeds the system limit\(hint)"
|
|
case .VMSocketFailed(let port, let explanation):
|
|
return "Failed to establish a VM socket connection to port \(port): \(explanation)"
|
|
case .TerminalOperationFailed(let message):
|
|
return message
|
|
}
|
|
}
|
|
}
|
|
|
|
extension RuntimeError : HasExitCode {
|
|
var exitCode: Int32 {
|
|
switch self {
|
|
case .VMDoesNotExist:
|
|
return 2
|
|
case .VMNotRunning:
|
|
return 2
|
|
case .VMAlreadyRunning:
|
|
return 2
|
|
default:
|
|
return 1
|
|
}
|
|
}
|
|
}
|
|
|
|
// Customize error description for Sentry[1]
|
|
//
|
|
// [1]: https://docs.sentry.io/platforms/apple/guides/ios/usage/#customizing-error-descriptions
|
|
extension RuntimeError : CustomNSError {
|
|
var errorUserInfo: [String : Any] {
|
|
[
|
|
NSDebugDescriptionErrorKey: description,
|
|
]
|
|
}
|
|
}
|