Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 30 additions & 28 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,9 @@ DSYM_PATH := bin/$(BUILD_CONFIGURATION)/bundle/container-dSYM.zip
CODESIGN_OPTS ?= --force --sign - --timestamp=none

# Conditionally use a temporary data directory for integration tests
ifeq ($(strip $(APP_ROOT)),)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't do this anymore since we'll want to add other options (--log-root) soon. Make it so we can accumulate startup options.

SYSTEM_START_OPTS :=
else
SYSTEM_START_OPTS := --app-root "$(strip $(APP_ROOT))"
SYSTEM_START_OPTS :=
ifneq ($(strip $(APP_ROOT)),)
SYSTEM_START_OPTS += --app-root "$(strip $(APP_ROOT))"
endif

MACOS_VERSION := $(shell sw_vers -productVersion)
Expand Down Expand Up @@ -77,7 +76,8 @@ release: all

.PHONY: init-block
init-block:
@scripts/install-init.sh
@echo Building initfs if containerization is in edit mode
@scripts/install-init.sh $(SYSTEM_START_OPTS)

.PHONY: install
install: installer-pkg
Expand Down Expand Up @@ -144,15 +144,17 @@ test:

.PHONY: install-kernel
install-kernel:
@echo Stopping system before installing kernel
@bin/container system stop || true
@bin/container system start --timeout 60 --enable-kernel-install $(SYSTEM_START_OPTS)
@echo Starting system to install kernel
@bin/container --debug system start --timeout 60 --enable-kernel-install $(SYSTEM_START_OPTS)

.PHONY: coverage
coverage: init-block
@echo Ensuring apiserver stopped before the CLI integration tests...
@echo Ensuring apiserver stopped before the coverage analysis
@bin/container system stop && sleep 3 && scripts/ensure-container-stopped.sh
@bin/container system start $(SYSTEM_START_OPTS) && \
echo "Starting unit tests" && \
@bin/container --debug system start $(SYSTEM_START_OPTS) && \
echo "Starting coverage analysis" && \
{ \
exit_code=0; \
$(SWIFT) test --no-parallel --enable-code-coverage -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) || exit_code=1 ; \
Expand All @@ -176,28 +178,28 @@ integration: init-block
@echo Ensuring apiserver stopped before the CLI integration tests...
@bin/container system stop && sleep 3 && scripts/ensure-container-stopped.sh
@echo Running the integration tests...
@bin/container system start --timeout 60 $(SYSTEM_START_OPTS) && \
@bin/container --debug system start --timeout 60 $(SYSTEM_START_OPTS) && \
echo "Starting CLI integration tests" && \
{ \
exit_code=0; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLINetwork || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIRunLifecycle || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIExecCommand || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLICreateCommand || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIRunCommand1 || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIRunCommand2 || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIRunCommand3 || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIPruneCommand || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIRegistry || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIStatsCommand || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIImagesCommand || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIRunBase || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIRunInitImage || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIBuildBase || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIVolumes || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIKernelSet || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIAnonymousVolumes || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLINoParallelCases || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLINetwork || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIRunLifecycle || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIExecCommand || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLICreateCommand || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIRunCommand1 || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIRunCommand2 || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIRunCommand3 || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIPruneCommand || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIRegistry || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIStatsCommand || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIImagesCommand || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIRunBase || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIRunInitImage || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIBuildBase || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIVolumes || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIKernelSet || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIAnonymousVolumes || exit_code=1 ; \
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLINoParallelCases || exit_code=1 ; \
echo Ensuring apiserver stopped after the CLI integration tests ; \
scripts/ensure-container-stopped.sh ; \
exit $${exit_code} ; \
Expand Down
6 changes: 3 additions & 3 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import PackageDescription
let releaseVersion = ProcessInfo.processInfo.environment["RELEASE_VERSION"] ?? "0.0.0"
let gitCommit = ProcessInfo.processInfo.environment["GIT_COMMIT"] ?? "unspecified"
let builderShimVersion = "0.8.0"
let scVersion = "0.25.0"
let scVersion = "0.26.1"

let package = Package(
name: "container",
Expand Down Expand Up @@ -98,6 +98,7 @@ let package = Package(
"ContainerPlugin",
"ContainerResource",
"ContainerVersion",
"ContainerXPC",
"TerminalProgress",
],
path: "Sources/ContainerCommands"
Expand Down
7 changes: 6 additions & 1 deletion Sources/ContainerCommands/Container/ContainerPrune.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,12 @@ extension Application {
try await client.delete(id: container.id)
prunedContainerIds.append(container.id)
} catch {
log.error("Failed to prune container \(container.id): \(error)")
log.error(
"failed to prune container",
metadata: [
"id": "\(container.id)",
"error": "\(error)",
])
}
}

Expand Down
7 changes: 6 additions & 1 deletion Sources/ContainerCommands/Image/ImagePrune.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,12 @@ extension Application {
try await ClientImage.delete(reference: image.reference, garbageCollect: false)
prunedImages.append(image.reference)
} catch {
log.error("Failed to prune image \(image.reference): \(error)")
log.error(
"failed to prune image",
metadata: [
"ref": "\(image.reference)",
"error": "\(error)",
])
}
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/ContainerCommands/Network/NetworkPrune.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ extension Application.NetworkCommand {
// Note: This failure may occur due to a race condition between the network/
// container collection above and a container run command that attaches to a
// network listed in the networksToPrune collection.
log.error("Failed to prune network \(network.id): \(error)")
log.error("failed to prune network", metadata: ["id": "\(network.id)", "error": "\(error)"])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
log.error("failed to prune network", metadata: ["id": "\(network.id)", "error": "\(error)"])
log.error(
"failed to prune network",
metadata: [
"id": "\(network.id)",
"error": "\(error)",
])

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is reasonable. Let me do it in the follow-up (log collection PR) so we can start unblocking the PR backlog.

}
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/ContainerCommands/System/SystemLogs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ extension Application {
do {
var args = ["log"]
args.append(self.follow ? "stream" : "show")
args.append(contentsOf: ["--info", "--debug"])
args.append(contentsOf: ["--info", logOptions.debug ? "--debug" : nil].compactMap { $0 })
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now we only pass debug into log stream for container --debug system logs.

if !self.follow {
args.append(contentsOf: ["--last", last])
}
Expand Down
17 changes: 12 additions & 5 deletions Sources/ContainerCommands/System/SystemStart.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import ArgumentParser
import ContainerAPIClient
import ContainerPersistence
import ContainerPlugin
import ContainerXPC
import ContainerizationError
import Foundation
import TerminalProgress
Expand Down Expand Up @@ -48,9 +49,15 @@ extension Application {
var kernelInstall: Bool?

@Option(
name: .long,
help: "Number of seconds to wait for API service to become responsive")
var timeout: Double = 10.0
help: "Number of seconds to wait for API service to become responsive",
transform: {
guard let timeoutSeconds = Double($0) else {
throw ValidationError("Invalid timeout value: \($0)")
}
return .seconds(timeoutSeconds)
}
)
var timeout: Duration = XPCClient.xpcRegistrationTimeout
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Key point here is that we use the common timeout value now.


@OptionGroup
public var logOptions: Flags.Logging
Expand Down Expand Up @@ -98,7 +105,7 @@ extension Application {
// Now ping our friendly daemon. Fail if we don't get a response.
do {
print("Verifying apiserver is running...")
_ = try await ClientHealthCheck.ping(timeout: .seconds(timeout))
_ = try await ClientHealthCheck.ping(timeout: timeout)
} catch {
throw ContainerizationError(
.internalError,
Expand All @@ -124,7 +131,7 @@ extension Application {
do {
try await pullCommand.run()
} catch {
log.error("failed to install base container filesystem: \(error)")
log.error("failed to install base container filesystem", metadata: ["error": "\(error)"])
}
}

Expand Down
7 changes: 6 additions & 1 deletion Sources/ContainerCommands/Volume/VolumePrune.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,12 @@ extension Application.VolumeCommand {
try await ClientVolume.delete(name: volume.name)
prunedVolumes.append(volume.name)
} catch {
log.error("Failed to prune volume \(volume.name): \(error)")
log.error(
"failed to prune volume",
metadata: [
"id": "\(volume.name)",
"error": "\(error)",
])
}
}

Expand Down
6 changes: 4 additions & 2 deletions Sources/ContainerPlugin/PluginLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,8 @@ extension PluginLoader {
plugin: Plugin,
pluginStateRoot: URL? = nil,
args: [String]? = nil,
instanceId: String? = nil
instanceId: String? = nil,
debug: Bool = false,
) throws {
// We only care about loading plugins that have a service
// to expose; otherwise, they may just be CLI commands.
Expand All @@ -223,9 +224,10 @@ extension PluginLoader {
env[ApplicationRoot.environmentName] = appRoot.path(percentEncoded: false)
env[InstallRoot.environmentName] = installRoot.path(percentEncoded: false)

let processedArgs = (args ?? ["start"]) + (debug ? ["--debug"] : [])
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the main change, adds --debug to the plugin startup as necessary.

The implication is that all plugins need to implement a start subcommand that accepts a --debug flag.

let plist = LaunchPlist(
label: id,
arguments: [plugin.binaryURL.path] + (args ?? ["start"]) + serviceConfig.defaultArguments,
arguments: [plugin.binaryURL.path] + processedArgs + serviceConfig.defaultArguments,
environment: env,
limitLoadToSessionType: [.Aqua, .Background, .System],
runAtLoad: serviceConfig.runAtLoad,
Expand Down
7 changes: 7 additions & 0 deletions Sources/ContainerXPC/XPCClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ import ContainerizationError
import Foundation

public final class XPCClient: Sendable {
/// The maximum amount of time to wait for a request to a recently
/// registered XPC service. Once a service has launched, XPC
/// requests only have milliseconds of overhead, but in some instances,
/// macOS can take 5 seconds (or considerably longer) to launch a
/// service after it has been registered.
public static let xpcRegistrationTimeout: Duration = .seconds(60)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our common timeout value, chosen to work with our slow service startup in CI.


private nonisolated(unsafe) let connection: xpc_connection_t
private let q: DispatchQueue?
private let service: String
Expand Down
10 changes: 7 additions & 3 deletions Sources/ContainerXPC/XPCServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,11 @@ public struct XPCServer: Sendable {
// a final XPC_ERROR_CONNECTION_INVALID message.
// We can ignore this if we know we have already handled
// the request.
self.log.error("xpc client handler connection error \(object.errorDescription ?? "no description")")
self.log.error(
"xpc client handler connection error",
metadata: [
"error": "\(object.errorDescription ?? "no description")"
])
}
default:
fatalError("unhandled xpc object type: \(xpc_get_type(object))")
Expand Down Expand Up @@ -195,14 +199,14 @@ public struct XPCServer: Sendable {
let response = try await handler(message)
xpc_connection_send_message(connection, response.underlying)
} catch let error as ContainerizationError {
log.error("handler for \(route) threw error \(error)")
log.error("route handler threw an error", metadata: ["route": "\(route)", "error": "\(error)"])
Self.replyWithError(
connection: connection,
object: object,
err: error
)
} catch {
log.error("handler for \(route) threw error \(error)")
log.error("route handler threw an error", metadata: ["route": "\(route)", "error": "\(error)"])
let message = XPCMessage(object: object)
let reply = message.reply()

Expand Down
24 changes: 13 additions & 11 deletions Sources/Helpers/APIServer/APIServer+Start.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,22 +46,22 @@ extension APIServer {
func run() async throws {
let commandName = Self.configuration.commandName ?? "container-apiserver"
let log = APIServer.setupLogger(debug: debug)
log.info("starting \(commandName)")
log.info("starting helper", metadata: ["name": "\(commandName)"])
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pattern is used for startup/shutdown for all services.

defer {
log.info("stopping \(commandName)")
log.info("stopping helper", metadata: ["name": "\(commandName)"])
}

do {
log.info("configuring XPC server")
var routes = [XPCRoute: XPCServer.RouteHandler]()
let pluginLoader = try initializePluginLoader(log: log)
try await initializePlugins(pluginLoader: pluginLoader, log: log, routes: &routes)
let containersService = try initializeContainerService(
let containersService = try initializeContainersService(
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some name nitpickery for "containers service" and "networks service".

pluginLoader: pluginLoader,
log: log,
routes: &routes
)
let networkService = try await initializeNetworkService(
let networkService = try await initializeNetworksService(
pluginLoader: pluginLoader,
containersService: containersService,
log: log,
Expand Down Expand Up @@ -131,7 +131,7 @@ extension APIServer {
*/
}
} catch {
log.error("\(commandName) failed", metadata: ["error": "\(error)"])
log.error("helper failed", metadata: ["name": "\(commandName)", "error": "\(error)"])
APIServer.exit(withError: error)
}
}
Expand Down Expand Up @@ -222,13 +222,14 @@ extension APIServer {
routes[XPCRoute.getDefaultKernel] = harness.getDefaultKernel
}

private func initializeContainerService(pluginLoader: PluginLoader, log: Logger, routes: inout [XPCRoute: XPCServer.RouteHandler]) throws -> ContainersService {
log.info("initializing container service")
private func initializeContainersService(pluginLoader: PluginLoader, log: Logger, routes: inout [XPCRoute: XPCServer.RouteHandler]) throws -> ContainersService {
log.info("initializing containers service")

let service = try ContainersService(
appRoot: appRoot,
pluginLoader: pluginLoader,
log: log
log: log,
debugHelpers: debug
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Propagate debug settings from API server to any runtime helpers we create.

)
let harness = ContainersHarness(service: service, log: log)

Expand All @@ -250,20 +251,21 @@ extension APIServer {
return service
}

private func initializeNetworkService(
private func initializeNetworksService(
pluginLoader: PluginLoader,
containersService: ContainersService,
log: Logger,
routes: inout [XPCRoute: XPCServer.RouteHandler]
) async throws -> NetworksService {
log.info("initializing network service")
log.info("initializing networks service")

let resourceRoot = appRoot.appendingPathComponent("networks")
let service = try await NetworksService(
pluginLoader: pluginLoader,
resourceRoot: resourceRoot,
containersService: containersService,
log: log
log: log,
debugHelpers: debug
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Propagate debug settings from API server to any network helpers we create.

)

let defaultNetwork = try await service.list()
Expand Down
2 changes: 1 addition & 1 deletion Sources/Helpers/APIServer/DirectoryWatcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public class DirectoryWatcher {
throw ContainerizationError(.invalidState, message: "failed to start watching on \(directoryURL.path)")
}

log.info("starting directory watcher for \(directoryURL.path)")
log.info("starting directory watcher", metadata: ["path": "\(directoryURL.path)"])

let descriptor = open(directoryURL.path, O_EVTONLY)

Expand Down
Loading