Skip to content

Commit a25ea80

Browse files
authored
refactor(orchestrator): log git install failures during startup (#107)
1 parent 8674c99 commit a25ea80

File tree

2 files changed

+53
-2
lines changed

2 files changed

+53
-2
lines changed

Sources/Zero/Services/ContainerOrchestrator.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,18 @@ class ContainerOrchestrator {
2525

2626
// 3-1. Git 설치 (Alpine 기반 이미지에 git이 없을 경우)
2727
if image.contains("alpine") {
28-
_ = try? dockerService.executeShell(container: containerName, script: "apk add --no-cache git")
28+
do {
29+
_ = try dockerService.executeShell(container: containerName, script: "apk add --no-cache git")
30+
} catch {
31+
AppLogStore.shared.append("ContainerOrchestrator git install failed (apk): \(error.localizedDescription)")
32+
}
2933
} else if image.contains("openjdk") || image.contains("temurin") || image.contains("corretto") {
3034
// JDK 이미지들은 보통 Debian/Ubuntu 기반이므로 apt 사용
31-
_ = try? dockerService.executeShell(container: containerName, script: "apt-get update && apt-get install -y git")
35+
do {
36+
_ = try dockerService.executeShell(container: containerName, script: "apt-get update && apt-get install -y git")
37+
} catch {
38+
AppLogStore.shared.append("ContainerOrchestrator git install failed (apt): \(error.localizedDescription)")
39+
}
3240
}
3341

3442
// 3. Git Clone (토큰 주입)

Tests/ZeroTests/ContainerOrchestratorTests.swift

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ class MockDockerService: DockerServiceProtocol {
77
var lastContainerName: String?
88
var lastImageName: String?
99
var executedScripts: [String] = []
10+
var shellErrorsByCommandSubstring: [String: Error] = [:]
1011

1112
// 호환성을 위한 계산 속성
1213
var executedScript: String? {
@@ -20,11 +21,17 @@ class MockDockerService: DockerServiceProtocol {
2021
}
2122

2223
func executeShell(container: String, script: String) throws -> String {
24+
if let match = shellErrorsByCommandSubstring.first(where: { script.contains($0.key) }) {
25+
throw match.value
26+
}
2327
executedScripts.append(script)
2428
return "mock shell output"
2529
}
2630

2731
func executeShellStreaming(container: String, script: String, onOutput: @escaping (String) -> Void) throws -> String {
32+
if let match = shellErrorsByCommandSubstring.first(where: { script.contains($0.key) }) {
33+
throw match.value
34+
}
2835
executedScripts.append(script)
2936
let output = "mock shell output"
3037
onOutput(output)
@@ -57,9 +64,11 @@ final class ContainerOrchestratorTests: XCTestCase {
5764
override func setUp() {
5865
super.setUp()
5966
testStoreURL = FileManager.default.temporaryDirectory.appendingPathComponent("orchestrator_test.json")
67+
AppLogStore.shared.clear()
6068
}
6169

6270
override func tearDown() {
71+
AppLogStore.shared.clear()
6372
try? FileManager.default.removeItem(at: testStoreURL)
6473
super.tearDown()
6574
}
@@ -100,4 +109,38 @@ final class ContainerOrchestratorTests: XCTestCase {
100109
let sessions = try sessionManager.loadSessions()
101110
XCTAssertEqual(sessions.count, 1)
102111
}
112+
113+
func testStartSessionLogsGitInstallFailureAndContinues() async throws {
114+
// Given
115+
let mockDocker = MockDockerService()
116+
mockDocker.shellErrorsByCommandSubstring["apk add --no-cache git"] = NSError(
117+
domain: "docker",
118+
code: 1,
119+
userInfo: [NSLocalizedDescriptionKey: "apk failed: temporary network error"]
120+
)
121+
let sessionManager = SessionManager(storeURL: testStoreURL)
122+
let orchestrator = ContainerOrchestrator(
123+
dockerService: mockDocker,
124+
sessionManager: sessionManager
125+
)
126+
127+
let repo = Repository(
128+
id: 1,
129+
name: "test-repo",
130+
fullName: "user/test-repo",
131+
isPrivate: false,
132+
htmlURL: URL(string: "https://github.com/user/test-repo")!,
133+
cloneURL: URL(string: "https://github.com/user/test-repo.git")!
134+
)
135+
136+
// When
137+
let session = try await orchestrator.startSession(repo: repo, token: "ghp_test_token")
138+
139+
// Then
140+
XCTAssertEqual(session.repoURL, repo.cloneURL)
141+
let logs = AppLogStore.shared.recentEntries()
142+
XCTAssertTrue(logs.contains { entry in
143+
entry.contains("ContainerOrchestrator git install failed") && entry.contains("apk failed")
144+
})
145+
}
103146
}

0 commit comments

Comments
 (0)