mirror of
https://github.com/apple/container.git
synced 2026-08-28 19:36:31 +00:00
Make inspect error handling for missing resources consistent (#1564)
- Closes #1539. - All `inspect` commands now consistently return exit 1 with a clear error when a requested resource is missing.
This commit is contained in:
@@ -17,8 +17,8 @@
|
||||
import ArgumentParser
|
||||
import ContainerAPIClient
|
||||
import ContainerResource
|
||||
import ContainerizationError
|
||||
import Foundation
|
||||
import SwiftProtobuf
|
||||
|
||||
extension Application {
|
||||
public struct ContainerInspect: AsyncLoggableCommand {
|
||||
@@ -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) }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -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()
|
||||
let uniqueNames = Set(images)
|
||||
let result = try await ClientImage.get(
|
||||
names: Array(uniqueNames), containerSystemConfig: containerSystemConfig
|
||||
)
|
||||
|
||||
if !result.error.isEmpty {
|
||||
let missing = result.error.sorted()
|
||||
throw ContainerizationError(
|
||||
.notFound,
|
||||
message: "image not found: \(missing.joined(separator: ", "))"
|
||||
)
|
||||
}
|
||||
|
||||
var printable: [ImageDetail] = []
|
||||
var succeededImages: [String] = []
|
||||
var allErrors: [(String, Error)] = []
|
||||
|
||||
let result = try await ClientImage.get(names: images, containerSystemConfig: containerSystemConfig)
|
||||
|
||||
for image in result.images {
|
||||
guard
|
||||
!Utility.isInfraImage(
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import ArgumentParser
|
||||
import ContainerAPIClient
|
||||
import ContainerizationError
|
||||
import Foundation
|
||||
|
||||
extension Application {
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import ArgumentParser
|
||||
import ContainerAPIClient
|
||||
import ContainerResource
|
||||
import ContainerizationError
|
||||
import Foundation
|
||||
|
||||
extension Application.VolumeCommand {
|
||||
@@ -35,11 +36,16 @@ extension Application.VolumeCommand {
|
||||
public init() {}
|
||||
|
||||
public func run() async throws {
|
||||
var volumes: [Volume] = []
|
||||
let uniqueNames = Set(names)
|
||||
let volumes = try await ClientVolume.list().filter { uniqueNames.contains($0.id) }
|
||||
|
||||
for name in names {
|
||||
let volume = try await ClientVolume.inspect(name)
|
||||
volumes.append(volume)
|
||||
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(
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user