mirror of https://github.com/cirruslabs/tart.git
Registry functional/integration tests (#96)
* Registry functional/integration tests * Remove DockerClientSwift import * Encodable, Decodable → Codable
This commit is contained in:
parent
13b05d75c5
commit
fec803277d
|
|
@ -3,26 +3,26 @@ import Foundation
|
|||
let ociManifestMediaType = "application/vnd.oci.image.manifest.v1+json"
|
||||
let ociConfigMediaType = "application/vnd.oci.image.config.v1+json"
|
||||
|
||||
struct OCIManifest: Encodable, Decodable {
|
||||
struct OCIManifest: Codable, Equatable {
|
||||
var schemaVersion: Int = 2
|
||||
var mediaType: String = ociManifestMediaType
|
||||
var config: OCIManifestConfig
|
||||
var layers: [OCIManifestLayer] = Array()
|
||||
}
|
||||
|
||||
struct OCIManifestConfig: Encodable, Decodable {
|
||||
struct OCIManifestConfig: Codable, Equatable {
|
||||
var mediaType: String = ociConfigMediaType
|
||||
var size: Int
|
||||
var digest: String
|
||||
}
|
||||
|
||||
struct OCIManifestLayer: Encodable, Decodable {
|
||||
struct OCIManifestLayer: Codable, Equatable {
|
||||
var mediaType: String
|
||||
var size: Int
|
||||
var digest: String
|
||||
}
|
||||
|
||||
struct Descriptor {
|
||||
struct Descriptor: Equatable {
|
||||
var size: Int
|
||||
var digest: String
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,19 +60,29 @@ class Registry {
|
|||
|
||||
var currentAuthToken: TokenResponse? = nil
|
||||
|
||||
init(host: String, namespace: String) throws {
|
||||
init(urlComponents: URLComponents, namespace: String) throws {
|
||||
baseURL = urlComponents.url!
|
||||
self.namespace = namespace
|
||||
}
|
||||
|
||||
convenience init(host: String, namespace: String) throws {
|
||||
var baseURLComponents = URLComponents()
|
||||
|
||||
baseURLComponents.scheme = "https"
|
||||
baseURLComponents.host = host
|
||||
baseURLComponents.path = "/v2/"
|
||||
|
||||
baseURL = baseURLComponents.url!
|
||||
self.namespace = namespace
|
||||
try self.init(urlComponents: baseURLComponents, namespace: namespace)
|
||||
}
|
||||
|
||||
func pushManifest(reference: String, config: Descriptor, layers: [OCIManifestLayer]) async throws -> String {
|
||||
let manifest = OCIManifest(config: OCIManifestConfig(size: config.size, digest: config.digest),
|
||||
layers: layers)
|
||||
func ping() async throws {
|
||||
let (_, response) = try await endpointRequest("GET", "/v2/")
|
||||
if response.statusCode != 200 {
|
||||
throw RegistryError.UnexpectedHTTPStatusCode(when: "doing ping", code: response.statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func pushManifest(reference: String, manifest: OCIManifest) async throws -> String {
|
||||
let manifestJSON = try JSONEncoder().encode(manifest)
|
||||
|
||||
let (responseData, response) = try await endpointRequest("PUT", "\(namespace)/manifests/\(reference)",
|
||||
|
|
|
|||
|
|
@ -120,20 +120,23 @@ extension VMDirectory {
|
|||
layers.append(OCIManifestLayer(mediaType: Self.nvramMediaType, size: nvram.count, digest: nvramDigest))
|
||||
|
||||
// Craft a stub OCI config for Docker Hub compatibility
|
||||
struct OCIConfig: Encodable, Decodable {
|
||||
struct OCIConfig: Codable {
|
||||
var architecture: String = "arm64"
|
||||
var os: String = "darwin"
|
||||
}
|
||||
|
||||
let ociConfigJSON = try JSONEncoder().encode(OCIConfig())
|
||||
let ociConfigDigest = try await registry.pushBlob(fromData: ociConfigJSON)
|
||||
let ociConfigDescriptor = Descriptor(size: ociConfigJSON.count, digest: ociConfigDigest)
|
||||
let manifest = OCIManifest(
|
||||
config: OCIManifestConfig(size: ociConfigJSON.count, digest: ociConfigDigest),
|
||||
layers: layers
|
||||
)
|
||||
|
||||
// Manifest
|
||||
for reference in references {
|
||||
defaultLogger.appendNewLine("pushing manifest for \(reference)...")
|
||||
|
||||
_ = try await registry.pushManifest(reference: reference, config: ociConfigDescriptor, layers: layers)
|
||||
_ = try await registry.pushManifest(reference: reference, manifest: manifest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
import XCTest
|
||||
@testable import tart
|
||||
|
||||
final class RegistryTests: XCTestCase {
|
||||
var registryRunner: RegistryRunner?
|
||||
|
||||
override func setUp() async throws {
|
||||
try await super.setUp()
|
||||
|
||||
do {
|
||||
registryRunner = try await RegistryRunner()
|
||||
} catch {
|
||||
try XCTSkipIf(ProcessInfo.processInfo.environment["CI"] == nil)
|
||||
}
|
||||
}
|
||||
|
||||
override func tearDown() async throws {
|
||||
try await super.tearDown()
|
||||
|
||||
registryRunner = nil
|
||||
}
|
||||
|
||||
var registry: Registry {
|
||||
registryRunner!.registry
|
||||
}
|
||||
|
||||
func testPushPullBlobSmall() async throws {
|
||||
// Generate a simple blob
|
||||
let pushedBlob = Data("The quick brown fox jumps over the lazy dog".utf8)
|
||||
|
||||
// Push it
|
||||
let pushedBlobDigest = try await registry.pushBlob(fromData: pushedBlob)
|
||||
XCTAssertEqual("sha256:d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592", pushedBlobDigest)
|
||||
|
||||
// Pull it
|
||||
let pulledBlob = try await registry.pullBlob(pushedBlobDigest)
|
||||
|
||||
// Ensure that both blobs are identical
|
||||
XCTAssertEqual(pushedBlob, pulledBlob)
|
||||
}
|
||||
|
||||
func testPushPullBlobHuge() async throws {
|
||||
// Generate a large enough blob
|
||||
let fh = FileHandle(forReadingAtPath: "/dev/urandom")!
|
||||
let largeBlobToPush = try fh.read(upToCount: 768 * 1024 * 1024)!
|
||||
|
||||
// Push it
|
||||
let largeBlobDigest = try await registry.pushBlob(fromData: largeBlobToPush)
|
||||
|
||||
// Pull it
|
||||
let pulledLargeBlob = try await registry.pullBlob(largeBlobDigest)
|
||||
|
||||
// Ensure that both blobs are identical
|
||||
XCTAssertEqual(largeBlobToPush, pulledLargeBlob)
|
||||
}
|
||||
|
||||
func testPushPullManifest() async throws {
|
||||
// Craft a basic config
|
||||
struct OCIConfig: Codable {
|
||||
var architecture: String = "arm64"
|
||||
var os: String = "darwin"
|
||||
}
|
||||
let configData = try JSONEncoder().encode(OCIConfig())
|
||||
let configDigest = try await registry.pushBlob(fromData: configData)
|
||||
|
||||
// Craft a basic layer
|
||||
let layerData = Data("doesn't matter".utf8)
|
||||
let layerDigest = try await registry.pushBlob(fromData: layerData)
|
||||
|
||||
// Craft a basic manifest and push it
|
||||
let manifest = OCIManifest(
|
||||
config: OCIManifestConfig(size: configData.count, digest: configDigest),
|
||||
layers: [
|
||||
OCIManifestLayer(mediaType: "application/octet-stream", size: layerData.count, digest: layerDigest)
|
||||
]
|
||||
)
|
||||
let pushedManifestDigest = try await registry.pushManifest(reference: "latest", manifest: manifest)
|
||||
|
||||
// Ensure that the manifest pulled by tag matches with the one pushed above
|
||||
let (pulledByTagManifest, _) = try await registry.pullManifest(reference: "latest")
|
||||
XCTAssertEqual(manifest, pulledByTagManifest)
|
||||
|
||||
// Ensure that the manifest pulled by digest matches with the one pushed above
|
||||
let (pulledByDigestManifest, _) = try await registry.pullManifest(reference: "\(pushedManifestDigest)")
|
||||
XCTAssertEqual(manifest, pulledByDigestManifest)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
import Foundation
|
||||
@testable import tart
|
||||
|
||||
enum RegistryRunnerError: Error {
|
||||
case DockerFailed(exitCode: Int32)
|
||||
}
|
||||
|
||||
class RegistryRunner {
|
||||
let containerID: String
|
||||
let registry: Registry
|
||||
|
||||
static func dockerCmd(_ arguments: String...) throws -> String {
|
||||
let stdoutPipe = Pipe()
|
||||
|
||||
let proc = Process()
|
||||
proc.executableURL = URL(fileURLWithPath: "/usr/local/bin/docker")
|
||||
proc.arguments = arguments
|
||||
proc.standardOutput = stdoutPipe
|
||||
try proc.run()
|
||||
|
||||
let stdoutData = stdoutPipe.fileHandleForReading.readDataToEndOfFile()
|
||||
|
||||
proc.waitUntilExit()
|
||||
|
||||
if proc.terminationStatus != 0 {
|
||||
throw RegistryRunnerError.DockerFailed(exitCode: proc.terminationStatus)
|
||||
}
|
||||
|
||||
return String(data: stdoutData, encoding: .utf8) ?? ""
|
||||
}
|
||||
|
||||
init() async throws {
|
||||
// Start container
|
||||
let container = try Self.dockerCmd("run", "-d", "--rm", "-p", "5000", "registry:2")
|
||||
.trimmingCharacters(in: CharacterSet.newlines)
|
||||
containerID = container
|
||||
|
||||
// Get forwarded port
|
||||
let port = try Self.dockerCmd("inspect", containerID, "--format", "{{(index (index .NetworkSettings.Ports \"5000/tcp\") 0).HostPort}}")
|
||||
.trimmingCharacters(in: CharacterSet.newlines)
|
||||
|
||||
registry = try Registry(urlComponents: URLComponents(string: "http://127.0.0.1:\(port)/v2/")!,
|
||||
namespace: "vm-image")
|
||||
|
||||
// Wait for the Docker Registry to start
|
||||
while ((try? await registry.ping()) == nil) {
|
||||
try await Task.sleep(nanoseconds: 100_000_000)
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
_ = try! Self.dockerCmd("kill", containerID)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue