//===----------------------------------------------------------------------===// // 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:` 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)" } }