mirror of https://github.com/cirruslabs/tart.git
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:
parent
e90d53eceb
commit
7a2c20ba30
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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] {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue