Source code documentation updates (#9)

This change adds documentation to quite a few existing public types that
didn't have a blurb before.

Additionally, this fixes a couple things that I think either didn't make
sense when going to document them:
- Rename ConnectionStream to VsockConnectionStream. This type only
functions for vsock connections.
- Deletes NsLock+Closure. This was not used anywhere.
- Rename ContainerizationOCI/Config.swift to ImageConfig.swift.

Signed-off-by: Danny Canter <danny_canter@apple.com>
This commit is contained in:
Danny Canter
2025-06-04 14:56:12 -07:00
committed by Kathryn Baldauf
parent 8d880efc36
commit 6bc4bf5124
36 changed files with 98 additions and 53 deletions
@@ -14,6 +14,8 @@
// limitations under the License.
//===----------------------------------------------------------------------===//
/// DNS configuration for a container. The values will be used to
/// construct /etc/resolv.conf for a given container.
public struct DNS: Sendable {
public static let defaultNameservers = ["1.1.1.1"]
@@ -26,6 +26,7 @@ import SystemPackage
import ContainerizationExtras
#endif
/// Type representing an OCI container image.
public struct Image: Sendable {
private let contentStore: ContentStore
@@ -18,6 +18,9 @@ import ContainerizationError
import ContainerizationOCI
import Foundation
/// Data representing the image to use as the root filesystem for a virtual machine.
/// Typically this image would contain the guest agent used to facilitate container
/// workloads, as well as any extras that may be useful to have in the guest.
public struct InitImage: Sendable {
public var name: String { image.reference }
+3 -1
View File
@@ -16,7 +16,9 @@
import Foundation
/// A kernel used to boot a sandbox.
/// An object representing a Linux kernel used to boot a virtual machine.
/// In addition to a path to the kernel itself, this type stores relevant
/// data such as the commandline to pass to the kernel, and init arguments.
public struct Kernel: Sendable, Codable {
/// The command line arguments passed to the kernel on boot.
public struct CommandLine: Sendable, Codable {
+2 -2
View File
@@ -163,7 +163,7 @@ public final class LinuxProcess: Sendable {
}
extension LinuxProcess {
func setupIO(streams: [ConnectionStream?]) async throws -> [FileHandle?] {
func setupIO(streams: [VsockConnectionStream?]) async throws -> [FileHandle?] {
let handles = try await Timeout.run(seconds: 3) {
await withTaskGroup(of: (Int, FileHandle?).self) { group in
var results = [FileHandle?](repeating: nil, count: 3)
@@ -231,7 +231,7 @@ extension LinuxProcess {
public func start() async throws {
let spec = self.state.withLock { $0.spec }
var streams = [ConnectionStream?](repeating: nil, count: 3)
var streams = [VsockConnectionStream?](repeating: nil, count: 3)
if let stdin = self.ioSetup.stdin {
streams[0] = try self.vm.listen(stdin.port)
}
@@ -22,6 +22,8 @@ import ContainerizationError
import Foundation
import Synchronization
/// An interface that uses NAT to provide an IP address for a given
/// container/virtual machine.
@available(macOS 16, *)
public final class NATNetworkInterface: Interface, Sendable {
public var address: String {
@@ -16,6 +16,9 @@
import ContainerizationOCI
/// `SystemPlatform` describes an operating system and architecture pair.
/// This is primarily used to choose what kind of OCI image to pull from a
/// registry.
public struct SystemPlatform: Sendable, Codable {
public enum OS: String, CaseIterable, Sendable, Codable {
case linux
@@ -184,8 +184,8 @@ extension VZVirtualMachineInstance {
).dupHandle()
}
func listen(_ port: UInt32) throws -> ConnectionStream {
let stream = ConnectionStream(port: port)
func listen(_ port: UInt32) throws -> VsockConnectionStream {
let stream = VsockConnectionStream(port: port)
let listener = VZVirtioSocketListener()
listener.delegate = stream
@@ -39,7 +39,7 @@ public protocol VirtualMachineInstance: Sendable {
/// Dial a vsock port in the guest.
func dial(_ port: UInt32) async throws -> FileHandle
/// Listen on a host vsock port.
func listen(_ port: UInt32) throws -> ConnectionStream
func listen(_ port: UInt32) throws -> VsockConnectionStream
/// Stop listening on a vsock port.
func stopListen(_ port: UInt32) throws
/// Start the virtual machine.
@@ -20,12 +20,14 @@ import Foundation
import Virtualization
#endif
public final class ConnectionStream: NSObject, Sendable {
/// A stream of vsock connections.
public final class VsockConnectionStream: NSObject, Sendable {
/// A stream of connections dialed from the remote.
public let connections: AsyncStream<FileHandle>
/// The port the connections are for.
public let port: UInt32
private let cont: AsyncStream<FileHandle>.Continuation
private let port: UInt32
public init(port: UInt32) {
self.port = port
@@ -41,7 +43,7 @@ public final class ConnectionStream: NSObject, Sendable {
#if os(macOS)
extension ConnectionStream: VZVirtioSocketListenerDelegate {
extension VsockConnectionStream: VZVirtioSocketListenerDelegate {
public func listener(
_: VZVirtioSocketListener, shouldAcceptNewConnection conn: VZVirtioSocketConnection,
from _: VZVirtioSocketDevice
+1 -1
View File
@@ -240,7 +240,7 @@ More details can be found here https://ext4.wiki.kernel.org/index.php/Ext4_Disk_
```
*/
/// A class for interacting with ext4 file systems.
/// A type for interacting with ext4 file systems.
///
/// The `Ext4` class provides functionality to read the superblock of an existing ext4 block device
/// and format a new block device with the ext4 file system.
@@ -16,6 +16,10 @@
import Foundation
/// `AsyncLock` provides a familiar locking API, with the main benefit being that it
/// is safe to call async methods while holding the lock. This is primarily used in spots
/// where an actor makes sense, but we may need to ensure we don't fall victim to actor
/// reentrancy issues.
public actor AsyncLock {
private var busy = false
private var queue: ArraySlice<CheckedContinuation<(), Never>> = []
@@ -16,7 +16,11 @@
import Foundation
/// `Timeout` contains helpers to run an operation and error out if
/// the operation does not finish within a provided time.
public struct Timeout {
/// Performs the passed in `operation` and throws a `CancellationError` if the operation
/// doesn't finish in the provided `seconds` amount.
public static func run<T: Sendable>(
seconds: UInt32,
operation: @escaping @Sendable () async -> T
@@ -18,6 +18,8 @@ import ContainerizationOS
import Foundation
import NIO
/// `ReadStream` is a utility type for streaming data from a `URL`
/// or `Data` blob.
public class ReadStream {
public static let bufferSize = Int(1.mib())
@@ -18,6 +18,9 @@ import ContainerizationExtras
import ContainerizationOS
import Logging
/// `NetlinkSession` facilitates interacting with netlink via a provided
/// `NetlinkSocket`. This is the core high level type offered to perform
/// actions to the netlink surface in the kernel.
public struct NetlinkSession {
private static let receiveDataLength = 65536
+2
View File
@@ -27,6 +27,8 @@ private let _mount = Glibc.mount
private let _umount = Glibc.umount2
#endif
/// `Bundle` represents an OCI runtime spec bundle for running
/// a container.
public struct Bundle: Sendable {
public let path: URL
@@ -16,6 +16,7 @@
import Foundation
/// Abstraction for returning a token needed for logging into an OCI compliant registry.
public protocol Authentication: Sendable {
func token() async throws -> String
}
@@ -18,12 +18,14 @@
import Foundation
import ContainerizationOS
/// Helper type to lookup registry related values in the macOS keychain.
public struct KeychainHelper: Sendable {
private let id: String
public init(id: String) {
self.id = id
}
/// Lookup authorization data for a given registry domain.
public func lookup(domain: String) throws -> Authentication {
let kq = KeychainQuery()
@@ -40,22 +42,28 @@ public struct KeychainHelper: Sendable {
)
}
/// Delete authorization data for a given domain from the keychain.
public func delete(domain: String) throws {
let kq = KeychainQuery()
try kq.delete(id: self.id, host: domain)
}
/// Save authorization data for a given domain to the keychain.
public func save(domain: String, username: String, password: String) throws {
let kq = KeychainQuery()
try kq.save(id: self.id, host: domain, user: username, token: password)
}
/// Prompt for authorization data for a given domain to be saved to the keychain.
/// This will cause the current terminal to enter a password prompt state where
/// key strokes are hidden.
public func credentialPrompt(domain: String) throws -> Authentication {
let username = try userPrompt(domain: domain)
let password = try passwordPrompt()
return BasicAuthentication(username: username, password: password)
}
/// Prompts the current stdin for a username entry and then returns the value.
public func userPrompt(domain: String) throws -> String {
print("Provide registry username \(domain): ", terminator: "")
guard let username = readLine() else {
@@ -64,6 +72,9 @@ public struct KeychainHelper: Sendable {
return username
}
/// Prompts the current stdin for a password entry and then returns the value.
/// This will cause the current stdin (if it is a terminal) to hide keystrokes
/// by disabling echo.
public func passwordPrompt() throws -> String {
print("Provide registry password: ", terminator: "")
let console = try Terminal.current
@@ -17,6 +17,8 @@
import Foundation
import Synchronization
/// Async friendly wrapper around DispatchSourceSignal. Provides an AsyncStream
/// interface to get notified of received signals.
public final class AsyncSignalHandler: Sendable {
/// An async stream that returns the signal that was caught, if ever
public var signals: AsyncStream<Int32> {
+1
View File
@@ -16,6 +16,7 @@
import Foundation
/// Trivial type to discover information about a given file (uid, gid, mode...).
public struct File: Sendable {
public enum Error: Swift.Error, CustomStringConvertible {
case errno(_ e: Int32)
@@ -17,6 +17,7 @@
#if os(macOS)
import Foundation
/// Holds the result of a query to the keychain.
public struct KeychainQueryResult {
public var account: String
public var data: String
@@ -24,9 +25,11 @@ public struct KeychainQueryResult {
public var createdDate: Date
}
/// Type that facilitates interacting with the macOS keychain.
public struct KeychainQuery {
public init() {}
/// Save a value to the keychain.
public func save(id: String, host: String, user: String, token: String) throws {
if try exists(id: id, host: host) {
try delete(id: id, host: host)
@@ -48,6 +51,7 @@ public struct KeychainQuery {
guard status == errSecSuccess else { throw Self.Error.unhandledError(status: status) }
}
/// Delete a value from the keychain.
public func delete(id: String, host: String) throws {
let query: [String: Any] = [
kSecClass as String: kSecClassInternetPassword,
@@ -61,6 +65,7 @@ public struct KeychainQuery {
}
}
/// Retrieve a value from the keychain.
public func get(id: String, host: String) throws -> KeychainQueryResult? {
let query: [String: Any] = [
kSecClass as String: kSecClassInternetPassword,
@@ -113,6 +118,7 @@ public struct KeychainQuery {
return true
}
/// Check if a value exists in the keychain.
public func exists(id: String, host: String) throws -> Bool {
let query: [String: Any] = [
kSecClass as String: kSecClassInternetPassword,
+2 -1
View File
@@ -27,7 +27,8 @@ import Glibc
import Foundation
import Synchronization
/// Register file descriptors to receive events.
/// Register file descriptors to receive events via Linux's
/// epoll syscall surface.
public final class Epoll: Sendable {
public typealias Mask = Int32
public typealias Handler = (@Sendable (Mask) -> Void)
+21 -14
View File
@@ -26,10 +26,10 @@ private let _mount = Glibc.mount
private let _umount = Glibc.umount2
#endif
/// Mount package modeled closely from containerd's: https://github.com/containerd/containerd/tree/main/core/mount
/// Technically, this would be fine in the Linux subdirectory as it's Linux specific for now, but that
/// might not always be the case.
// Mount package modeled closely from containerd's: https://github.com/containerd/containerd/tree/main/core/mount
/// `Mount` models a Linux mount (although potentially could be used on other unix platforms), and
/// provides a simple interface to mount what the type describes.
public struct Mount: Sendable {
// Type specifies the host-specific of the mount.
public var type: String
@@ -99,6 +99,7 @@ extension Mount {
}
}
/// Whether the mount is read only.
public var readOnly: Bool {
for option in self.options {
if option == "ro" {
@@ -108,6 +109,23 @@ extension Mount {
return false
}
/// Mount the mount relative to `root` with the current set of data in the object.
/// Optionally provide `createWithPerms` to set the permissions for the directory that
/// it will be mounted at.
public func mount(root: String, createWithPerms: Int16? = nil) throws {
var rootURL = URL(fileURLWithPath: root)
rootURL = rootURL.resolvingSymlinksInPath()
rootURL = rootURL.appendingPathComponent(self.target)
try self.mountToTarget(target: rootURL.path, createWithPerms: createWithPerms)
}
/// Mount the mount with the current set of data in the object. Optionally
/// provide `createWithPerms` to set the permissions for the directory that
/// it will be mounted at.
public func mount(createWithPerms: Int16? = nil) throws {
try self.mountToTarget(target: self.target, createWithPerms: createWithPerms)
}
private func mountToTarget(target: String, createWithPerms: Int16?) throws {
let pageSize = sysconf(_SC_PAGESIZE)
@@ -154,17 +172,6 @@ extension Mount {
}
}
public func mount(root: String, createWithPerms: Int16? = nil) throws {
var rootURL = URL(fileURLWithPath: root)
rootURL = rootURL.resolvingSymlinksInPath()
rootURL = rootURL.appendingPathComponent(self.target)
try self.mountToTarget(target: rootURL.path, createWithPerms: createWithPerms)
}
public func mount(createWithPerms: Int16? = nil) throws {
try self.mountToTarget(target: self.target, createWithPerms: createWithPerms)
}
private func mkdirAll(_ name: String, _ perm: Int16) throws {
try FileManager.default.createDirectory(
atPath: name,
@@ -1,27 +0,0 @@
//===----------------------------------------------------------------------===//
// Copyright © 2025 Apple Inc. and the containerization project authors. All rights reserved.
//
// 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 Foundation
extension NSLock {
/// lock during the execution of the provided function
public func lock<T>(_ fn: () throws -> T) rethrows -> T {
self.lock()
defer { self.unlock() }
return try fn()
}
}
+2
View File
@@ -16,6 +16,8 @@
import Foundation
/// `Path` provides utilities to look for binaries in the current PATH,
/// or to return the current PATH.
public struct Path {
/// lookPath looks up an executable's path from $PATH
public static func lookPath(_ name: String) -> URL? {
+1
View File
@@ -16,6 +16,7 @@
import Foundation
/// Helper type with utilities to parse and manipulate unix signals.
public struct Signals {
public static func allNumeric() -> [Int32] {
Array(Signals.all.values)
@@ -44,6 +44,7 @@ let sysConnect = connect
let sysIoctl: @convention(c) (CInt, CUnsignedLong, UnsafeMutableRawPointer) -> CInt = ioctl
#endif
/// Thread-safe socket wrapper.
public final class Socket: Sendable {
public enum TimeoutOption {
case send
@@ -24,6 +24,7 @@ import Darwin
#error("SocketType not supported on this platform.")
#endif
/// Protocol used to describe the family of socket to be created with `Socket`.
public protocol SocketType: Sendable, CustomStringConvertible {
var domain: Int32 { get }
var type: Int32 { get }
@@ -27,6 +27,7 @@ let _SOCK_STREAM = SOCK_STREAM
#error("UnixType not supported on this platform.")
#endif
/// Unix domain socket variant of `SocketType`.
public struct UnixType: SocketType, Sendable, CustomStringConvertible {
public var domain: Int32 { AF_UNIX }
public var type: Int32 { _SOCK_STREAM }
@@ -26,6 +26,7 @@ import Darwin
#error("VsockType not supported on this platform.")
#endif
/// Vsock variant of `SocketType`.
public struct VsockType: SocketType, Sendable {
public var domain: Int32 { AF_VSOCK }
public var type: Int32 { _SOCK_STREAM }
+1
View File
@@ -24,6 +24,7 @@ import Darwin
#error("retryingSyscall not supported on this platform.")
#endif
/// Helper type to deal with running system calls.
public struct Syscall {
/// Retry a syscall on EINTR.
public static func retrying<T: FixedWidthInteger>(_ closure: () -> T) -> T {
+1
View File
@@ -16,6 +16,7 @@
import Foundation
/// Helper type to deal with system control functionalities.
public struct Sysctl {
#if os(macOS)
/// Simple `sysctlbyname` wrapper.
@@ -16,6 +16,8 @@
import Foundation
/// `Terminal` provides a clean interface to deal with terminal
/// interactions on Unix platforms.
public struct Terminal: Sendable {
private let initState: termios?
+2
View File
@@ -17,6 +17,8 @@
import ContainerizationError
import Foundation
/// `User` provides utilities to ensure that a given username exists in
/// /etc/passwd (and /etc/group).
public enum User {
private static let passwdFile = "/etc/passwd"
private static let groupFile = "/etc/group"
+1 -1
View File
@@ -35,7 +35,7 @@ extension String {
#endif
public final class ContainerStore: Sendable {
struct ContainerStore: Sendable {
private static let initImage = "vminit:latest"
private let content: ContentStore