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
19 changes: 14 additions & 5 deletions Sources/ContainerCommands/Container/ContainerInspect.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
import ArgumentParser
import ContainerAPIClient
import ContainerResource
import ContainerizationError
import Foundation
import SwiftProtobuf

extension Application {
public struct ContainerInspect: AsyncLoggableCommand {
Expand All @@ -36,12 +36,21 @@ extension Application {

public func run() async throws {
let client = ContainerClient()
let uniqueIds = Set(containerIds)
let containers = try await client.list().filter {
containerIds.contains($0.id)
}.map {
PrintableContainer($0)
uniqueIds.contains($0.id)
}
try Output.emit(Output.renderJSON(containers))

if containers.count != uniqueIds.count {
let found = Set(containers.map { $0.id })
let missing = uniqueIds.subtracting(found).sorted()
throw ContainerizationError(
.notFound,
message: "container not found: \(missing.joined(separator: ", "))"
)
}

try Output.emit(Output.renderJSON(containers.map { PrintableContainer($0) }))
}
}
}
47 changes: 13 additions & 34 deletions Sources/ContainerCommands/Image/ImageInspect.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,10 @@

import ArgumentParser
import ContainerAPIClient
import ContainerLog
import ContainerPersistence
import ContainerPlugin
import ContainerResource
import ContainerizationError
import Foundation
import Logging
import SwiftProtobuf

extension Application {
public struct ImageInspect: AsyncLoggableCommand {
Expand All @@ -39,19 +35,22 @@ extension Application {

public init() {}

struct InspectError: Error {
let succeeded: [String]
let failed: [(String, Error)]
}

public func run() async throws {
let containerSystemConfig: ContainerSystemConfig = try await ConfigurationLoader.load()
var printable: [ImageDetail] = []
var succeededImages: [String] = []
var allErrors: [(String, Error)] = []
let uniqueNames = Set(images)
let result = try await ClientImage.get(
names: Array(uniqueNames), containerSystemConfig: containerSystemConfig
)

let result = try await ClientImage.get(names: images, containerSystemConfig: containerSystemConfig)
if !result.error.isEmpty {
let missing = result.error.sorted()
throw ContainerizationError(
.notFound,
message: "image not found: \(missing.joined(separator: ", "))"
)
}

var printable: [ImageDetail] = []
for image in result.images {
guard
!Utility.isInfraImage(
Expand All @@ -61,29 +60,9 @@ extension Application {
)
else { continue }
printable.append(try await image.details())
succeededImages.append(image.reference)
}

for missing in result.error {
allErrors.append((missing, ContainerizationError(.notFound, message: "Image not found")))
}

if !printable.isEmpty {
try Output.emit(Output.renderJSON(printable))
}

if !allErrors.isEmpty {
for (name, error) in allErrors {
log.error(
"image inspect failed",
metadata: [
"name": "\(name)",
"error": "\(error.localizedDescription)",
])
}

throw InspectError(succeeded: succeededImages, failed: allErrors)
}
try Output.emit(Output.renderJSON(printable))
}
}
}
14 changes: 13 additions & 1 deletion Sources/ContainerCommands/Network/NetworkInspect.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import ArgumentParser
import ContainerAPIClient
import ContainerizationError
import Foundation

extension Application {
Expand All @@ -34,7 +35,18 @@ extension Application {

public func run() async throws {
let networkClient = NetworkClient()
let items = try await networkClient.list().filter { networks.contains($0.id) }
let uniqueNames = Set(networks)
let items = try await networkClient.list().filter { uniqueNames.contains($0.id) }

if items.count != uniqueNames.count {
let found = Set(items.map { $0.id })
let missing = uniqueNames.subtracting(found).sorted()
throw ContainerizationError(
.notFound,
message: "network not found: \(missing.joined(separator: ", "))"
)
}

try Output.emit(Output.renderJSON(items))
}
}
Expand Down
16 changes: 11 additions & 5 deletions Sources/ContainerCommands/Volume/VolumeInspect.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import ArgumentParser
import ContainerAPIClient
import ContainerResource
import ContainerizationError
import Foundation

extension Application.VolumeCommand {
Expand All @@ -35,11 +36,16 @@ extension Application.VolumeCommand {
public init() {}

public func run() async throws {
var volumes: [Volume] = []

for name in names {
let volume = try await ClientVolume.inspect(name)
volumes.append(volume)
let uniqueNames = Set(names)
let volumes = try await ClientVolume.list().filter { uniqueNames.contains($0.id) }

if volumes.count != uniqueNames.count {
let found = Set(volumes.map { $0.id })
let missing = uniqueNames.subtracting(found).sorted()
throw ContainerizationError(
.notFound,
message: "volume not found: \(missing.joined(separator: ", "))"
)
}

let options = JSONOptions(
Expand Down
6 changes: 6 additions & 0 deletions Tests/CLITests/Subcommands/Containers/TestCLIRemove.swift
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,10 @@ class TestCLIRemove: CLITest {
let lines = output.split(separator: "\n").filter { $0.contains(name) }
#expect(lines.count == 1, "Expected container to be deleted exactly once, got \(lines.count) lines")
}

@Test func testInspectMissingContainerFails() throws {
let (_, _, error, status) = try run(arguments: ["inspect", "definitely-missing-container"])
#expect(status != 0, "Expected non-zero exit for missing container")
#expect(error.contains("container not found"))
}
}
6 changes: 6 additions & 0 deletions Tests/CLITests/Subcommands/Images/TestCLIImagesCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -593,4 +593,10 @@ class TestCLIImagesCommand: CLITest {
try FileManager.default.removeItem(atPath: tarPath)
try FileManager.default.moveItem(at: tempModifiedTar, to: URL(fileURLWithPath: tarPath))
}

@Test func testInspectMissingImageFails() throws {
let (_, _, error, status) = try run(arguments: ["image", "inspect", "definitely-missing-image:latest"])
#expect(status != 0, "Expected non-zero exit for missing image")
#expect(error.contains("image not found"))
}
}
6 changes: 6 additions & 0 deletions Tests/CLITests/Subcommands/Networks/TestCLINetwork.swift
Original file line number Diff line number Diff line change
Expand Up @@ -313,4 +313,10 @@ class TestCLINetwork: CLITest {
}
#expect(json.contains { ($0["id"] as? String) == name }, "JSON should contain the created network")
}

@Test func testInspectMissingNetworkFails() throws {
let (_, _, error, status) = try run(arguments: ["network", "inspect", "definitely-missing-network"])
#expect(status != 0, "Expected non-zero exit for missing network")
#expect(error.contains("network not found"))
}
}
8 changes: 8 additions & 0 deletions Tests/CLITests/Subcommands/Volumes/TestCLIVolumes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,14 @@ class TestCLIVolumes: CLITest {
#expect(error.contains("conflict"))
}

// MARK: - Inspect validation tests

@Test func testVolumeInspectMissingFails() throws {
let (_, _, error, status) = try run(arguments: ["volume", "inspect", "definitely-missing-volume"])
#expect(status != 0, "Expected non-zero exit for missing volume")
#expect(error.contains("volume not found"))
}

// MARK: - Journal option tests

@Test func testVolumeCreateWithJournalOrdered() throws {
Expand Down