Files
2026-07-21 18:15:47 -07:00

93 lines
4.2 KiB
Swift

//===----------------------------------------------------------------------===//
// Copyright © 2026 Apple Inc. and the container project authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//===----------------------------------------------------------------------===//
import ContainerPlugin
import Containerization
import ContainerizationArchive
import CryptoKit
import Foundation
/// Fixture helpers for `TestCLIKernelSetSerial`.
///
/// `container system kernel set` always requires a kernel to already be
/// installed for the integration suite to run at all (`system start
/// --enable-kernel-install` guarantees this). Rather than downloading the real
/// ~570MB kata-static release tarball in every test, these helpers capture the
/// bytes of whatever kernel is already installed and repackage them into a
/// tar with the same internal layout (a real binary plus the `vmlinux.container`
/// symlink member kata's release tarballs ship) so the real install/extract/
/// digest-verify code paths still get exercised, without any network access.
public struct KernelFixture {
public init() {}
private let kernelsDirectory = URL(fileURLWithPath: ApplicationRoot.pathname).appendingPathComponent("kernels")
private var defaultKernelSymlink: URL {
kernelsDirectory.appendingPathComponent("default.kernel-\(SystemPlatform.linuxArm.architecture.rawValue)")
}
/// Copies the bytes of the currently-installed default kernel to `destination`.
public func captureInstalledBinary(to destination: URL) throws {
let resolved = defaultKernelSymlink.resolvingSymlinksInPath()
guard FileManager.default.fileExists(atPath: resolved.path) else {
throw CommandError.executionFailed("no default kernel installed at \(resolved.path)")
}
try FileManager.default.copyItem(at: resolved, to: destination)
}
/// Writes a tar at `tarPath` containing `binary`'s bytes at `binaryArchivePath`,
/// plus a `vmlinux.container` symlink alongside it pointing at the binary's
/// filename — mirroring the layout `KernelService.extractFile`'s
/// symlink-following branch expects.
///
/// Returns the tar's own `sha256:<hex>` digest, since `container system
/// kernel set --digest` verifies the archive's digest, not the extracted
/// binary's.
@discardableResult
public func writeTar(binary: URL, binaryArchivePath: String, to tarPath: URL) throws -> String {
let binaryData = try Data(contentsOf: binary)
let writer = try ArchiveWriter(format: .ustar, filter: .none, file: tarPath)
let fileEntry = WriteEntry()
fileEntry.path = binaryArchivePath
fileEntry.fileType = .regular
fileEntry.permissions = 0o644
fileEntry.size = Int64(binaryData.count)
try writer.writeEntry(entry: fileEntry, data: binaryData)
let symlinkEntry = WriteEntry()
symlinkEntry.path =
URL(filePath: binaryArchivePath)
.deletingLastPathComponent()
.appending(path: "vmlinux.container")
.relativePath
symlinkEntry.fileType = .symbolicLink
symlinkEntry.symlinkTarget = URL(filePath: binaryArchivePath).lastPathComponent
symlinkEntry.permissions = 0o644
try writer.writeEntry(entry: symlinkEntry, data: nil)
try writer.finishEncoding()
var hasher = SHA256()
let handle = try FileHandle(forReadingFrom: tarPath)
defer { try? handle.close() }
while let chunk = try handle.read(upToCount: 4 * 1024 * 1024), !chunk.isEmpty {
hasher.update(data: chunk)
}
let hex = hasher.finalize().map { String(format: "%02x", $0) }.joined()
return "sha256:\(hex)"
}
}