Skip to content

Commit fbea4e9

Browse files
committed
Image list: skip unreadable entries
1 parent d6de569 commit fbea4e9

2 files changed

Lines changed: 86 additions & 7 deletions

File tree

‎Sources/ContainerCommands/Image/ImageList.swift‎

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,16 @@ extension Application {
6363
return
6464
}
6565

66-
let resources = try await Self.buildResources(images: images, containerSystemConfig: containerSystemConfig)
66+
let resources = await Self.buildResources(
67+
images: images,
68+
containerSystemConfig: containerSystemConfig,
69+
onError: { image, error in
70+
log.warning(
71+
"skipping unreadable image",
72+
metadata: ["image": "\(image.reference)", "error": "\(error)"]
73+
)
74+
}
75+
)
6776

6877
try Output.render(payload: resources, format: format) {
6978
if verbose {
@@ -81,14 +90,34 @@ extension Application {
8190

8291
/// Builds the resource for each image, denormalizing the reference so the
8392
/// display name omits the default registry.
84-
private static func buildResources(images: [ClientImage], containerSystemConfig: ContainerSystemConfig) async throws -> [ImageResource] {
85-
var resources: [ImageResource] = []
86-
for image in images {
87-
resources.append(
93+
private static func buildResources(
94+
images: [ClientImage],
95+
containerSystemConfig: ContainerSystemConfig,
96+
onError: (ClientImage, any Error) -> Void
97+
) async -> [ImageResource] {
98+
await collectReadableValues(
99+
from: images,
100+
resolve: { image in
88101
try await image.toImageResource(containerSystemConfig: containerSystemConfig)
89-
)
102+
},
103+
onError: onError
104+
)
105+
}
106+
107+
static func collectReadableValues<Input, Value>(
108+
from inputs: [Input],
109+
resolve: (Input) async throws -> Value,
110+
onError: (Input, any Error) -> Void
111+
) async -> [Value] {
112+
var values: [Value] = []
113+
for input in inputs {
114+
do {
115+
values.append(try await resolve(input))
116+
} catch {
117+
onError(input, error)
118+
}
90119
}
91-
return resources
120+
return values
92121
}
93122
}
94123
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//===----------------------------------------------------------------------===//
2+
// Copyright © 2026 Apple Inc. and the container project authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// https://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//===----------------------------------------------------------------------===//
16+
17+
import Testing
18+
19+
@testable import ContainerCommands
20+
21+
struct ImageListTests {
22+
private enum TestError: Error {
23+
case unreadable
24+
}
25+
26+
@Test
27+
func unreadableEntryDoesNotPreventOtherEntriesFromResolving() async {
28+
let inputs = ["healthy-one", "unreadable", "healthy-two"]
29+
var failures: [String] = []
30+
var failureDescriptions: [String] = []
31+
32+
let values = await Application.ImageList.collectReadableValues(
33+
from: inputs,
34+
resolve: { input in
35+
if input == "unreadable" {
36+
throw TestError.unreadable
37+
}
38+
return input.uppercased()
39+
},
40+
onError: { input, error in
41+
failures.append(input)
42+
failureDescriptions.append("\(error)")
43+
}
44+
)
45+
46+
#expect(values == ["HEALTHY-ONE", "HEALTHY-TWO"])
47+
#expect(failures == ["unreadable"])
48+
#expect(failureDescriptions == ["unreadable"])
49+
}
50+
}

0 commit comments

Comments
 (0)