Skip to content

Commit

Permalink
tart push: allow pushing OCI VMs from the cache too (#465)
Browse files Browse the repository at this point in the history
* tart push: allow pushing OCI VMs from the cache too

* Check for RemoteName earlier

* Refactored pushing of OCI images under new tag (#466)

* Refactored pushing of OCI images under new tag

* Fixed compilation

---------

Co-authored-by: Fedor Korotkov <fedor.korotkov@gmail.com>
  • Loading branch information
edigaryev and fkorotkov authored Apr 6, 2023
1 parent 261c180 commit 1d3aa5a
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 15 deletions.
55 changes: 44 additions & 11 deletions Sources/tart/Commands/Push.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Compression
struct Push: AsyncParsableCommand {
static var configuration = CommandConfiguration(abstract: "Push a VM to a registry")

@Argument(help: "local VM name")
@Argument(help: "local or remote VM name")
var localName: String

@Argument(help: "remote VM name(s)")
Expand All @@ -28,7 +28,8 @@ struct Push: AsyncParsableCommand {
var populateCache: Bool = false

func run() async throws {
let localVMDir = try VMStorageLocal().open(localName)
let ociStorage = VMStorageOCI()
let localVMDir = try VMStorageHelper.open(localName)

// Parse remote names supplied by the user
let remoteNames = try remoteNames.map{
Expand All @@ -53,23 +54,55 @@ struct Push: AsyncParsableCommand {
defaultLogger.appendNewLine("pushing \(localName) to "
+ "\(registryIdentifier.host)/\(registryIdentifier.namespace)\(remoteNamesForRegistry.referenceNames())...")

let pushedRemoteName = try await localVMDir.pushToRegistry(
registry: registry,
references: remoteNamesForRegistry.map{ $0.reference.value },
chunkSizeMb: chunkSize
)
let references = remoteNamesForRegistry.map{ $0.reference.value }

let pushedRemoteName: RemoteName
// If we're pushing a local OCI VM, check if points to an already existing registry manifest
// and if so, only upload manifests (without config, disk and NVRAM) to the user-specified references
if let remoteName = try? RemoteName(localName) {
pushedRemoteName = try await lightweightPushToRegistry(
registry: registry,
remoteName: remoteName,
references: references
)
} else {
pushedRemoteName = try await localVMDir.pushToRegistry(
registry: registry,
references: references,
chunkSizeMb: chunkSize
)
// Populate the local cache (if requested)
if populateCache {
let expectedPushedVMDir = try ociStorage.create(pushedRemoteName)
try localVMDir.clone(to: expectedPushedVMDir, generateMAC: false)
}
}

// Populate the local cache (if requested)
// link the rest remote names
if populateCache {
let ociStorage = VMStorageOCI()
let expectedPushedVMDir = try ociStorage.create(pushedRemoteName)
try localVMDir.clone(to: expectedPushedVMDir, generateMAC: false)
for remoteName in remoteNamesForRegistry {
try ociStorage.link(from: remoteName, to: pushedRemoteName)
}
}
}
}

func lightweightPushToRegistry(registry: Registry, remoteName: RemoteName, references: [String]) async throws -> RemoteName {
// Is the local OCI VM already present in the registry?
let digest = try VMStorageOCI().digest(remoteName)

let (remoteManifest, _) = try await registry.pullManifest(reference: digest)

// Overwrite registry's references with the retrieved manifest
for reference in references {
defaultLogger.appendNewLine("pushing manifest for \(reference)...")

_ = try await registry.pushManifest(reference: reference, manifest: remoteManifest)
}

return RemoteName(host: registry.baseURL.host!, namespace: registry.namespace,
reference: Reference(digest: digest))
}
}

extension Collection where Element == RemoteName {
Expand Down
4 changes: 2 additions & 2 deletions Sources/tart/VMDirectory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ struct VMDirectory: Prunable {
try? FileManager.default.removeItem(at: nvramURL)
}

func validate() throws {
func validate(userFriendlyName: String) throws {
if !FileManager.default.fileExists(atPath: baseURL.path) {
throw RuntimeError.VMDoesNotExist(name: baseURL.lastPathComponent)
throw RuntimeError.VMDoesNotExist(name: userFriendlyName)
}

if !initialized {
Expand Down
3 changes: 3 additions & 0 deletions Sources/tart/VMStorageHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ enum RuntimeError : Error {
case ExportFailed(_ message: String)
case ImportFailed(_ message: String)
case SoftnetFailed(_ message: String)
case OCIStorageError(_ message: String)
}

protocol HasExitCode {
Expand Down Expand Up @@ -98,6 +99,8 @@ extension RuntimeError : CustomStringConvertible {
return "VM import failed: \(message)"
case .SoftnetFailed(let message):
return "Softnet failed: \(message)"
case .OCIStorageError(let message):
return "OCI storage error: \(message)"
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/tart/VMStorageLocal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class VMStorageLocal {
func open(_ name: String) throws -> VMDirectory {
let vmDir = VMDirectory(baseURL: vmURL(name))

try vmDir.validate()
try vmDir.validate(userFriendlyName: name)

return vmDir
}
Expand Down
12 changes: 11 additions & 1 deletion Sources/tart/VMStorageOCI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,20 @@ class VMStorageOCI: PrunableStorage {
VMDirectory(baseURL: vmURL(name)).initialized
}

func digest(_ name: RemoteName) throws -> String {
let digest = vmURL(name).resolvingSymlinksInPath().lastPathComponent

if !digest.starts(with: "sha256:") {
throw RuntimeError.OCIStorageError("\(name) is not a digest and doesn't point to a digest")
}

return digest
}

func open(_ name: RemoteName) throws -> VMDirectory {
let vmDir = VMDirectory(baseURL: vmURL(name))

try vmDir.validate()
try vmDir.validate(userFriendlyName: name.description)

try vmDir.baseURL.updateAccessDate()

Expand Down

0 comments on commit 1d3aa5a

Please sign in to comment.