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
8 changes: 4 additions & 4 deletions Sources/ContainerCommands/Image/ImageDelete.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,15 @@ extension Application {
}

static func removeImage(options: RemoveImageOptions, containerSystemConfig: ContainerSystemConfig, log: Logger) async throws {
let (found, notFound) = try await {
let (found, lookupErrors) = try await {
if options.all {
let found = try await ClientImage.list()
let notFound: [String] = []
return (found, notFound)
let lookupErrors: [(String, Error)] = []
return (found, lookupErrors)
}
return try await ClientImage.get(names: options.images, containerSystemConfig: containerSystemConfig)
}()
var failures: [String] = options.force ? [] : notFound
var failures: [String] = options.force ? [] : lookupErrors.map { "\($0.0) (\($0.1.localizedDescription))" }
var didDeleteAnyImage = false
for image in found {
guard
Expand Down
4 changes: 2 additions & 2 deletions Sources/ContainerCommands/Image/ImageInspect.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ extension Application {
names: Array(uniqueNames), containerSystemConfig: containerSystemConfig
)

if !result.error.isEmpty {
let missing = result.error.sorted()
if !result.errors.isEmpty {
let missing = result.errors.map { "\($0.0) (\($0.1.localizedDescription))" }.sorted()
throw ContainerizationError(
.notFound,
message: "image not found: \(missing.joined(separator: ", "))"
Expand Down
15 changes: 9 additions & 6 deletions Sources/Services/ContainerAPIService/Client/ClientImage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -164,19 +164,20 @@ extension ClientImage {
}
}

public static func get(names: [String], containerSystemConfig: ContainerSystemConfig) async throws -> (images: [ClientImage], error: [String]) {
public static func get(names: [String], containerSystemConfig: ContainerSystemConfig) async throws -> (images: [ClientImage], errors: [(String, Error)]) {
let all = try await self.list()
var errors: [String] = []
var errors: [(String, Error)] = []
var found: [ClientImage] = []
for name in names {
do {
guard let img = try Self._search(reference: name, in: all, containerSystemConfig: containerSystemConfig) else {
errors.append(name)
let err = ContainerizationError(.notFound, message: "image not found")
errors.append((name, err))
continue
}
found.append(img)
} catch {
errors.append(name)
errors.append((name, error))
}
}
return (found, errors)
Expand Down Expand Up @@ -295,8 +296,10 @@ extension ClientImage {
public static func save(references: [String], out: String, platform: Platform? = nil, containerSystemConfig: ContainerSystemConfig) async throws {
let (clientImages, errors) = try await get(names: references, containerSystemConfig: containerSystemConfig)
guard errors.isEmpty else {
// TODO: Improve error handling here
throw ContainerizationError(.invalidArgument, message: "one or more image references are invalid: \(errors.joined(separator: ", "))")
let errorDetails = errors.map { name, error in
"\(name) (\(error.localizedDescription))"
}.joined(separator: ", ")
throw ContainerizationError(.invalidArgument, message: "one or more image references are invalid: \(errorDetails)")
}

let descriptions = clientImages.map { $0.description }
Expand Down
22 changes: 22 additions & 0 deletions Tests/CLITests/Subcommands/Images/TestCLIImagesCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -608,4 +608,26 @@ class TestCLIImagesCommand: CLITest {
#expect(stdout.isEmpty, "Expected stdout to be empty, got: \(stdout)")
#expect(stderr.contains("file does not exist") && stderr.contains(missingPath), "Expected stderr to contain error message, got: \(stderr)")
}

@Test func testImageSaveInvalidReferenceFails() throws {
let tempDir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)
defer {
try? FileManager.default.removeItem(at: tempDir)
}
let tempFile = tempDir.appendingPathComponent(UUID().uuidString)
let saveArgs = [
"image",
"save",
"nonexistent-image:latest",
"another-nonexistent-image:latest",
"--output",
tempFile.path(),
]
let (_, _, error, status) = try run(arguments: saveArgs)
#expect(status != 0, "expected save to fail for missing/invalid images")
#expect(error.contains("one or more image references are invalid:"), "expected validation error, got: \(error)")
#expect(error.contains("nonexistent-image:latest"), "expected first nonexistent image in error, got: \(error)")
#expect(error.contains("another-nonexistent-image:latest"), "expected second nonexistent image in error, got: \(error)")
}
}