Enumerate using relative paths to avoid mismatch with symlink resolution of special paths like /tmp (#613)

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

## Motivation and Context
Fixes https://github.com/apple/container/issues/588. This PR changes the
archiver compression file enumeration to use the
[enumerator(atPath:)](https://developer.apple.com/documentation/foundation/filemanager/enumerator(atpath:))
version. This version returns relative paths instead of full file paths
from the filesystem. /tmp is symlinked to /private/tmp and some swift
packages will handle that path differently. While a call to Foundation's
`URL.resolvingSymlinksInPath()` will return "/tmp", a call to
`FileManager.enumerator(at:)` will return "/private/tmp". This
difference causes a container image build to fail when the user is using
a path under /tmp or other special case paths as the context directory.

## Testing
- [x] Tested locally
- [x] Added/updated tests
- [ ] Added/updated docs

Signed-off-by: Kathryn Baldauf <k_baldauf@apple.com>
This commit is contained in:
Kathryn Baldauf
2025-09-16 10:52:08 -07:00
committed by GitHub
parent 79cc363e78
commit 386fd87b5d
2 changed files with 24 additions and 5 deletions
+3 -5
View File
@@ -60,10 +60,7 @@ public final class Archiver: Sendable {
try fileManager.createDirectory(at: directory, withIntermediateDirectories: true)
guard
let enumerator = FileManager.default.enumerator(
at: source,
includingPropertiesForKeys: [.isDirectoryKey, .isRegularFileKey, .isSymbolicLinkKey]
)
let enumerator = FileManager.default.enumerator(atPath: source.path)
else {
throw Error.fileDoesNotExist(source)
}
@@ -74,7 +71,8 @@ public final class Archiver: Sendable {
entryInfo.append(info)
}
} else {
while let url = enumerator.nextObject() as? URL {
while let relPath = enumerator.nextObject() as? String {
let url = source.appending(path: relPath).standardizedFileURL
guard let info = closure(url) else {
continue
}
@@ -94,6 +94,27 @@ extension TestCLIBuildBase {
#expect(try self.inspectImage(newImageName) == newImageName, "expected to have successfully built \(newImageName)")
}
@Test func testBuildAddFromSpecialDirs() throws {
let tempDir = URL(filePath: "/tmp/container/.clitests" + testUUID)
try! FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)
defer {
try! FileManager.default.removeItem(at: tempDir)
}
let dockerfile: String =
"""
FROM scratch
ADD emptyFile /
"""
let context: [FileSystemEntry] = [.file("emptyFile", content: .zeroFilled(size: 1))]
try createContext(tempDir: tempDir, dockerfile: dockerfile, context: context)
let imageName = "registry.local/scratch-add-special-dir:\(UUID().uuidString)"
try self.build(tag: imageName, tempDir: tempDir)
#expect(try self.inspectImage(imageName) == imageName, "expected to have successfully built \(imageName)")
}
@Test func testBuildScratchAdd() throws {
let tempDir: URL = try createTempDir()
let dockerfile: String =