Reorganize client libraries. (#1020)

- Closes #461.
- Extract core types into ContainerResources target.
- Extract ContainerNetworkServiceClient from ContainerNetworkService.
- Relocate sandbox client from ContainerClient to
ContainerSandboxServiceClient.
- Relocate ContainerClient to ContainerAPIServiceClient.
- Common structure from services and clients under Source/Services.

Updated project hierarchy:

```
Sources/CAuditToken - audit token access wrapper
Sources/CLI - CLI executable
Sources/ContainerBuild - builder
Sources/ContainerCommands - CLI command implementations
Sources/ContainerLog - logging helpers
Sources/ContainerPersistence - persistent data and system property helpers
Sources/ContainerPlugin - plugin system
Sources/ContainerResource - resource (container, image, volume, network) types
Sources/ContainerVersion - version helpers
Sources/ContainerXPC - XPC helpers
Sources/CVersion - injected project version
Sources/DNSServer - container DNS resolver
Sources/Helpers - service executables
Sources/Services/*/Client - service clients
Sources/Services/*/Server - service implementations
Sources/SocketForwarder - port forwarding
Sources/TerminalProgress - progress bar
```

## Type of Change
- [ ] Bug fix
- [ ] New feature  
- [x] Breaking change
- [ ] Documentation update

## Motivation and Context
The ContainerClient library was a bit of a grab bag. This refactor
applies a more sensible project and library structure for resource data
types, services, and clients.

## Testing
- [x] Tested locally
- [x] Added/updated tests
- [ ] Added/updated docs
This commit is contained in:
J Logan
2026-01-06 08:27:14 -08:00
committed by GitHub
parent 8c439cd356
commit 356c8d2f88
165 changed files with 643 additions and 472 deletions
@@ -14,7 +14,7 @@
// limitations under the License.
//===----------------------------------------------------------------------===//
import ContainerClient
import ContainerResource
import Foundation
import Testing
@@ -14,7 +14,7 @@
// limitations under the License.
//===----------------------------------------------------------------------===//
import ContainerClient
import ContainerAPIClient
import ContainerizationOCI
import Foundation
import Testing
@@ -15,7 +15,7 @@
//===----------------------------------------------------------------------===//
import AsyncHTTPClient
import ContainerClient
import ContainerAPIClient
import ContainerizationError
import ContainerizationExtras
import ContainerizationOS
@@ -15,7 +15,7 @@
//===----------------------------------------------------------------------===//
import AsyncHTTPClient
import ContainerClient
import ContainerAPIClient
import ContainerizationExtras
import ContainerizationOS
import Foundation
@@ -14,7 +14,7 @@
// limitations under the License.
//===----------------------------------------------------------------------===//
import ContainerClient
import ContainerAPIClient
import ContainerPersistence
import ContainerizationArchive
import Foundation
@@ -81,7 +81,7 @@ class TestCLIKernelSet: CLITest {
try await withTempDir { tempDir in
// manually download the tar file
let localTarPath = tempDir.appending(path: remoteTar.lastPathComponent)
try await ContainerClient.FileDownloader.downloadFile(url: remoteTar, to: localTarPath)
try await ContainerAPIClient.FileDownloader.downloadFile(url: remoteTar, to: localTarPath)
let extraArgs: [String] = [
"--tar",
@@ -113,7 +113,7 @@ class TestCLIKernelSet: CLITest {
try await withTempDir { tempDir in
// manually download the tar file
let localTarPath = tempDir.appending(path: remoteTar.lastPathComponent)
try await ContainerClient.FileDownloader.downloadFile(url: remoteTar, to: localTarPath)
try await ContainerAPIClient.FileDownloader.downloadFile(url: remoteTar, to: localTarPath)
// extract just the file we want
let targetPath = tempDir.appending(path: URL(string: defaultBinaryPath)!.lastPathComponent)
@@ -14,7 +14,7 @@
// limitations under the License.
//===----------------------------------------------------------------------===//
import ContainerClient
import ContainerResource
import Foundation
import Testing
@@ -14,7 +14,7 @@
// limitations under the License.
//===----------------------------------------------------------------------===//
import ContainerClient
import ContainerAPIClient
import Foundation
import Testing
+1 -1
View File
@@ -14,7 +14,7 @@
// limitations under the License.
//===----------------------------------------------------------------------===//
import ContainerClient
import ContainerAPIClient
import ContainerizationOCI
import Foundation
import Testing
+2 -3
View File
@@ -15,8 +15,7 @@
//===----------------------------------------------------------------------===//
import AsyncHTTPClient
import ContainerClient
import ContainerNetworkService
import ContainerResource
import Containerization
import ContainerizationOS
import Foundation
@@ -305,7 +304,7 @@ class CLITest {
struct inspectOutput: Codable {
let status: String
let configuration: ContainerConfiguration
let networks: [ContainerNetworkService.Attachment]
let networks: [ContainerResource.Attachment]
}
func getContainerStatus(_ name: String) throws -> String {
@@ -1,5 +1,5 @@
//===----------------------------------------------------------------------===//
// Copyright © 2025-2026 Apple Inc. and the container project authors.
// 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.
@@ -17,7 +17,7 @@
import Foundation
import Testing
@testable import ContainerClient
@testable import ContainerAPIClient
struct DiskUsageTests {
@@ -1,5 +1,5 @@
//===----------------------------------------------------------------------===//
// Copyright © 2025-2026 Apple Inc. and the container project authors.
// 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.
@@ -18,7 +18,7 @@ import ContainerizationError
import Foundation
import Testing
@testable import ContainerClient
@testable import ContainerAPIClient
struct HostDNSResolverTest {
@Test
@@ -1,5 +1,5 @@
//===----------------------------------------------------------------------===//
// Copyright © 2025-2026 Apple Inc. and the container project authors.
// 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.
@@ -17,7 +17,7 @@
import Foundation
import Testing
@testable import ContainerClient
@testable import ContainerAPIClient
struct MeasurementParseTests {
@@ -1,5 +1,5 @@
//===----------------------------------------------------------------------===//
// Copyright © 2025-2026 Apple Inc. and the container project authors.
// 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.
@@ -18,7 +18,7 @@ import ContainerizationError
import Foundation
import Testing
@testable import ContainerClient
@testable import ContainerAPIClient
struct ParserTest {
@Test
@@ -1,5 +1,5 @@
//===----------------------------------------------------------------------===//
// Copyright © 2025-2026 Apple Inc. and the container project authors.
// 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.
@@ -19,7 +19,7 @@ import ContainerizationError
import Foundation
import Testing
@testable import ContainerClient
@testable import ContainerAPIClient
struct RequestSchemeTests {
static let defaultDnsDomain = DefaultsStore.get(key: .defaultDNSDomain)
@@ -1,5 +1,5 @@
//===----------------------------------------------------------------------===//
// Copyright © 2025-2026 Apple Inc. and the container project authors.
// 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.
@@ -14,11 +14,12 @@
// limitations under the License.
//===----------------------------------------------------------------------===//
import ContainerResource
import ContainerizationError
import Foundation
import Testing
@testable import ContainerClient
@testable import ContainerAPIClient
struct UtilityTests {
@@ -90,44 +91,21 @@ struct UtilityTests {
}
@Test
func testPublishPortsNonOverlapping() throws {
let result = try Parser.publishPorts([
"8080-8179:9000-9099/tcp",
"8180-8279:9100-9199/tcp",
func testPublishPortParser() throws {
let ports = try Parser.publishPorts([
"127.0.0.1:8000:9080",
"8080-8179:9000-9099/udp",
])
#expect(result.count == 2)
try Utility.validPublishPorts(result)
}
@Test
func testPublishPortsOverlapping() throws {
let result = try Parser.publishPorts([
"9000-9100:8080-8180/tcp",
"9100-9199:8180-8279/tcp",
])
#expect(result.count == 2)
#expect {
try Utility.validPublishPorts(result)
} throws: { error in
guard let error = error as? ContainerizationError else {
return false
}
return error.description.contains("port specs may not overlap")
}
}
@Test
func testPublishPortsSamePortDifferentProtocols() throws {
let result = try Parser.publishPorts([
"8080:8080/tcp",
"8080:8080/udp",
"1024-2048:1024-2048/tcp",
"1024-2048:1024-2048/udp",
"8081:8081",
"8081:8081/udp",
])
#expect(result.count == 6)
try Utility.validPublishPorts(result)
#expect(ports.count == 2)
#expect(ports[0].hostAddress == "127.0.0.1")
#expect(ports[0].hostPort == 8000)
#expect(ports[0].containerPort == 9080)
#expect(ports[0].proto == .tcp)
#expect(ports[0].count == 1)
#expect(ports[1].hostAddress == "0.0.0.0")
#expect(ports[1].hostPort == 8080)
#expect(ports[1].containerPort == 9000)
#expect(ports[1].proto == .udp)
#expect(ports[1].count == 100)
}
}
@@ -1,5 +1,5 @@
//===----------------------------------------------------------------------===//
// Copyright © 2025-2026 Apple Inc. and the container project authors.
// 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.
@@ -18,7 +18,7 @@ import ContainerizationError
import ContainerizationExtras
import Testing
@testable import ContainerNetworkService
@testable import ContainerResource
struct NetworkConfigurationTest {
@Test func testValidationOkDefaults() throws {
@@ -0,0 +1,51 @@
//===----------------------------------------------------------------------===//
// 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
@testable import ContainerResource
struct PublshPortTests {
@Test
func testPublishPortsNonOverlapping() throws {
let ports = [
PublishPort(hostAddress: "0.0.0.0", hostPort: 9000, containerPort: 8080, proto: .tcp, count: 100),
PublishPort(hostAddress: "0.0.0.0", hostPort: 9100, containerPort: 8180, proto: .tcp, count: 100),
]
#expect(!ports.hasOverlaps())
}
@Test
func testPublishPortsOverlapping() throws {
let ports = [
PublishPort(hostAddress: "0.0.0.0", hostPort: 9000, containerPort: 8080, proto: .tcp, count: 101),
PublishPort(hostAddress: "0.0.0.0", hostPort: 9100, containerPort: 8180, proto: .tcp, count: 100),
]
#expect(ports.hasOverlaps())
}
@Test
func testPublishPortsSamePortDifferentProtocols() throws {
let ports = [
PublishPort(hostAddress: "0.0.0.0", hostPort: 8080, containerPort: 8080, proto: .tcp, count: 1),
PublishPort(hostAddress: "0.0.0.0", hostPort: 8080, containerPort: 8080, proto: .udp, count: 1),
PublishPort(hostAddress: "0.0.0.0", hostPort: 1024, containerPort: 1024, proto: .tcp, count: 1025),
PublishPort(hostAddress: "0.0.0.0", hostPort: 1024, containerPort: 1024, proto: .udp, count: 1025),
]
#expect(!ports.hasOverlaps())
}
}
@@ -1,5 +1,5 @@
//===----------------------------------------------------------------------===//
// Copyright © 2025-2026 Apple Inc. and the container project authors.
// 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.
@@ -17,7 +17,7 @@
import Foundation
import Testing
@testable import ContainerClient
@testable import ContainerResource
struct VolumeValidationTests {