Forji/integration/forgejo-seed/Sources/DockerExec.swift

47 lines
1.6 KiB
Swift
Raw Normal View History

import Foundation
enum DockerExec {
static func run(
composeFile: String, service: String,
command: [String],
timeout: TimeInterval = 60,
) throws {
let process = Process()
process.executableURL = URL(fileURLWithPath: "/usr/bin/env")
process.arguments = [
"docker", "compose", "-f", composeFile,
"exec", "-T", "-u", "git", service,
] + command
let stderrPipe = Pipe()
process.standardError = stderrPipe
process.standardOutput = FileHandle.nullDevice
let didExit = DispatchSemaphore(value: 0)
process.terminationHandler = { _ in didExit.signal() }
try process.run()
// Enforce a timeout so a hung `docker compose exec` (a known Docker-on-macOS flake)
// fails fast and can be retried, instead of blocking the whole seed indefinitely.
if didExit.wait(timeout: .now() + timeout) == .timedOut {
process.terminate()
_ = didExit.wait(timeout: .now() + 5)
throw SeedError.dockerExecTimedOut(
command: command.joined(separator: " "),
seconds: timeout,
)
}
if process.terminationStatus != 0 {
let stderrData = stderrPipe.fileHandleForReading.readDataToEndOfFile()
let stderr = String(data: stderrData, encoding: .utf8) ?? ""
throw SeedError.dockerExecFailed(
command: command.joined(separator: " "),
exitCode: process.terminationStatus,
stderr: stderr,
)
}
}
}