Skip to content
Merged
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
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,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 ; \
Expand Down
42 changes: 11 additions & 31 deletions Sources/ContainerCommands/Container/ContainerDelete.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 7 additions & 9 deletions Sources/ContainerCommands/Container/ContainerStop.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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)
Comment thread
jglogan marked this conversation as resolved.
print(container)
return nil
} catch {
return error
Expand Down
2 changes: 1 addition & 1 deletion Sources/ContainerCommands/System/SystemStop.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
37 changes: 37 additions & 0 deletions Tests/CLITests/Subcommands/Containers/TestCLINotFound.swift
Original file line number Diff line number Diff line change
@@ -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")
}
}
Loading