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:
committed by
Kathryn Baldauf
parent
8d880efc36
commit
6bc4bf5124
@@ -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 }
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
+5
-3
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+11
@@ -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> {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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? {
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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?
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user