mirror of
https://github.com/apple/container.git
synced 2026-08-28 19:36:31 +00:00
CLI: stop and delete should error on not found containers (#878)
Same as we'd done with kill. ``` ➜ container git:(stop-kill-throw-error) ✗ ./bin/container stop foo bar baz Warning! Running debug build. Performance may be degraded. Error: internalError: "failed to stop container" (cause: "notFound: "container with ID baz not found"") internalError: "failed to stop container" (cause: "notFound: "container with ID bar not found"") internalError: "failed to stop container" (cause: "notFound: "container with ID foo not found"") ➜ container git:(stop-kill-throw-error) ✗ ./bin/container kill foo bar baz Warning! Running debug build. Performance may be degraded. Error: internalError: "failed to kill container" (cause: "notFound: "container with ID foo not found"") internalError: "failed to kill container" (cause: "notFound: "container with ID bar not found"") internalError: "failed to kill container" (cause: "notFound: "container with ID baz not found"") ➜ container git:(stop-kill-throw-error) ✗ ./bin/container delete foo bar baz Warning! Running debug build. Performance may be degraded. Error: internalError: "failed to delete container" (cause: "notFound: "container with ID foo not found"") internalError: "failed to delete container" (cause: "notFound: "container with ID baz not found"") internalError: "failed to delete container" (cause: "notFound: "container with ID bar not found"") ```
This commit is contained in:
@@ -207,6 +207,7 @@ integration: init-block
|
||||
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIVolumes || exit_code=1 ; \
|
||||
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIKernelSet || exit_code=1 ; \
|
||||
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIAnonymousVolumes || exit_code=1 ; \
|
||||
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLINotFound || exit_code=1 ; \
|
||||
$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLINoParallelCases || exit_code=1 ; \
|
||||
echo Ensuring apiserver stopped after the CLI integration tests ; \
|
||||
scripts/ensure-container-stopped.sh ; \
|
||||
|
||||
@@ -54,49 +54,29 @@ extension Application {
|
||||
}
|
||||
|
||||
public mutating func run() async throws {
|
||||
let set = Set<String>(containerIds)
|
||||
let client = ContainerClient()
|
||||
var containers = [ContainerSnapshot]()
|
||||
let force = self.force
|
||||
|
||||
let containers: [String]
|
||||
if all {
|
||||
containers = try await client.list()
|
||||
} else {
|
||||
let ctrs = try await client.list()
|
||||
containers = ctrs.filter { c in
|
||||
set.contains(c.id)
|
||||
}
|
||||
// If one of the containers requested isn't present, let's throw. We don't need to do
|
||||
// this for --all as --all should be perfectly usable with no containers to remove; otherwise,
|
||||
// it'd be quite clunky.
|
||||
if containers.count != set.count {
|
||||
let missing = set.filter { id in
|
||||
!containers.contains { c in
|
||||
c.id == id
|
||||
}
|
||||
containers = try await client.list().compactMap { c in
|
||||
// Skip running containers when using --all without --force
|
||||
if c.status == .running && !force {
|
||||
return nil
|
||||
}
|
||||
throw ContainerizationError(
|
||||
.notFound,
|
||||
message: "failed to delete one or more containers: \(missing)"
|
||||
)
|
||||
return c.id
|
||||
}
|
||||
} else {
|
||||
containers = containerIds
|
||||
}
|
||||
|
||||
var errors: [any Error] = []
|
||||
let force = self.force
|
||||
let all = self.all
|
||||
try await withThrowingTaskGroup(of: (any Error)?.self) { group in
|
||||
for container in containers {
|
||||
group.addTask {
|
||||
do {
|
||||
if container.status == .running && !force {
|
||||
guard all else {
|
||||
throw ContainerizationError(.invalidState, message: "container is running")
|
||||
}
|
||||
return nil // Skip running container when using --all
|
||||
}
|
||||
|
||||
try await client.delete(id: container.id, force: force)
|
||||
print(container.id)
|
||||
try await client.delete(id: container, force: force)
|
||||
print(container)
|
||||
return nil
|
||||
} catch {
|
||||
return error
|
||||
|
||||
@@ -56,15 +56,13 @@ extension Application {
|
||||
}
|
||||
|
||||
public mutating func run() async throws {
|
||||
let set = Set<String>(containerIds)
|
||||
let client = ContainerClient()
|
||||
var containers = [ContainerSnapshot]()
|
||||
|
||||
let containers: [String]
|
||||
if self.all {
|
||||
containers = try await client.list()
|
||||
containers = try await client.list().map { $0.id }
|
||||
} else {
|
||||
containers = try await client.list().filter { c in
|
||||
set.contains(c.id)
|
||||
}
|
||||
containers = containerIds
|
||||
}
|
||||
|
||||
let opts = ContainerStopOptions(
|
||||
@@ -78,14 +76,14 @@ extension Application {
|
||||
)
|
||||
}
|
||||
|
||||
static func stopContainers(client: ContainerClient, containers: [ContainerSnapshot], stopOptions: ContainerStopOptions) async throws {
|
||||
static func stopContainers(client: ContainerClient, containers: [String], stopOptions: ContainerStopOptions) async throws {
|
||||
var errors: [any Error] = []
|
||||
await withTaskGroup(of: (any Error)?.self) { group in
|
||||
for container in containers {
|
||||
group.addTask {
|
||||
do {
|
||||
try await client.stop(id: container.id, opts: stopOptions)
|
||||
print(container.id)
|
||||
try await client.stop(id: container, opts: stopOptions)
|
||||
print(container)
|
||||
return nil
|
||||
} catch {
|
||||
return error
|
||||
|
||||
@@ -64,7 +64,7 @@ extension Application {
|
||||
let client = ContainerClient()
|
||||
log.info("stopping containers", metadata: ["stopTimeoutSeconds": "\(Self.stopTimeoutSeconds)"])
|
||||
do {
|
||||
let containers = try await client.list()
|
||||
let containers = try await client.list().map { $0.id }
|
||||
let signal = try Signals.parseSignal("SIGTERM")
|
||||
let opts = ContainerStopOptions(timeoutInSeconds: Self.stopTimeoutSeconds, signal: signal)
|
||||
try await ContainerStop.stopContainers(
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
// 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.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import Foundation
|
||||
import Testing
|
||||
|
||||
/// Tests that stop, kill, and delete return errors for non-existent containers.
|
||||
class TestCLINotFound: CLITest {
|
||||
|
||||
@Test func testStopNonExistentContainer() throws {
|
||||
let (_, _, _, status) = try run(arguments: ["stop", "does-not-exist"])
|
||||
#expect(status != 0, "stop should fail for a non-existent container")
|
||||
}
|
||||
|
||||
@Test func testKillNonExistentContainer() throws {
|
||||
let (_, _, _, status) = try run(arguments: ["kill", "does-not-exist"])
|
||||
#expect(status != 0, "kill should fail for a non-existent container")
|
||||
}
|
||||
|
||||
@Test func testDeleteNonExistentContainer() throws {
|
||||
let (_, _, _, status) = try run(arguments: ["delete", "does-not-exist"])
|
||||
#expect(status != 0, "delete should fail for a non-existent container")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user