diff --git a/Sources/APIServer/Containers/ContainersService.swift b/Sources/APIServer/Containers/ContainersService.swift index edadc3f0..31483ad9 100644 --- a/Sources/APIServer/Containers/ContainersService.swift +++ b/Sources/APIServer/Containers/ContainersService.swift @@ -41,6 +41,7 @@ actor ContainersService { struct Item: Sendable { let bundle: ContainerClient.Bundle var state: State + var startedAt: Date? enum State: Sendable { case dead @@ -81,7 +82,7 @@ actor ContainersService { do { let bundle = ContainerClient.Bundle(path: dir) let config = try bundle.configuration - results[config.id] = .init(bundle: bundle, state: .dead) + results[config.id] = .init(bundle: bundle, state: .dead, startedAt: nil) let plugin = runtimePlugins.first { $0.name == config.runtimeHandler } guard let plugin else { throw ContainerizationError(.internalError, message: "Failed to find runtime plugin \(config.runtimeHandler)") @@ -158,7 +159,7 @@ actor ContainersService { } throw error } - self.containers[configuration.id] = Item(bundle: bundle, state: .dead) + self.containers[configuration.id] = Item(bundle: bundle, state: .dead, startedAt: nil) } private func getInitBlock(for platform: Platform) async throws -> Filesystem { @@ -277,6 +278,7 @@ actor ContainersService { let configuration = try item.bundle.configuration let client = SandboxClient(id: configuration.id, runtime: configuration.runtimeHandler) item.state = .alive(client) + item.startedAt = Date() await self.setContainer(id, item, context: context) } catch { self.log.error( @@ -347,6 +349,7 @@ extension ContainersService.Item { configuration: config, status: RuntimeStatus.stopped, networks: [] + started: self.startedAt ), .stopped ) case .alive(let client): @@ -355,7 +358,8 @@ extension ContainersService.Item { .init( configuration: config, status: state.status, - networks: state.networks + networks: state.networks, + startedAt: self.startedAt ), state.status ) } diff --git a/Sources/CLI/Container/ContainerList.swift b/Sources/CLI/Container/ContainerList.swift index 43e5a4ce..fa30aad3 100644 --- a/Sources/CLI/Container/ContainerList.swift +++ b/Sources/CLI/Container/ContainerList.swift @@ -92,6 +92,7 @@ extension ClientContainer { self.configuration.platform.os, self.configuration.platform.architecture, self.status.rawValue, + self.configuration.startedAt.map { ISO8601DateFormatter().string(from: $0) } ?? "", self.networks.compactMap { try? CIDRAddress($0.address).address.description }.joined(separator: ","), ] } @@ -101,10 +102,12 @@ struct PrintableContainer: Codable { let status: RuntimeStatus let configuration: ContainerConfiguration let networks: [Attachment] + let startedAt: Date? init(_ container: ClientContainer) { self.status = container.status self.configuration = container.configuration self.networks = container.networks + self.startedAt = container.startedAt } } diff --git a/Sources/ContainerClient/Core/ClientContainer.swift b/Sources/ContainerClient/Core/ClientContainer.swift index 91744443..b9103604 100644 --- a/Sources/ContainerClient/Core/ClientContainer.swift +++ b/Sources/ContainerClient/Core/ClientContainer.swift @@ -47,6 +47,9 @@ public struct ClientContainer: Sendable, Codable { /// Network allocated to the container. public let networks: [Attachment] + /// When the container was started. + public let startedAt: Date? + package init(configuration: ContainerConfiguration) { self.configuration = configuration self.status = .stopped @@ -57,6 +60,7 @@ public struct ClientContainer: Sendable, Codable { self.configuration = snapshot.configuration self.status = snapshot.status self.networks = snapshot.networks + self.startedAt = snapshot.startedAt } public var initProcess: ClientProcess { @@ -248,4 +252,6 @@ extension ClientContainer { ) } } + + } diff --git a/Sources/ContainerClient/Core/ContainerSnapshot.swift b/Sources/ContainerClient/Core/ContainerSnapshot.swift index ebd6f1ca..cf6417b7 100644 --- a/Sources/ContainerClient/Core/ContainerSnapshot.swift +++ b/Sources/ContainerClient/Core/ContainerSnapshot.swift @@ -25,14 +25,18 @@ public struct ContainerSnapshot: Codable, Sendable { public let status: RuntimeStatus /// Network interfaces attached to the sandbox that are provided to the container. public let networks: [Attachment] + /// When the container was started. + public let startedAt: Date? public init( configuration: ContainerConfiguration, status: RuntimeStatus, - networks: [Attachment] + networks: [Attachment], + startedAt: Date? = nil ) { self.configuration = configuration self.status = status self.networks = networks + self.startedAt = startedAt } }