Skip to content
Open
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
1 change: 1 addition & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ let package = Package(
name: "ContainerAPIServiceTests",
dependencies: [
.product(name: "Containerization", package: "containerization"),
"ContainerAPIService",
"ContainerResource",
"ContainerRuntimeLinuxClient",
"ContainerRuntimeClient",
Expand Down
2 changes: 1 addition & 1 deletion Sources/ContainerCommands/Container/ContainerDelete.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ extension Application {
return c.id
}
} else {
containers = Array(Set(containerIds))
containers = try await client.resolve(ids: containerIds)
}

var errors: [any Error] = []
Expand Down
7 changes: 3 additions & 4 deletions Sources/ContainerCommands/Container/ContainerInspect.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,9 @@ extension Application {

public func run() async throws {
let client = ContainerClient()
let uniqueIds = Set(containerIds)
let containers = try await client.list().filter {
uniqueIds.contains($0.id)
}
let resolvedIds = try await client.resolve(ids: containerIds)
let uniqueIds = Set(resolvedIds)
let containers = try await client.list().filter { uniqueIds.contains($0.id) }

if containers.count != uniqueIds.count {
let found = Set(containers.map { $0.id })
Expand Down
2 changes: 1 addition & 1 deletion Sources/ContainerCommands/Container/ContainerKill.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ extension Application {
let filters = ContainerListFilters(status: .running).withoutMachines()
containers = try await client.list(filters: filters).map { $0.id }
} else {
containers = containerIds
containers = try await client.resolve(ids: containerIds)
}

var errors: [any Error] = []
Expand Down
17 changes: 11 additions & 6 deletions Sources/ContainerCommands/Container/ContainerStats.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,10 @@ extension Application {
containersToShow = try await client.list(filters: ContainerListFilters(status: .running))
} else {
// Fetch specified containers by ID
containersToShow = try await client.list(filters: ContainerListFilters(ids: containers))
let resolvedContainers = try await client.resolve(ids: containers)
containersToShow = try await client.list(filters: ContainerListFilters(ids: resolvedContainers))
// Validate all specified containers were found
for containerId in containers {
for containerId in resolvedContainers {
guard containersToShow.contains(where: { $0.id == containerId }) else {
throw ContainerizationError(
.notFound,
Expand All @@ -111,16 +112,20 @@ extension Application {
let client = ContainerClient()

// If containers were specified, validate they all exist upfront
let resolvedContainerIds: [String]
if !containerIds.isEmpty {
let specifiedContainers = try await client.list(filters: ContainerListFilters(ids: containerIds))
for containerId in containerIds {
resolvedContainerIds = try await client.resolve(ids: containerIds)
let specifiedContainers = try await client.list(filters: ContainerListFilters(ids: resolvedContainerIds))
for containerId in resolvedContainerIds {
guard specifiedContainers.contains(where: { $0.id == containerId }) else {
throw ContainerizationError(
.notFound,
message: "no such container: \(containerId)"
)
}
}
} else {
resolvedContainerIds = []
}

clearScreen()
Expand All @@ -130,10 +135,10 @@ extension Application {
while true {
do {
let containersToShow: [ContainerSnapshot]
if containerIds.isEmpty {
if resolvedContainerIds.isEmpty {
containersToShow = try await client.list(filters: ContainerListFilters(status: .running))
} else {
containersToShow = try await client.list(filters: ContainerListFilters(ids: containerIds))
containersToShow = try await client.list(filters: ContainerListFilters(ids: resolvedContainerIds))
}

let statsData = try await collectStats(client: client, for: containersToShow)
Expand Down
2 changes: 1 addition & 1 deletion Sources/ContainerCommands/Container/ContainerStop.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ extension Application {
let filters = ContainerListFilters().withoutMachines()
containers = try await client.list(filters: filters).map { $0.id }
} else {
containers = containerIds
containers = try await client.resolve(ids: containerIds)
}

let opts = ContainerStopOptions(
Expand Down
48 changes: 45 additions & 3 deletions Sources/Services/ContainerAPIService/Client/ContainerClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,16 +107,58 @@ public struct ContainerClient: Sendable {

/// Get the container for the provided id.
public func get(id: String) async throws -> ContainerSnapshot {
let containers = try await list(filters: ContainerListFilters(ids: [id]))
guard let container = containers.first else {
let containers = try await list()
let resolvedID = try Self.resolve(id: id, in: containers.map { $0.id })
guard let container = containers.first(where: { $0.id == resolvedID }) else {
throw ContainerizationError(
.notFound,
message: "get failed: container \(id) not found"
message: "container with ID \(id) not found"
)
}
return container
}

/// Resolve a container ID or unique ID prefix to its full ID.
public func resolve(id: String, filters: ContainerListFilters = .all) async throws -> String {
let containers = try await list(filters: filters)
let ids = containers.map { $0.id }
return try Self.resolve(id: id, in: ids)
}

/// Resolve container IDs or unique ID prefixes to their full IDs.
public func resolve(ids: [String], filters: ContainerListFilters = .all) async throws -> [String] {
let containers = try await list(filters: filters)
let containerIDs = containers.map { $0.id }
var seen = Set<String>()
var resolved = [String]()
for id in ids where seen.insert(id).inserted {
resolved.append(try Self.resolve(id: id, in: containerIDs))
}
return resolved
}

private static func resolve(id: String, in ids: [String]) throws -> String {
if ids.contains(id) {
return id
}

let matches = ids.filter { $0.hasPrefix(id) }
if matches.count == 1, let match = matches.first {
return match
}
if matches.count > 1 {
throw ContainerizationError(
.invalidArgument,
message: "container ID prefix \(id) is ambiguous"
)
}

throw ContainerizationError(
.notFound,
message: "container with ID \(id) not found"
)
}

/// Bootstrap the container's init process.
public func bootstrap(
id: String,
Expand Down
Loading