tart create: support fetching URLs specified in the --from-ipsw option (#256)

* tart create: support fetching URLs specified in the --from-ipsw option

* Use x-amz-meta-digest-sha256 header to cache IPSWs
This commit is contained in:
Nikolay Edigaryev 2022-09-27 23:36:18 +04:00 committed by GitHub
parent e90d53eceb
commit 7a2c20ba30
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 52 additions and 25 deletions

View File

@ -9,7 +9,7 @@ struct Create: AsyncParsableCommand {
@Argument(help: "VM name")
var name: String
@Option(help: ArgumentHelp("create a macOS VM using path to the IPSW file (or \"latest\") to fetch the latest appropriate IPSW", valueName: "path"))
@Option(help: ArgumentHelp("create a macOS VM using path to the IPSW file or URL (or \"latest\", to fetch the latest supported IPSW automatically)", valueName: "path"))
var fromIPSW: String?
@Flag(help: "create a Linux VM")
@ -34,11 +34,17 @@ struct Create: AsyncParsableCommand {
try await withTaskCancellationHandler(operation: {
if let fromIPSW = fromIPSW {
let ipswURL: URL
if fromIPSW == "latest" {
_ = try await VM(vmDir: tmpVMDir, ipswURL: nil, diskSizeGB: diskSize)
ipswURL = try await VM.latestIPSWURL()
} else if fromIPSW.starts(with: "http://") || fromIPSW.starts(with: "https://") {
ipswURL = URL(string: fromIPSW)!
} else {
_ = try await VM(vmDir: tmpVMDir, ipswURL: URL(fileURLWithPath: fromIPSW), diskSizeGB: diskSize)
ipswURL = URL(fileURLWithPath: fromIPSW)
}
_ = try await VM(vmDir: tmpVMDir, ipswURL: ipswURL, diskSizeGB: diskSize)
}
if linux {

View File

@ -9,8 +9,8 @@ class IPSWCache: PrunableStorage {
try FileManager.default.createDirectory(at: baseURL, withIntermediateDirectories: true)
}
func locationFor(image: VZMacOSRestoreImage) -> URL {
baseURL.appendingPathComponent("\(image.buildVersion).ipsw", isDirectory: false)
func locationFor(fileName: String) -> URL {
baseURL.appendingPathComponent(fileName, isDirectory: false)
}
func prunables() throws -> [Prunable] {

View File

@ -60,26 +60,29 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
virtualMachine.delegate = self
}
static func retrieveLatestIPSW() async throws -> URL {
defaultLogger.appendNewLine("Looking up the latest supported IPSW...")
let image = try await withCheckedThrowingContinuation { continuation in
VZMacOSRestoreImage.fetchLatestSupported() { result in
continuation.resume(with: result)
static func retrieveIPSW(remoteURL: URL) async throws -> URL {
// Check if we already have this IPSW in cache
var request = URLRequest(url: remoteURL)
request.httpMethod = "HEAD"
let (_, response) = try await URLSession.shared.data(for: request)
let httpURLResponse = response as! HTTPURLResponse
if let hash = httpURLResponse.value(forHTTPHeaderField: "x-amz-meta-digest-sha256") {
let ipswLocation = try IPSWCache().locationFor(fileName: "sha256:\(hash).ipsw")
if FileManager.default.fileExists(atPath: ipswLocation.path) {
defaultLogger.appendNewLine("Using cached *.ipsw file...")
try ipswLocation.updateAccessDate()
return ipswLocation
}
}
let expectedIPSWLocation = try IPSWCache().locationFor(image: image)
if FileManager.default.fileExists(atPath: expectedIPSWLocation.path) {
defaultLogger.appendNewLine("Using cached *.ipsw file...")
try expectedIPSWLocation.updateAccessDate()
return expectedIPSWLocation
}
defaultLogger.appendNewLine("Fetching \(expectedIPSWLocation.lastPathComponent)...")
// Download the IPSW
defaultLogger.appendNewLine("Fetching \(remoteURL.lastPathComponent)...")
let data: Data = try await withCheckedThrowingContinuation { continuation in
let downloadedTask = URLSession.shared.dataTask(with: image.url) { data, response, error in
let downloadedTask = URLSession.shared.dataTask(with: remoteURL) { data, response, error in
if error != nil {
continuation.resume(throwing: error!)
return
@ -94,10 +97,24 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
downloadedTask.resume()
}
try data.write(to: expectedIPSWLocation, options: [.atomic])
return expectedIPSWLocation
let ipswLocation = try IPSWCache().locationFor(fileName: Digest.hash(data) + ".ipsw")
try data.write(to: ipswLocation, options: [.atomic])
return ipswLocation
}
static func latestIPSWURL() async throws -> URL {
defaultLogger.appendNewLine("Looking up the latest supported IPSW...")
let image = try await withCheckedThrowingContinuation { continuation in
VZMacOSRestoreImage.fetchLatestSupported() { result in
continuation.resume(with: result)
}
}
return image.url
}
var inFinalState: Bool {
get {
virtualMachine.state == VZVirtualMachine.State.stopped ||
@ -109,12 +126,16 @@ class VM: NSObject, VZVirtualMachineDelegate, ObservableObject {
init(
vmDir: VMDirectory,
ipswURL: URL?,
ipswURL: URL,
diskSizeGB: UInt16,
network: Network = NetworkShared(),
additionalDiskAttachments: [VZDiskImageStorageDeviceAttachment] = []
) async throws {
let ipswURL = ipswURL != nil ? ipswURL! : try await VM.retrieveLatestIPSW();
var ipswURL = ipswURL
if !ipswURL.isFileURL {
ipswURL = try await VM.retrieveIPSW(remoteURL: ipswURL)
}
// Load the restore image and try to get the requirements
// that match both the image and our platform