Skip to content

Commit 6e9b8d7

Browse files
authored
CLI: Forego logging errors on multi-container operations (apple#1163)
Instead of logging errors, and then additionally throwing an error stating what containers couldn't be stopped/killed/deleted, let's just concatenate the errors and throw the single error.
1 parent d79bc0d commit 6e9b8d7

File tree

5 files changed

+64
-40
lines changed

5 files changed

+64
-40
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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+
/// An error type that aggregates multiple errors into one.
18+
///
19+
/// When displayed, each underlying error is printed on its own line.
20+
public struct AggregateError: Swift.Error, Sendable {
21+
public let errors: [any Error]
22+
23+
public init(_ errors: [any Error]) {
24+
self.errors = errors
25+
}
26+
}
27+
28+
extension AggregateError: CustomStringConvertible {
29+
public var description: String {
30+
errors.map { String(describing: $0) }.joined(separator: "\n")
31+
}
32+
}

‎Sources/ContainerCommands/Container/ContainerDelete.swift‎

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,10 @@ extension Application {
8181
}
8282
}
8383

84-
var failed = [String]()
84+
var errors: [any Error] = []
8585
let force = self.force
8686
let all = self.all
87-
let logger = log
88-
try await withThrowingTaskGroup(of: String?.self) { group in
87+
try await withThrowingTaskGroup(of: (any Error)?.self) { group in
8988
for container in containers {
9089
group.addTask {
9190
do {
@@ -100,25 +99,20 @@ extension Application {
10099
print(container.id)
101100
return nil
102101
} catch {
103-
logger.error("failed to delete container \(container.id): \(error)")
104-
return container.id
102+
return error
105103
}
106104
}
107105
}
108106

109-
for try await ctr in group {
110-
guard let ctr else {
111-
continue
107+
for try await error in group {
108+
if let error {
109+
errors.append(error)
112110
}
113-
failed.append(ctr)
114111
}
115112
}
116113

117-
if failed.count > 0 {
118-
throw ContainerizationError(
119-
.internalError,
120-
message: "delete failed for one or more containers: \(failed)"
121-
)
114+
if !errors.isEmpty {
115+
throw AggregateError(errors)
122116
}
123117
}
124118
}

‎Sources/ContainerCommands/Container/ContainerKill.swift‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,18 +64,17 @@ extension Application {
6464

6565
let signalNumber = try Signals.parseSignal(signal)
6666

67-
var failed: [String] = []
67+
var errors: [any Error] = []
6868
for container in containers {
6969
do {
7070
try await client.kill(id: container.id, signal: signalNumber)
7171
print(container.id)
7272
} catch {
73-
log.error("failed to kill container \(container.id): \(error)")
74-
failed.append(container.id)
73+
errors.append(error)
7574
}
7675
}
77-
if failed.count > 0 {
78-
throw ContainerizationError(.internalError, message: "kill failed for one or more containers \(failed.joined(separator: ","))")
76+
if !errors.isEmpty {
77+
throw AggregateError(errors)
7978
}
8079
}
8180
}

‎Sources/ContainerCommands/Container/ContainerStop.swift‎

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -71,40 +71,38 @@ extension Application {
7171
timeoutInSeconds: self.time,
7272
signal: try Signals.parseSignal(self.signal)
7373
)
74-
let failed = try await Self.stopContainers(client: client, containers: containers, stopOptions: opts, log: log)
75-
if failed.count > 0 {
76-
throw ContainerizationError(
77-
.internalError,
78-
message: "stop failed for one or more containers \(failed.joined(separator: ","))"
79-
)
80-
}
74+
try await Self.stopContainers(
75+
client: client,
76+
containers: containers,
77+
stopOptions: opts
78+
)
8179
}
8280

83-
static func stopContainers(client: ContainerClient, containers: [ContainerSnapshot], stopOptions: ContainerStopOptions, log: Logger) async throws -> [String] {
84-
var failed: [String] = []
85-
try await withThrowingTaskGroup(of: ContainerSnapshot?.self) { group in
81+
static func stopContainers(client: ContainerClient, containers: [ContainerSnapshot], stopOptions: ContainerStopOptions) async throws {
82+
var errors: [any Error] = []
83+
await withTaskGroup(of: (any Error)?.self) { group in
8684
for container in containers {
8785
group.addTask {
8886
do {
8987
try await client.stop(id: container.id, opts: stopOptions)
9088
print(container.id)
9189
return nil
9290
} catch {
93-
log.error("failed to stop container \(container.id): \(error)")
94-
return container
91+
return error
9592
}
9693
}
9794
}
9895

99-
for try await ctr in group {
100-
guard let ctr else {
101-
continue
96+
for await error in group {
97+
if let error {
98+
errors.append(error)
10299
}
103-
failed.append(ctr.id)
104100
}
105101
}
106102

107-
return failed
103+
if !errors.isEmpty {
104+
throw AggregateError(errors)
105+
}
108106
}
109107
}
110108
}

‎Sources/ContainerCommands/System/SystemStop.swift‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,11 @@ extension Application {
6767
let containers = try await client.list()
6868
let signal = try Signals.parseSignal("SIGTERM")
6969
let opts = ContainerStopOptions(timeoutInSeconds: Self.stopTimeoutSeconds, signal: signal)
70-
let failed = try await ContainerStop.stopContainers(client: client, containers: containers, stopOptions: opts, log: log)
71-
if !failed.isEmpty {
72-
log.warning("some containers could not be stopped gracefully", metadata: ["ids": "\(failed)"])
73-
}
70+
try await ContainerStop.stopContainers(
71+
client: client,
72+
containers: containers,
73+
stopOptions: opts,
74+
)
7475
} catch {
7576
log.warning("failed to stop all containers", metadata: ["error": "\(error)"])
7677
}

0 commit comments

Comments
 (0)