Skip to content

Commit b2726ee

Browse files
committed
tar: Use OutputStream
With OutputStream, the image can be saved to a memory buffer (this is the default for Archive) or directly to a file on disk.
1 parent 9417b63 commit b2726ee

File tree

4 files changed

+59
-94
lines changed

4 files changed

+59
-94
lines changed

Sources/Tar/tar.swift

Lines changed: 24 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15+
import Foundation
16+
1517
// This file defines a basic tar writer which produces POSIX tar files.
1618
// This avoids the need to depend on a system-provided tar binary.
1719
//
@@ -342,44 +344,44 @@ public func tar(_ bytes: [UInt8], filename: String = "app") throws -> [UInt8] {
342344
}
343345

344346
/// Represents a tar archive
345-
public struct Archive {
347+
public struct Archive: ~Copyable {
346348
/// The files, directories and other members of the archive
347-
var members: [ArchiveMember]
349+
var output: OutputStream
348350

349351
/// Creates an empty Archive
350352
public init() {
351-
members = []
353+
output = OutputStream.toMemory()
354+
output.open()
355+
output.schedule(in: .current, forMode: .default) // is this needed?
352356
}
353357

354-
/// Appends a member to the archive
355-
/// Parameters:
356-
/// - member: The member to append
357-
public mutating func append(_ member: ArchiveMember) {
358-
self.members.append(member)
358+
deinit {
359+
output.close()
359360
}
360361

361-
/// Returns a new archive made by appending a member to the receiver
362+
/// Appends a member to the archive
362363
/// Parameters:
363364
/// - member: The member to append
364-
/// Returns: A new archive made by appending `member` to the receiver.
365-
public func appending(_ member: ArchiveMember) -> Self {
366-
var ret = self
367-
ret.members += [member]
368-
return ret
365+
public func append(_ member: ArchiveMember) {
366+
print("append: \(member.bytes.count)")
367+
let written = output.write(member.bytes, maxLength: member.bytes.count)
368+
print("append wrote: \(written)")
369+
if written != member.bytes.count {
370+
print("count: \(member.bytes.count), written: \(written)")
371+
fatalError()
372+
}
369373
}
370374

371375
/// The serialized byte representation of the archive, including padding and end-of-archive marker.
372376
public var bytes: [UInt8] {
373-
var ret: [UInt8] = []
374-
for member in members {
375-
ret.append(contentsOf: member.bytes)
377+
guard let data = output.property(forKey: .dataWrittenToMemoryStreamKey) as? Data else {
378+
fatalError("retrieving memory stream contents")
376379
}
380+
print("bytes returned: \(data.count)")
377381

378382
// Append the end of file marker
379383
let marker = [UInt8](repeating: 0, count: 2 * blockSize)
380-
ret.append(contentsOf: marker)
381-
382-
return ret
384+
return [UInt8](data) + marker
383385
}
384386
}
385387

@@ -416,32 +418,15 @@ extension Archive {
416418
/// - name: File name
417419
/// - prefix: Path prefix
418420
/// - data: File contents
419-
public mutating func appendFile(name: String, prefix: String = "", data: [UInt8]) throws {
421+
public func appendFile(name: String, prefix: String = "", data: [UInt8]) throws {
420422
try append(.init(header: .init(name: name, size: data.count, prefix: prefix), data: data))
421423
}
422424

423-
/// Adds a new file member at the end of the archive
424-
/// parameters:
425-
/// - name: File name
426-
/// - prefix: Path prefix
427-
/// - data: File contents
428-
public func appendingFile(name: String, prefix: String = "", data: [UInt8]) throws -> Self {
429-
try appending(.init(header: .init(name: name, size: data.count, prefix: prefix), data: data))
430-
}
431-
432425
/// Adds a new directory member at the end of the archive
433426
/// parameters:
434427
/// - name: Directory name
435428
/// - prefix: Path prefix
436-
public mutating func appendDirectory(name: String, prefix: String = "") throws {
429+
public func appendDirectory(name: String, prefix: String = "") throws {
437430
try append(.init(header: .init(name: name, typeflag: .DIRTYPE, prefix: prefix)))
438431
}
439-
440-
/// Adds a new directory member at the end of the archive
441-
/// parameters:
442-
/// - name: Directory name
443-
/// - prefix: Path prefix
444-
public func appendingDirectory(name: String, prefix: String = "") throws -> Self {
445-
try self.appending(.init(header: .init(name: name, typeflag: .DIRTYPE, prefix: prefix)))
446-
}
447432
}

Sources/containertool/Extensions/Archive+appending.swift

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,30 +30,28 @@ extension Archive {
3030
/// Parameters:
3131
/// - root: The path to the file or directory to add.
3232
/// Returns: A new archive made by appending `root` to the receiver.
33-
public func appendingRecursively(atPath root: String) throws -> Self {
33+
public func appendRecursively(atPath root: String) throws {
3434
let url = URL(fileURLWithPath: root)
3535
if url.isDirectory {
36-
return try self.appendingDirectoryTree(at: url)
36+
try self.appendDirectoryTree(at: url)
3737
} else {
38-
return try self.appendingFile(at: url)
38+
try self.appendFile(at: url)
3939
}
4040
}
4141

4242
/// Append a single file to the archive.
4343
/// Parameters:
4444
/// - path: The path to the file to add.
4545
/// Returns: A new archive made by appending `path` to the receiver.
46-
func appendingFile(at path: URL) throws -> Self {
47-
try self.appendingFile(name: path.lastPathComponent, data: try [UInt8](Data(contentsOf: path)))
46+
func appendFile(at path: URL) throws {
47+
try self.appendFile(name: path.lastPathComponent, data: try [UInt8](Data(contentsOf: path)))
4848
}
4949

5050
/// Recursively append a single directory tree to the archive.
5151
/// Parameters:
5252
/// - root: The path to the directory to add.
5353
/// Returns: A new archive made by appending `root` to the receiver.
54-
func appendingDirectoryTree(at root: URL) throws -> Self {
55-
var ret = self
56-
54+
func appendDirectoryTree(at root: URL) throws {
5755
guard let enumerator = FileManager.default.enumerator(atPath: root.path) else {
5856
throw ("Unable to read \(root.path)")
5957
}
@@ -69,16 +67,14 @@ extension Archive {
6967
switch filetype {
7068
case .typeRegular:
7169
let resource = try [UInt8](Data(contentsOf: root.appending(path: subpath)))
72-
try ret.appendFile(name: subpath, prefix: root.lastPathComponent, data: resource)
70+
try self.appendFile(name: subpath, prefix: root.lastPathComponent, data: resource)
7371

7472
case .typeDirectory:
75-
try ret.appendDirectory(name: subpath, prefix: root.lastPathComponent)
73+
try self.appendDirectory(name: subpath, prefix: root.lastPathComponent)
7674

7775
default:
7876
throw "Resource file \(subpath) of type \(filetype) is not supported"
7977
}
8078
}
81-
82-
return ret
8379
}
8480
}

Sources/containertool/Extensions/RegistryClient+publish.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,11 @@ func publishContainerImage<Source: ImageSource, Destination: ImageDestination>(
4949

5050
var resourceLayers: [(descriptor: ContentDescriptor, diffID: ImageReference.Digest)] = []
5151
for resourceDir in resources {
52-
let resourceTardiff = try Archive().appendingRecursively(atPath: resourceDir).bytes
52+
let resourceTardiff = Archive()
53+
try resourceTardiff.appendRecursively(atPath: resourceDir)
5354
let resourceLayer = try await destination.uploadLayer(
5455
repository: destinationImage.repository,
55-
contents: resourceTardiff
56+
contents: resourceTardiff.bytes
5657
)
5758

5859
if verbose {
@@ -64,9 +65,12 @@ func publishContainerImage<Source: ImageSource, Destination: ImageDestination>(
6465

6566
// MARK: Upload the application layer
6667

68+
let applicationTardiff = Archive()
69+
try applicationTardiff.appendFile(at: executableURL)
70+
6771
let applicationLayer = try await destination.uploadLayer(
6872
repository: destinationImage.repository,
69-
contents: try Archive().appendingFile(at: executableURL).bytes
73+
contents: applicationTardiff.bytes
7074
)
7175
if verbose {
7276
log("application layer: \(applicationLayer.descriptor.digest) (\(applicationLayer.descriptor.size) bytes)")

Tests/TarTests/TarUnitTests.swift

Lines changed: 20 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -339,12 +339,13 @@ let trailer = [UInt8](repeating: 0, count: trailerSize)
339339
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
340340
]
341341

342-
@Test func testAppendingEmptyFile() async throws {
343-
let archive = try Archive().appendingFile(name: "emptyfile", data: []).bytes
342+
@Test func testAppendEmptyFile() async throws {
343+
let archive = Archive()
344+
try archive.appendFile(name: "emptyfile", data: [])
344345

345346
// Expecting: member header, no file content, 2-block end of archive marker
346-
#expect(archive.count == headerSize + trailerSize)
347-
#expect(archive == emptyFile + trailer)
347+
#expect(archive.bytes.count == headerSize + trailerSize)
348+
#expect(archive.bytes == emptyFile + trailer)
348349
}
349350

350351
let helloFile: [UInt8] =
@@ -456,21 +457,12 @@ let trailer = [UInt8](repeating: 0, count: trailerSize)
456457
]
457458

458459
@Test func testAppendFile() async throws {
459-
var archive = Archive()
460+
let archive = Archive()
460461
try archive.appendFile(name: "hellofile", data: [UInt8]("hello".utf8))
461-
let output = archive.bytes
462462

463463
// Expecting: member header, file content, 2-block end of archive marker
464-
#expect(output.count == headerSize + blockSize + trailerSize)
465-
#expect(output == helloFile + trailer)
466-
}
467-
468-
@Test func testAppendingFile() async throws {
469-
let archive = try Archive().appendingFile(name: "hellofile", data: [UInt8]("hello".utf8)).bytes
470-
471-
// Expecting: member header, file content, 2-block end of archive marker
472-
#expect(archive.count == headerSize + blockSize + trailerSize)
473-
#expect(archive == helloFile + trailer)
464+
#expect(archive.bytes.count == headerSize + blockSize + trailerSize)
465+
#expect(archive.bytes == helloFile + trailer)
474466
}
475467

476468
let directoryWithPrefix: [UInt8] = [
@@ -547,47 +539,35 @@ let trailer = [UInt8](repeating: 0, count: trailerSize)
547539
]
548540

549541
@Test func testAppendDirectory() async throws {
550-
var archive = Archive()
542+
let archive = Archive()
551543
try archive.appendDirectory(name: "directory", prefix: "prefix")
552-
let output = archive.bytes
553-
554-
// Expecting: member header, no content, 2-block end of archive marker
555-
#expect(output.count == headerSize + trailerSize)
556-
#expect(output == directoryWithPrefix + trailer)
557-
}
558-
559-
@Test func testAppendingDirectory() async throws {
560-
let archive = try Archive().appendingDirectory(name: "directory", prefix: "prefix").bytes
561544

562545
// Expecting: member header, no content, 2-block end of archive marker
563-
#expect(archive.count == headerSize + trailerSize)
564-
#expect(archive == directoryWithPrefix + trailer)
546+
#expect(archive.bytes.count == headerSize + trailerSize)
547+
#expect(archive.bytes == directoryWithPrefix + trailer)
565548
}
566549

567550
@Test func testAppendFilesAndDirectories() async throws {
568-
var archive = Archive()
551+
let archive = Archive()
569552
try archive.appendFile(name: "hellofile", data: [UInt8]("hello".utf8))
570553
try archive.appendFile(name: "emptyfile", data: [UInt8]())
571554
try archive.appendDirectory(name: "directory", prefix: "prefix")
572555

573-
let output = archive.bytes
574-
575556
// Expecting: file member header, file content, file member header, no file content,
576557
// directory member header, 2-block end of archive marker
577-
#expect(output.count == headerSize + blockSize + headerSize + headerSize + trailerSize)
578-
#expect(output == helloFile + emptyFile + directoryWithPrefix + trailer)
558+
#expect(archive.bytes.count == headerSize + blockSize + headerSize + headerSize + trailerSize)
559+
#expect(archive.bytes == helloFile + emptyFile + directoryWithPrefix + trailer)
579560
}
580561

581562
@Test func testAppendingFilesAndDirectories() async throws {
582-
let archive = try Archive()
583-
.appendingFile(name: "hellofile", data: [UInt8]("hello".utf8))
584-
.appendingFile(name: "emptyfile", data: [UInt8]())
585-
.appendingDirectory(name: "directory", prefix: "prefix")
586-
.bytes
563+
let archive = Archive()
564+
try archive.appendFile(name: "hellofile", data: [UInt8]("hello".utf8))
565+
try archive.appendFile(name: "emptyfile", data: [UInt8]())
566+
try archive.appendDirectory(name: "directory", prefix: "prefix")
587567

588568
// Expecting: file member header, file content, file member header, no file content,
589569
// directory member header, 2-block end of archive marker
590-
#expect(archive.count == headerSize + blockSize + headerSize + headerSize + trailerSize)
591-
#expect(archive == helloFile + emptyFile + directoryWithPrefix + trailer)
570+
#expect(archive.bytes.count == headerSize + blockSize + headerSize + headerSize + trailerSize)
571+
#expect(archive.bytes == helloFile + emptyFile + directoryWithPrefix + trailer)
592572
}
593573
}

0 commit comments

Comments
 (0)