diff --git a/Sources/Services/ContainerAPIService/Client/Parser.swift b/Sources/Services/ContainerAPIService/Client/Parser.swift index 1a6c9547..2e7faa06 100644 --- a/Sources/Services/ContainerAPIService/Client/Parser.swift +++ b/Sources/Services/ContainerAPIService/Client/Parser.swift @@ -316,18 +316,18 @@ public struct Parser { return result } - public static func mounts(_ rawMounts: [String]) throws -> [VolumeOrFilesystem] { + public static func mounts(_ rawMounts: [String], relativeTo basePath: URL? = nil) throws -> [VolumeOrFilesystem] { var mounts: [VolumeOrFilesystem] = [] let rawMounts = rawMounts.dedupe() for mount in rawMounts { - let m = try Parser.mount(mount) + let m = try Parser.mount(mount, relativeTo: basePath) try validateMount(m) mounts.append(m) } return mounts } - public static func mount(_ mount: String) throws -> VolumeOrFilesystem { + public static func mount(_ mount: String, relativeTo basePath: URL? = nil) throws -> VolumeOrFilesystem { let parts = mount.split(separator: ",") if parts.count == 0 { throw ContainerizationError(.invalidArgument, message: "invalid mount format: \(mount)") @@ -407,7 +407,7 @@ public struct Parser { switch type { case "virtiofs", "bind": // For bind mounts, resolve both absolute and relative paths - let url = URL(filePath: val) + let url = basePath?.appending(path: val).standardizedFileURL ?? URL(filePath: val) let absolutePath = url.absoluteURL.path var isDirectory: ObjCBool = false @@ -458,17 +458,17 @@ public struct Parser { )) } - public static func volumes(_ rawVolumes: [String]) throws -> [VolumeOrFilesystem] { + public static func volumes(_ rawVolumes: [String], relativeTo basePath: URL? = nil) throws -> [VolumeOrFilesystem] { var mounts: [VolumeOrFilesystem] = [] for volume in rawVolumes { - let m = try Parser.volume(volume) + let m = try Parser.volume(volume, relativeTo: basePath) try Parser.validateMount(m) mounts.append(m) } return mounts } - public static func volume(_ volume: String) throws -> VolumeOrFilesystem { + public static func volume(_ volume: String, relativeTo basePath: URL? = nil) throws -> VolumeOrFilesystem { var vol = volume vol.trimLeft(char: ":") @@ -508,7 +508,7 @@ public struct Parser { options: options )) } - let url = URL(filePath: src) + let url = basePath?.appending(path: src).standardizedFileURL ?? URL(filePath: src) let absolutePath = url.absoluteURL.path var isDirectory: ObjCBool = false diff --git a/Tests/ContainerAPIClientTests/ParserTest.swift b/Tests/ContainerAPIClientTests/ParserTest.swift index 83f761b9..90fd9b07 100644 --- a/Tests/ContainerAPIClientTests/ParserTest.swift +++ b/Tests/ContainerAPIClientTests/ParserTest.swift @@ -329,20 +329,6 @@ struct ParserTest { @Test func testRelativePaths() throws { - let originalDir = FileManager.default.currentDirectoryPath - defer { - FileManager.default.changeCurrentDirectoryPath(originalDir) - } - - func realpath(_ path: String) -> String { - let resolved = UnsafeMutablePointer.allocate(capacity: Int(PATH_MAX)) - defer { resolved.deallocate() } - guard let result = Darwin.realpath(path, resolved) else { - return path - } - return String(cString: result) - } - // Test bind mount with relative path "." do { let tempDir = FileManager.default.temporaryDirectory.appendingPathComponent("test-bind-\(UUID().uuidString)") @@ -351,12 +337,11 @@ struct ParserTest { try? FileManager.default.removeItem(at: tempDir) } - FileManager.default.changeCurrentDirectoryPath(tempDir.path) - let result = try Parser.mount("type=bind,src=.,dst=/foo") + let result = try Parser.mount("type=bind,src=.,dst=/foo", relativeTo: tempDir) switch result { case .filesystem(let fs): - #expect(fs.source == realpath(tempDir.path)) + #expect(fs.source == tempDir.standardizedFileURL.path) #expect(fs.destination == "/foo") #expect(!fs.isVolume) case .volume: @@ -372,12 +357,11 @@ struct ParserTest { try? FileManager.default.removeItem(at: tempDir) } - FileManager.default.changeCurrentDirectoryPath(tempDir.path) - let result = try Parser.volume("./:/foo") + let result = try Parser.volume("./:/foo", relativeTo: tempDir) switch result { case .filesystem(let fs): - let expectedPath = realpath(tempDir.path) + let expectedPath = tempDir.standardizedFileURL.path // Normalize trailing slashes for comparison #expect(fs.source.trimmingCharacters(in: CharacterSet(charactersIn: "/")) == expectedPath.trimmingCharacters(in: CharacterSet(charactersIn: "/"))) #expect(fs.destination == "/foo") @@ -395,12 +379,11 @@ struct ParserTest { try? FileManager.default.removeItem(at: tempDir) } - FileManager.default.changeCurrentDirectoryPath(tempDir.path) - let result = try Parser.volume("./subdir:/foo") + let result = try Parser.volume("./subdir:/foo", relativeTo: tempDir) switch result { case .filesystem(let fs): - let expectedPath = realpath(nestedDir.path) + let expectedPath = nestedDir.standardizedFileURL.path // Normalize trailing slashes for comparison #expect(fs.source.trimmingCharacters(in: CharacterSet(charactersIn: "/")) == expectedPath.trimmingCharacters(in: CharacterSet(charactersIn: "/"))) #expect(fs.destination == "/foo")