From 007f9a41f79a2a28b067fcb38f225cb6b8a36db9 Mon Sep 17 00:00:00 2001 From: Aditya Ramani Date: Thu, 5 Jun 2025 18:09:36 -0700 Subject: [PATCH] Update kernel install UX (#22) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change updates the UX when we install required dependencies at first launch ### Fresh install - respond `y` to prompt ``` ➜ bin/container system start Verifying apiserver is running... Installing base container filesystem... No default kernel configured. Install the recommended default kernel from [https://github.com/kata-containers/kata-containers/releases/download/3.17.0/kata-static-3.17.0-arm64.tar.xz]? [Y/n]: y Installing kernel... ``` ### Fresh install - respond `n` to prompt and then run the provided command ``` ➜ bin/container system start Verifying apiserver is running... Installing base container filesystem... No default kernel configured. Install the recommended default kernel from [https://github.com/kata-containers/kata-containers/releases/download/3.17.0/kata-static-3.17.0-arm64.tar.xz]? [Y/n]: n Please use the `container system kernel set --recommended` command to configure the default kernel ➜ bin/container system kernel set --recommended Installing the recommended kernel from https://github.com/kata-containers/kata-containers/releases/download/3.17.0/kata-static-3.17.0-arm64.tar.xz... ``` ### Fresh install - respond `n` to prompt and then run a container ``` ➜ bin/container system start Verifying apiserver is running... Installing base container filesystem... No default kernel configured. Install the recommended default kernel from [https://github.com/kata-containers/kata-containers/releases/download/3.17.0/kata-static-3.17.0-arm64.tar.xz]? [Y/n]: n Please use the `container system kernel set --recommended` command to configure the default kernel ➜ bin/container run alpine uname Error: notFound: "Default kernel not configured for architecture arm64. Please use the `container system kernel set` command to configure it ``` --------- Signed-off-by: Aditya Ramani --- Makefile | 1 + Sources/APIServer/Kernel/KernelService.swift | 8 +- Sources/CLI/System/Kernel/KernelSet.swift | 7 +- Sources/CLI/System/SystemStart.swift | 77 +++++++++---------- .../ContainerClient/Core/ClientKernel.swift | 2 +- Sources/ContainerClient/XPC+.swift | 2 - 6 files changed, 48 insertions(+), 49 deletions(-) diff --git a/Makefile b/Makefile index 2011a244..782779d3 100644 --- a/Makefile +++ b/Makefile @@ -178,6 +178,7 @@ _site: .PHONY: cleancontent cleancontent: + @bin/container system stop || true @echo Cleaning the content... @rm -rf ~/Library/Application\ Support/com.apple.container diff --git a/Sources/APIServer/Kernel/KernelService.swift b/Sources/APIServer/Kernel/KernelService.swift index 70b968e5..6d56a8d8 100644 --- a/Sources/APIServer/Kernel/KernelService.swift +++ b/Sources/APIServer/Kernel/KernelService.swift @@ -42,7 +42,13 @@ actor KernelService { let kFile = url.resolvingSymlinksInPath() let destPath = self.kernelDirectory.appendingPathComponent(kFile.lastPathComponent) try FileManager.default.copyItem(at: kFile, to: destPath) - try self.setDefaultKernel(name: kFile.lastPathComponent, platform: platform) + try Task.checkCancellation() + do { + try self.setDefaultKernel(name: kFile.lastPathComponent, platform: platform) + } catch { + try? FileManager.default.removeItem(at: destPath) + throw error + } } /// Copies a kernel binary from inside of tar file into the managed kernels directory diff --git a/Sources/CLI/System/Kernel/KernelSet.swift b/Sources/CLI/System/Kernel/KernelSet.swift index ff57a339..6a1ac179 100644 --- a/Sources/CLI/System/Kernel/KernelSet.swift +++ b/Sources/CLI/System/Kernel/KernelSet.swift @@ -39,13 +39,14 @@ extension Application { @Option(name: .customLong("arch"), help: "The architecture of the kernel binary. One of (amd64, arm64)") var architecture: String = ContainerizationOCI.Platform.current.architecture.description - @Flag(name: .customLong("install-recommended"), help: "Download and install the recommended kernel as the default. This flag ignores any other arguments") - var installRecommended: Bool = false + @Flag(name: .customLong("recommended"), help: "Download and install the recommended kernel as the default. This flag ignores any other arguments") + var recommended: Bool = false func run() async throws { - if installRecommended { + if recommended { let url = ClientDefaults.get(key: .defaultKernelURL) let path = ClientDefaults.get(key: .defaultKernelBinaryPath) + print("Installing the recommended kernel from \(url)...") try await Self.downloadAndInstallWithProgressBar(tarRemoteURL: url, kernelFilePath: path) return } diff --git a/Sources/CLI/System/SystemStart.swift b/Sources/CLI/System/SystemStart.swift index f29cb951..168300a6 100644 --- a/Sources/CLI/System/SystemStart.swift +++ b/Sources/CLI/System/SystemStart.swift @@ -77,7 +77,6 @@ extension Application { do { print("Verifying apiserver is running...") try await ClientHealthCheck.ping(timeout: .seconds(10)) - print("Done") } catch { throw ContainerizationError( .internalError, @@ -85,65 +84,50 @@ extension Application { ) } - var kernelConfigured: Bool = true - var missingDependencies: [Dependencies] = [] if await !initImageExists() { - missingDependencies.append(.initFs) + try? await installInitialFilesystem() } - if await !kernelExists() { - kernelConfigured = false - missingDependencies.append(.kernel) - } - guard missingDependencies.count > 0 else { + + guard await !kernelExists() else { return } + try await installDefaultKernel() + } - print("Missing required runtime dependencies:") - for (idx, dependency) in missingDependencies.enumerated() { - print(" \(idx+1). \(dependency.rawValue)") + private func installInitialFilesystem() async throws { + let dep = Dependencies.initFs + let pullCommand = ImagePull(reference: dep.source) + print("Installing base container filesystem...") + do { + try await pullCommand.run() + } catch { + log.error("Failed to install base container filesystem: \(error)") } + } + private func installDefaultKernel() async throws { + let kernelDependency = Dependencies.kernel + let defaultKernelURL = kernelDependency.source + let defaultKernelBinaryPath = ClientDefaults.get(key: .defaultKernelBinaryPath) + + print("No default kernel configured.") + print("Install the recommended default kernel from [\(kernelDependency.source)]? [Y/n]: ", terminator: "") if !installDependencies { - print("Would like to install them now? [Y/n]: ", terminator: "") guard let read = readLine(strippingNewline: true) else { throw ContainerizationError(.internalError, message: "Failed to read user input") } guard read.lowercased() == "y" || read.count == 0 else { - if !kernelConfigured { - print("Please use the `container system kernel set` command to configure the kernel") - } + print("Please use the `container system kernel set --recommended` command to configure the default kernel") return } } - try await installDeps(deps: missingDependencies) - } - - private func installDeps(deps: [Dependencies]) async throws { - if deps.contains(.kernel) { - try await installDefaultKernel() - } - if deps.contains(.initFs) { - try await installInitialFilesystem() - } - } - - private func installInitialFilesystem() async throws { - let reference = ClientDefaults.get(key: .defaultInitImage) - let pullCommand = ImagePull(reference: reference) - print("Installing initial filesystem from [\(reference)]...") - try await pullCommand.run() - } - - private func installDefaultKernel() async throws { - let defaultKernelURL = ClientDefaults.get(key: .defaultKernelURL) - let defaultKernelBinaryPath = ClientDefaults.get(key: .defaultKernelBinaryPath) - print("Installing default kernel from [\(defaultKernelURL)]...") + print("Installing kernel...") try await KernelSet.downloadAndInstallWithProgressBar(tarRemoteURL: defaultKernelURL, kernelFilePath: defaultKernelBinaryPath) } private func initImageExists() async -> Bool { do { - let img = try await ClientImage.get(reference: ClientDefaults.get(key: .defaultInitImage)) + let img = try await ClientImage.get(reference: Dependencies.initFs.source) let _ = try await img.getSnapshot(platform: .current) return true } catch { @@ -162,7 +146,16 @@ extension Application { } private enum Dependencies: String { - case kernel = "Kernel" - case initFs = "Initial filesystem" + case kernel + case initFs + + var source: String { + switch self { + case .initFs: + return ClientDefaults.get(key: .defaultInitImage) + case .kernel: + return ClientDefaults.get(key: .defaultKernelURL) + } + } } } diff --git a/Sources/ContainerClient/Core/ClientKernel.swift b/Sources/ContainerClient/Core/ClientKernel.swift index ccfd2011..4ca10108 100644 --- a/Sources/ContainerClient/Core/ClientKernel.swift +++ b/Sources/ContainerClient/Core/ClientKernel.swift @@ -80,7 +80,7 @@ extension ClientKernel { throw err } throw ContainerizationError( - .notFound, message: "Default kernel not configured for architecture \(platform.architecture). Please use the `container system kernel` command to configure it") + .notFound, message: "Default kernel not configured for architecture \(platform.architecture). Please use the `container system kernel set` command to configure it") } } } diff --git a/Sources/ContainerClient/XPC+.swift b/Sources/ContainerClient/XPC+.swift index aa06e4a7..cbf977d4 100644 --- a/Sources/ContainerClient/XPC+.swift +++ b/Sources/ContainerClient/XPC+.swift @@ -93,9 +93,7 @@ public enum XPCKeys: String { case kernel case kernelTarURL case kernelFilePath - case setDefault case systemPlatform - case kernelName } public enum XPCRoute: String {