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
2 changes: 2 additions & 0 deletions Sources/Containerization/DNSConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"]

Expand Down
1 change: 1 addition & 0 deletions Sources/Containerization/Image/Image.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import SystemPackage
import ContainerizationExtras
#endif

/// Type representing an OCI container image.
public struct Image: Sendable {

private let contentStore: ContentStore
Expand Down
3 changes: 3 additions & 0 deletions Sources/Containerization/Image/InitImage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 }

Expand Down
4 changes: 3 additions & 1 deletion Sources/Containerization/Kernel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions Sources/Containerization/LinuxProcess.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}
Expand Down
2 changes: 2 additions & 0 deletions Sources/Containerization/NATNetworkInterface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
3 changes: 3 additions & 0 deletions Sources/Containerization/SystemPlatform.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions Sources/Containerization/VZVirtualMachineInstance.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion Sources/Containerization/VirtualMachineInstance.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Sources/ContainerizationEXT4/EXT4.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 4 additions & 0 deletions Sources/ContainerizationExtras/AsyncLock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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>> = []
Expand Down
4 changes: 4 additions & 0 deletions Sources/ContainerizationExtras/Timeout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions Sources/ContainerizationIO/ReadStream.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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())

Expand Down
3 changes: 3 additions & 0 deletions Sources/ContainerizationNetlink/NetlinkSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions Sources/ContainerizationOCI/Bundle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions Sources/ContainerizationOCI/Client/Authentication.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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 {
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions Sources/ContainerizationOS/AsyncSignalHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down
1 change: 1 addition & 0 deletions Sources/ContainerizationOS/File.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions Sources/ContainerizationOS/Keychain/KeychainQuery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,19 @@
#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
public var modifiedDate: Date
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)
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion Sources/ContainerizationOS/Linux/Epoll.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
35 changes: 21 additions & 14 deletions Sources/ContainerizationOS/Mount/Mount.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -99,6 +99,7 @@ extension Mount {
}
}

/// Whether the mount is read only.
public var readOnly: Bool {
for option in self.options {
if option == "ro" {
Expand All @@ -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)

Expand Down Expand Up @@ -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,
Expand Down
Loading