Skip to content

Commit f1ba52f

Browse files
authored
Merge branch 'main' into fix-image-size
2 parents 137f452 + 474906d commit f1ba52f

17 files changed

Lines changed: 419 additions & 84 deletions

File tree

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import PackageDescription
2222

2323
let releaseVersion = ProcessInfo.processInfo.environment["RELEASE_VERSION"] ?? "0.0.0"
2424
let gitCommit = ProcessInfo.processInfo.environment["GIT_COMMIT"] ?? "unspecified"
25-
let builderShimVersion = "0.7.0"
25+
let builderShimVersion = "0.8.0"
2626
let scVersion = "0.25.0"
2727

2828
let package = Package(

Sources/ContainerBuild/BuildFSSync.swift

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import Collections
1818
import ContainerAPIClient
1919
import ContainerizationArchive
2020
import ContainerizationOCI
21+
import CryptoKit
2122
import Foundation
2223
import GRPC
2324

@@ -199,7 +200,7 @@ actor BuildFSSync: BuildPipelineHandler {
199200
format: .paxRestricted,
200201
filter: .none)
201202

202-
try Archiver.compress(
203+
let tarHash = try Archiver.compress(
203204
source: contextDir,
204205
destination: tarURL,
205206
writerConfiguration: writerCfg
@@ -229,6 +230,25 @@ actor BuildFSSync: BuildPipelineHandler {
229230
pathInArchive: URL(fileURLWithPath: rel))
230231
}
231232

233+
let hash = tarHash.compactMap { String(format: "%02x", $0) }.joined()
234+
let header = BuildTransfer(
235+
id: packet.id,
236+
source: tarURL.path,
237+
complete: false,
238+
isDir: false,
239+
metadata: [
240+
"os": "linux",
241+
"stage": "fssync",
242+
"mode": "tar",
243+
"hash": hash,
244+
]
245+
)
246+
var resp = ClientStream()
247+
resp.buildID = buildID
248+
resp.buildTransfer = header
249+
resp.packetType = .buildTransfer(header)
250+
sender.yield(resp)
251+
232252
for try await chunk in try tarURL.bufferedCopyReader() {
233253
let part = BuildTransfer(
234254
id: packet.id,
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//===----------------------------------------------------------------------===//
2+
// Copyright © 2026 Apple Inc. and the container project authors.
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+
/// An error type that aggregates multiple errors into one.
18+
///
19+
/// When displayed, each underlying error is printed on its own line.
20+
public struct AggregateError: Swift.Error, Sendable {
21+
public let errors: [any Error]
22+
23+
public init(_ errors: [any Error]) {
24+
self.errors = errors
25+
}
26+
}
27+
28+
extension AggregateError: CustomStringConvertible {
29+
public var description: String {
30+
errors.map { String(describing: $0) }.joined(separator: "\n")
31+
}
32+
}

Sources/ContainerCommands/Container/ContainerCreate.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ extension Application {
7878
resource: resourceFlags,
7979
registry: registryFlags,
8080
imageFetch: imageFetchFlags,
81-
progressUpdate: progress.handler
81+
progressUpdate: progress.handler,
82+
log: log
8283
)
8384

8485
let options = ContainerCreateOptions(autoRemove: managementFlags.remove)

Sources/ContainerCommands/Container/ContainerDelete.swift

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,10 @@ extension Application {
8181
}
8282
}
8383

84-
var failed = [String]()
84+
var errors: [any Error] = []
8585
let force = self.force
8686
let all = self.all
87-
let logger = log
88-
try await withThrowingTaskGroup(of: String?.self) { group in
87+
try await withThrowingTaskGroup(of: (any Error)?.self) { group in
8988
for container in containers {
9089
group.addTask {
9190
do {
@@ -100,25 +99,20 @@ extension Application {
10099
print(container.id)
101100
return nil
102101
} catch {
103-
logger.error("failed to delete container \(container.id): \(error)")
104-
return container.id
102+
return error
105103
}
106104
}
107105
}
108106

109-
for try await ctr in group {
110-
guard let ctr else {
111-
continue
107+
for try await error in group {
108+
if let error {
109+
errors.append(error)
112110
}
113-
failed.append(ctr)
114111
}
115112
}
116113

117-
if failed.count > 0 {
118-
throw ContainerizationError(
119-
.internalError,
120-
message: "delete failed for one or more containers: \(failed)"
121-
)
114+
if !errors.isEmpty {
115+
throw AggregateError(errors)
122116
}
123117
}
124118
}

Sources/ContainerCommands/Container/ContainerKill.swift

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,18 +64,17 @@ extension Application {
6464

6565
let signalNumber = try Signals.parseSignal(signal)
6666

67-
var failed: [String] = []
67+
var errors: [any Error] = []
6868
for container in containers {
6969
do {
7070
try await client.kill(id: container.id, signal: signalNumber)
7171
print(container.id)
7272
} catch {
73-
log.error("failed to kill container \(container.id): \(error)")
74-
failed.append(container.id)
73+
errors.append(error)
7574
}
7675
}
77-
if failed.count > 0 {
78-
throw ContainerizationError(.internalError, message: "kill failed for one or more containers \(failed.joined(separator: ","))")
76+
if !errors.isEmpty {
77+
throw AggregateError(errors)
7978
}
8079
}
8180
}

Sources/ContainerCommands/Container/ContainerRun.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ extension Application {
103103
resource: resourceFlags,
104104
registry: registryFlags,
105105
imageFetch: imageFetchFlags,
106-
progressUpdate: progress.handler
106+
progressUpdate: progress.handler,
107+
log: log
107108
)
108109

109110
progress.set(description: "Starting container")

Sources/ContainerCommands/Container/ContainerStop.swift

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -71,40 +71,38 @@ extension Application {
7171
timeoutInSeconds: self.time,
7272
signal: try Signals.parseSignal(self.signal)
7373
)
74-
let failed = try await Self.stopContainers(client: client, containers: containers, stopOptions: opts, log: log)
75-
if failed.count > 0 {
76-
throw ContainerizationError(
77-
.internalError,
78-
message: "stop failed for one or more containers \(failed.joined(separator: ","))"
79-
)
80-
}
74+
try await Self.stopContainers(
75+
client: client,
76+
containers: containers,
77+
stopOptions: opts
78+
)
8179
}
8280

83-
static func stopContainers(client: ContainerClient, containers: [ContainerSnapshot], stopOptions: ContainerStopOptions, log: Logger) async throws -> [String] {
84-
var failed: [String] = []
85-
try await withThrowingTaskGroup(of: ContainerSnapshot?.self) { group in
81+
static func stopContainers(client: ContainerClient, containers: [ContainerSnapshot], stopOptions: ContainerStopOptions) async throws {
82+
var errors: [any Error] = []
83+
await withTaskGroup(of: (any Error)?.self) { group in
8684
for container in containers {
8785
group.addTask {
8886
do {
8987
try await client.stop(id: container.id, opts: stopOptions)
9088
print(container.id)
9189
return nil
9290
} catch {
93-
log.error("failed to stop container \(container.id): \(error)")
94-
return container
91+
return error
9592
}
9693
}
9794
}
9895

99-
for try await ctr in group {
100-
guard let ctr else {
101-
continue
96+
for await error in group {
97+
if let error {
98+
errors.append(error)
10299
}
103-
failed.append(ctr.id)
104100
}
105101
}
106102

107-
return failed
103+
if !errors.isEmpty {
104+
throw AggregateError(errors)
105+
}
108106
}
109107
}
110108
}

Sources/ContainerCommands/System/SystemStop.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,11 @@ extension Application {
6767
let containers = try await client.list()
6868
let signal = try Signals.parseSignal("SIGTERM")
6969
let opts = ContainerStopOptions(timeoutInSeconds: Self.stopTimeoutSeconds, signal: signal)
70-
let failed = try await ContainerStop.stopContainers(client: client, containers: containers, stopOptions: opts, log: log)
71-
if !failed.isEmpty {
72-
log.warning("some containers could not be stopped gracefully", metadata: ["ids": "\(failed)"])
73-
}
70+
try await ContainerStop.stopContainers(
71+
client: client,
72+
containers: containers,
73+
stopOptions: opts,
74+
)
7475
} catch {
7576
log.warning("failed to stop all containers", metadata: ["error": "\(error)"])
7677
}

Sources/Services/ContainerAPIService/Client/Archiver.swift

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@
1616

1717
import ContainerizationArchive
1818
import ContainerizationOS
19+
import CryptoKit
1920
import Foundation
2021

2122
public final class Archiver: Sendable {
22-
public struct ArchiveEntryInfo: Sendable {
23+
public struct ArchiveEntryInfo: Sendable, Codable {
2324
public let pathOnHost: URL
2425
public let pathInArchive: URL
2526

@@ -48,13 +49,15 @@ public final class Archiver: Sendable {
4849
followSymlinks: Bool = false,
4950
writerConfiguration: ArchiveWriterConfiguration = ArchiveWriterConfiguration(format: .paxRestricted, filter: .gzip),
5051
closure: (URL) -> ArchiveEntryInfo?
51-
) throws {
52+
) throws -> SHA256.Digest {
5253
let source = source.standardizedFileURL
5354
let destination = destination.standardizedFileURL
5455

5556
let fileManager = FileManager.default
5657
try? fileManager.removeItem(at: destination)
5758

59+
var hasher = SHA256()
60+
5861
do {
5962
let directory = destination.deletingLastPathComponent()
6063
try fileManager.createDirectory(at: directory, withIntermediateDirectories: true)
@@ -71,7 +74,8 @@ public final class Archiver: Sendable {
7174
entryInfo.append(info)
7275
}
7376
} else {
74-
while let relPath = enumerator.nextObject() as? String {
77+
let relPaths = enumerator.compactMap { $0 as? String }
78+
for relPath in relPaths.sorted(by: { $0 < $1 }) {
7579
let url = source.appending(path: relPath).standardizedFileURL
7680
guard let info = closure(url) else {
7781
continue
@@ -85,17 +89,23 @@ public final class Archiver: Sendable {
8589
)
8690
try archiver.open(file: destination)
8791

92+
let encoder = JSONEncoder()
93+
encoder.outputFormatting = .sortedKeys
94+
8895
for info in entryInfo {
8996
guard let entry = try Self._createEntry(entryInfo: info) else {
9097
throw Error.failedToCreateEntry
9198
}
92-
try Self._compressFile(item: info.pathOnHost, entry: entry, archiver: archiver)
99+
hasher.update(data: try encoder.encode(info))
100+
try Self._compressFile(item: info.pathOnHost, entry: entry, archiver: archiver, hasher: &hasher)
93101
}
94102
try archiver.finishEncoding()
95103
} catch {
96104
try? fileManager.removeItem(at: destination)
97105
throw error
98106
}
107+
108+
return hasher.finalize()
99109
}
100110

101111
public static func uncompress(source: URL, destination: URL) throws {
@@ -186,7 +196,7 @@ public final class Archiver: Sendable {
186196
}
187197

188198
// MARK: private functions
189-
private static func _compressFile(item: URL, entry: WriteEntry, archiver: ArchiveWriter) throws {
199+
private static func _compressFile(item: URL, entry: WriteEntry, archiver: ArchiveWriter, hasher: inout SHA256) throws {
190200
guard let stream = InputStream(url: item) else {
191201
return
192202
}
@@ -204,6 +214,7 @@ public final class Archiver: Sendable {
204214
break
205215
} else {
206216
let data = Data(bytes: readBuffer, count: byteRead)
217+
hasher.update(data: data)
207218
try data.withUnsafeBytes { pointer in
208219
try writer.writeChunk(data: pointer)
209220
}

0 commit comments

Comments
 (0)