CLI: Forego logging errors on multi-container operations (#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.
This commit is contained in:
Danny Canter
2026-02-05 09:47:20 -08:00
committed by GitHub
parent d79bc0d056
commit 6e9b8d724d
5 changed files with 64 additions and 40 deletions
@@ -0,0 +1,32 @@
//===----------------------------------------------------------------------===//
// Copyright © 2026 Apple Inc. and the container project authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//===----------------------------------------------------------------------===//
/// An error type that aggregates multiple errors into one.
///
/// When displayed, each underlying error is printed on its own line.
public struct AggregateError: Swift.Error, Sendable {
public let errors: [any Error]
public init(_ errors: [any Error]) {
self.errors = errors
}
}
extension AggregateError: CustomStringConvertible {
public var description: String {
errors.map { String(describing: $0) }.joined(separator: "\n")
}
}
@@ -81,11 +81,10 @@ extension Application {
}
}
var failed = [String]()
var errors: [any Error] = []
let force = self.force
let all = self.all
let logger = log
try await withThrowingTaskGroup(of: String?.self) { group in
try await withThrowingTaskGroup(of: (any Error)?.self) { group in
for container in containers {
group.addTask {
do {
@@ -100,25 +99,20 @@ extension Application {
print(container.id)
return nil
} catch {
logger.error("failed to delete container \(container.id): \(error)")
return container.id
return error
}
}
}
for try await ctr in group {
guard let ctr else {
continue
for try await error in group {
if let error {
errors.append(error)
}
failed.append(ctr)
}
}
if failed.count > 0 {
throw ContainerizationError(
.internalError,
message: "delete failed for one or more containers: \(failed)"
)
if !errors.isEmpty {
throw AggregateError(errors)
}
}
}
@@ -64,18 +64,17 @@ extension Application {
let signalNumber = try Signals.parseSignal(signal)
var failed: [String] = []
var errors: [any Error] = []
for container in containers {
do {
try await client.kill(id: container.id, signal: signalNumber)
print(container.id)
} catch {
log.error("failed to kill container \(container.id): \(error)")
failed.append(container.id)
errors.append(error)
}
}
if failed.count > 0 {
throw ContainerizationError(.internalError, message: "kill failed for one or more containers \(failed.joined(separator: ","))")
if !errors.isEmpty {
throw AggregateError(errors)
}
}
}
@@ -71,18 +71,16 @@ extension Application {
timeoutInSeconds: self.time,
signal: try Signals.parseSignal(self.signal)
)
let failed = try await Self.stopContainers(client: client, containers: containers, stopOptions: opts, log: log)
if failed.count > 0 {
throw ContainerizationError(
.internalError,
message: "stop failed for one or more containers \(failed.joined(separator: ","))"
)
}
try await Self.stopContainers(
client: client,
containers: containers,
stopOptions: opts
)
}
static func stopContainers(client: ContainerClient, containers: [ContainerSnapshot], stopOptions: ContainerStopOptions, log: Logger) async throws -> [String] {
var failed: [String] = []
try await withThrowingTaskGroup(of: ContainerSnapshot?.self) { group in
static func stopContainers(client: ContainerClient, containers: [ContainerSnapshot], stopOptions: ContainerStopOptions) async throws {
var errors: [any Error] = []
await withTaskGroup(of: (any Error)?.self) { group in
for container in containers {
group.addTask {
do {
@@ -90,21 +88,21 @@ extension Application {
print(container.id)
return nil
} catch {
log.error("failed to stop container \(container.id): \(error)")
return container
return error
}
}
}
for try await ctr in group {
guard let ctr else {
continue
for await error in group {
if let error {
errors.append(error)
}
failed.append(ctr.id)
}
}
return failed
if !errors.isEmpty {
throw AggregateError(errors)
}
}
}
}
@@ -67,10 +67,11 @@ extension Application {
let containers = try await client.list()
let signal = try Signals.parseSignal("SIGTERM")
let opts = ContainerStopOptions(timeoutInSeconds: Self.stopTimeoutSeconds, signal: signal)
let failed = try await ContainerStop.stopContainers(client: client, containers: containers, stopOptions: opts, log: log)
if !failed.isEmpty {
log.warning("some containers could not be stopped gracefully", metadata: ["ids": "\(failed)"])
}
try await ContainerStop.stopContainers(
client: client,
containers: containers,
stopOptions: opts,
)
} catch {
log.warning("failed to stop all containers", metadata: ["error": "\(error)"])
}