Update kernel install UX (#22)

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 <a_ramani@apple.com>
This commit is contained in:
Aditya Ramani
2025-06-05 18:09:36 -07:00
committed by GitHub
parent 0a71d8e95e
commit 007f9a41f7
6 changed files with 48 additions and 49 deletions
+1
View File
@@ -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
+7 -1
View File
@@ -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
+4 -3
View File
@@ -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
}
+35 -42
View File
@@ -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)
}
}
}
}
@@ -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")
}
}
}
-2
View File
@@ -93,9 +93,7 @@ public enum XPCKeys: String {
case kernel
case kernelTarURL
case kernelFilePath
case setDefault
case systemPlatform
case kernelName
}
public enum XPCRoute: String {