| 
 | 1 | +//===----------------------------------------------------------------------===//  | 
 | 2 | +// Copyright © 2025 Apple Inc. and the container project authors. All rights reserved.  | 
 | 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 ArgumentParser  | 
 | 18 | +import ContainerClient  | 
 | 19 | +import ContainerNetworkService  | 
 | 20 | +import ContainerizationError  | 
 | 21 | +import Foundation  | 
 | 22 | + | 
 | 23 | +extension Application {  | 
 | 24 | +    struct NetworkDelete: AsyncParsableCommand {  | 
 | 25 | +        static let configuration = CommandConfiguration(  | 
 | 26 | +            commandName: "delete",  | 
 | 27 | +            abstract: "Delete one or more networks",  | 
 | 28 | +            aliases: ["rm"])  | 
 | 29 | + | 
 | 30 | +        @Flag(name: .shortAndLong, help: "Remove all networks")  | 
 | 31 | +        var all = false  | 
 | 32 | + | 
 | 33 | +        @OptionGroup  | 
 | 34 | +        var global: Flags.Global  | 
 | 35 | + | 
 | 36 | +        @Argument(help: "Network names")  | 
 | 37 | +        var networkNames: [String] = []  | 
 | 38 | + | 
 | 39 | +        func validate() throws {  | 
 | 40 | +            if networkNames.count == 0 && !all {  | 
 | 41 | +                throw ContainerizationError(.invalidArgument, message: "no networks specified and --all not supplied")  | 
 | 42 | +            }  | 
 | 43 | +            if networkNames.count > 0 && all {  | 
 | 44 | +                throw ContainerizationError(  | 
 | 45 | +                    .invalidArgument,  | 
 | 46 | +                    message: "explicitly supplied network name(s) conflict with the --all flag"  | 
 | 47 | +                )  | 
 | 48 | +            }  | 
 | 49 | +        }  | 
 | 50 | + | 
 | 51 | +        mutating func run() async throws {  | 
 | 52 | +            let uniqueNetworkNames = Set<String>(networkNames)  | 
 | 53 | +            let networks: [NetworkState]  | 
 | 54 | + | 
 | 55 | +            if all {  | 
 | 56 | +                networks = try await ClientNetwork.list()  | 
 | 57 | +            } else {  | 
 | 58 | +                networks = try await ClientNetwork.list()  | 
 | 59 | +                    .filter { c in  | 
 | 60 | +                        uniqueNetworkNames.contains(c.id)  | 
 | 61 | +                    }  | 
 | 62 | + | 
 | 63 | +                // If one of the networks requested isn't present lets throw. We don't need to do  | 
 | 64 | +                // this for --all as --all should be perfectly usable with no networks to remove,  | 
 | 65 | +                // otherwise it'd be quite clunky.  | 
 | 66 | +                if networks.count != uniqueNetworkNames.count {  | 
 | 67 | +                    let missing = uniqueNetworkNames.filter { id in  | 
 | 68 | +                        !networks.contains { n in  | 
 | 69 | +                            n.id == id  | 
 | 70 | +                        }  | 
 | 71 | +                    }  | 
 | 72 | +                    throw ContainerizationError(  | 
 | 73 | +                        .notFound,  | 
 | 74 | +                        message: "failed to delete one or more networks: \(missing)"  | 
 | 75 | +                    )  | 
 | 76 | +                }  | 
 | 77 | +            }  | 
 | 78 | + | 
 | 79 | +            if uniqueNetworkNames.contains(ClientNetwork.defaultNetworkName) {  | 
 | 80 | +                throw ContainerizationError(  | 
 | 81 | +                    .invalidArgument,  | 
 | 82 | +                    message: "cannot delete the default network"  | 
 | 83 | +                )  | 
 | 84 | +            }  | 
 | 85 | + | 
 | 86 | +            var failed = [String]()  | 
 | 87 | +            try await withThrowingTaskGroup(of: NetworkState?.self) { group in  | 
 | 88 | +                for network in networks {  | 
 | 89 | +                    group.addTask {  | 
 | 90 | +                        do {  | 
 | 91 | +                            // delete atomically disables the IP allocator, then deletes  | 
 | 92 | +                            // the allocator disable fails if any IPs are still in use  | 
 | 93 | +                            try await ClientNetwork.delete(id: network.id)  | 
 | 94 | +                            print(network.id)  | 
 | 95 | +                            return nil  | 
 | 96 | +                        } catch {  | 
 | 97 | +                            log.error("failed to delete network \(network.id): \(error)")  | 
 | 98 | +                            return network  | 
 | 99 | +                        }  | 
 | 100 | +                    }  | 
 | 101 | +                }  | 
 | 102 | + | 
 | 103 | +                for try await network in group {  | 
 | 104 | +                    guard let network else {  | 
 | 105 | +                        continue  | 
 | 106 | +                    }  | 
 | 107 | +                    failed.append(network.id)  | 
 | 108 | +                }  | 
 | 109 | +            }  | 
 | 110 | + | 
 | 111 | +            if failed.count > 0 {  | 
 | 112 | +                throw ContainerizationError(.internalError, message: "delete failed for one or more networks: \(failed)")  | 
 | 113 | +            }  | 
 | 114 | +        }  | 
 | 115 | +    }  | 
 | 116 | +}  | 
0 commit comments