From 7320f8360ec24159b4fa6e5cdea124f3308384de Mon Sep 17 00:00:00 2001 From: Danny Canter Date: Wed, 11 Jun 2025 08:39:39 -0700 Subject: [PATCH] VZVirtualMachineInstance: Adjust silent success behavior of nested virt (#88) Fixes #85 The virtualization bool on LinuxContainer mostly just forwards to VZVirtualMachineInstance which today would silently take your boolean and do nothing if the underlying platform doesn't have support for it. This is (to me) arguably worse than erroring, as it gives the client a false security that the setting is on, and they should have virt capabilities in the container/guest now. This change makes it so that we throw a ContainerizationError for this case, with a code of .unsupported so it's checkable by a user if they want more information on the "why". --- .../VZVirtualMachineInstance.swift | 14 +++++++----- Sources/Integration/Suite.swift | 22 +++++++++++++++++-- Sources/Integration/VMTests.swift | 13 +++++++++-- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/Sources/Containerization/VZVirtualMachineInstance.swift b/Sources/Containerization/VZVirtualMachineInstance.swift index a5107ea2..87af204d 100644 --- a/Sources/Containerization/VZVirtualMachineInstance.swift +++ b/Sources/Containerization/VZVirtualMachineInstance.swift @@ -294,16 +294,18 @@ extension VZVirtualMachineInstance.Configuration { try mount.configure(config: &config) } - #if arch(arm64) - let platform = VZGenericPlatformConfiguration() - if VZGenericPlatformConfiguration.isNestedVirtualizationSupported { - platform.isNestedVirtualizationEnabled = self.nestedVirtualization + // We shouldn't silently succeed if the user asked for virt and their hardware does + // not support it. + if !VZGenericPlatformConfiguration.isNestedVirtualizationSupported && self.nestedVirtualization { + throw ContainerizationError( + .unsupported, + message: "nested virtualization is not supported on the platform" + ) } + platform.isNestedVirtualizationEnabled = self.nestedVirtualization config.platform = platform - #endif - try config.validate() return config } diff --git a/Sources/Integration/Suite.swift b/Sources/Integration/Suite.swift index d7cfb07e..b5760ada 100644 --- a/Sources/Integration/Suite.swift +++ b/Sources/Integration/Suite.swift @@ -37,6 +37,14 @@ enum IntegrationError: Swift.Error { case noOutput } +struct SkipTest: Swift.Error, CustomStringConvertible { + let reason: String + + var description: String { + reason + } +} + @main struct IntegrationSuite: AsyncParsableCommand { static let appRoot: URL = { @@ -209,6 +217,7 @@ struct IntegrationSuite: AsyncParsableCommand { ] var passed = 0 + var skipped = 0 for (name, test) in tests { do { log.info("test \(name) started...") @@ -218,15 +227,24 @@ struct IntegrationSuite: AsyncParsableCommand { let lasted = CFAbsoluteTimeGetCurrent() - started log.info("✅ test \(name) complete in \(lasted)s.") passed += 1 + } catch let err as SkipTest { + log.info("⏭️ skipped test: \(err)") + skipped += 1 } catch { log.error("❌ test \(name) failed: \(error)") } } let ended = CFAbsoluteTimeGetCurrent() - suiteStarted - log.info("\nintegration suite completed in \(ended)s with \(passed)/\(tests.count) passed!") + var finishingText = "\n\nIntegration suite completed in \(ended)s with \(passed)/\(tests.count) passed" + if skipped > 0 { + finishingText += " and \(skipped)/\(tests.count) skipped" + } + finishingText += "!" - if passed < tests.count { + log.info("\(finishingText)") + + if passed + skipped < tests.count { log.error("❌") throw ExitCode(1) } diff --git a/Sources/Integration/VMTests.swift b/Sources/Integration/VMTests.swift index e20013ad..682558b9 100644 --- a/Sources/Integration/VMTests.swift +++ b/Sources/Integration/VMTests.swift @@ -17,6 +17,7 @@ import ArgumentParser import Containerization +import ContainerizationError import ContainerizationOCI import Foundation import Logging @@ -69,8 +70,16 @@ extension IntegrationSuite { container.virtualization = true - try await container.create() - try await container.start() + do { + try await container.create() + try await container.start() + } catch { + if let err = error as? ContainerizationError { + if err.code == .unsupported { + throw SkipTest(reason: err.message) + } + } + } let status = try await container.wait() try await container.stop()