Skip to content

Commit 9da4d07

Browse files
committed
fix(ci): restore check workflow compliance
1 parent 7f4cb2c commit 9da4d07

4 files changed

Lines changed: 153 additions & 148 deletions

File tree

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { FetchHttpClient, HttpClient } from "@effect/platform"
2+
import * as ParseResult from "@effect/schema/ParseResult"
3+
import * as Schema from "@effect/schema/Schema"
4+
import { Effect, Either } from "effect"
5+
6+
import { buildApiBaseUrlCandidates, resolveApiPort, resolveConfiguredApiBaseUrl } from "./controller-reachability.js"
7+
import type { ControllerBootstrapError } from "./host-errors.js"
8+
9+
type HealthProbeResult = {
10+
readonly apiBaseUrl: string
11+
readonly revision: string | null
12+
}
13+
14+
const HealthProbeBodySchema = Schema.Struct({
15+
revision: Schema.optional(Schema.String)
16+
})
17+
18+
const HealthProbeBodyFromStringSchema = Schema.parseJson(HealthProbeBodySchema)
19+
20+
const controllerBootstrapError = (message: string): ControllerBootstrapError => ({
21+
_tag: "ControllerBootstrapError",
22+
message
23+
})
24+
25+
const parseHealthRevision = (text: string): string | null =>
26+
Either.match(ParseResult.decodeUnknownEither(HealthProbeBodyFromStringSchema)(text), {
27+
onLeft: () => null,
28+
onRight: (body) => {
29+
const revision = body.revision
30+
return revision !== undefined && revision.trim().length > 0 ? revision.trim() : null
31+
}
32+
})
33+
34+
const probeHealth = (apiBaseUrl: string): Effect.Effect<HealthProbeResult, ControllerBootstrapError> =>
35+
Effect.gen(function*(_) {
36+
const client = yield* _(HttpClient.HttpClient)
37+
const response = yield* _(client.get(`${apiBaseUrl}/health`, { headers: { accept: "application/json" } }))
38+
const bodyText = yield* _(response.text)
39+
40+
if (response.status >= 200 && response.status < 300) {
41+
return {
42+
apiBaseUrl,
43+
revision: parseHealthRevision(bodyText)
44+
}
45+
}
46+
47+
return yield* _(
48+
Effect.fail(
49+
controllerBootstrapError(
50+
`docker-git controller health returned ${response.status} at ${apiBaseUrl}/health`
51+
)
52+
)
53+
)
54+
}).pipe(
55+
Effect.provide(FetchHttpClient.layer),
56+
Effect.mapError((error): ControllerBootstrapError =>
57+
error._tag === "ControllerBootstrapError"
58+
? error
59+
: {
60+
_tag: "ControllerBootstrapError",
61+
message: `docker-git controller health probe failed at ${apiBaseUrl}/health\nDetails: ${String(error)}`
62+
}
63+
)
64+
)
65+
66+
const findReachableHealthProbe = (
67+
candidateUrls: ReadonlyArray<string>
68+
): Effect.Effect<HealthProbeResult, ControllerBootstrapError> =>
69+
Effect.gen(function*(_) {
70+
if (candidateUrls.length === 0) {
71+
return yield* _(
72+
Effect.fail(controllerBootstrapError("No docker-git controller endpoint candidates were generated."))
73+
)
74+
}
75+
76+
for (const candidateUrl of candidateUrls) {
77+
const healthy = yield* _(probeHealth(candidateUrl).pipe(Effect.either))
78+
if (Either.isRight(healthy)) {
79+
return healthy.right
80+
}
81+
}
82+
83+
return yield* _(Effect.fail(controllerBootstrapError("No docker-git controller endpoint responded to /health.")))
84+
})
85+
86+
const findReachableHealthProbeOrNull = (
87+
candidateUrls: ReadonlyArray<string>
88+
): Effect.Effect<HealthProbeResult | null> =>
89+
findReachableHealthProbe(candidateUrls).pipe(
90+
Effect.match({
91+
onFailure: () => null,
92+
onSuccess: (probe) => probe
93+
})
94+
)
95+
96+
export const findReachableApiBaseUrl = (
97+
candidateUrls: ReadonlyArray<string>
98+
): Effect.Effect<string, ControllerBootstrapError> =>
99+
findReachableHealthProbe(candidateUrls).pipe(Effect.map(({ apiBaseUrl }) => apiBaseUrl))
100+
101+
export const findReachableDirectHealthProbe = (options: {
102+
readonly explicitApiBaseUrl: string | undefined
103+
readonly cachedApiBaseUrl: string | undefined
104+
}): Effect.Effect<HealthProbeResult | null> =>
105+
findReachableHealthProbeOrNull(
106+
buildApiBaseUrlCandidates({
107+
explicitApiBaseUrl: options.explicitApiBaseUrl,
108+
cachedApiBaseUrl: options.cachedApiBaseUrl,
109+
defaultApiBaseUrl: resolveConfiguredApiBaseUrl(),
110+
currentContainerNetworks: {},
111+
controllerNetworks: {},
112+
port: resolveApiPort()
113+
})
114+
)

packages/app/src/docker-git/controller.ts

Lines changed: 25 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { FetchHttpClient, HttpClient } from "@effect/platform"
21
import { Duration, Effect, pipe, Schedule } from "effect"
32

43
import {
@@ -13,6 +12,7 @@ import {
1312
resolveCurrentContainerNetworks,
1413
runCompose
1514
} from "./controller-docker.js"
15+
import { findReachableApiBaseUrl, findReachableDirectHealthProbe } from "./controller-health.js"
1616
import {
1717
buildApiBaseUrlCandidates,
1818
type DockerNetworkIps,
@@ -31,11 +31,6 @@ export { buildApiBaseUrlCandidates, isRemoteDockerHost } from "./controller-reac
3131

3232
let selectedApiBaseUrl: string | undefined
3333

34-
type HealthProbeResult = {
35-
readonly apiBaseUrl: string
36-
readonly revision: string | null
37-
}
38-
3934
const controllerBootstrapError = (message: string): ControllerBootstrapError => ({
4035
_tag: "ControllerBootstrapError",
4136
message
@@ -48,84 +43,6 @@ const rememberSelectedApiBaseUrl = (value: string): void => {
4843
export const resolveApiBaseUrl = (): string =>
4944
resolveExplicitApiBaseUrl() ?? selectedApiBaseUrl ?? resolveConfiguredApiBaseUrl()
5045

51-
const parseHealthRevision = (text: string): string | null => {
52-
try {
53-
const parsed: unknown = JSON.parse(text)
54-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
55-
return null
56-
}
57-
const revision = Reflect.get(parsed, "revision")
58-
return typeof revision === "string" && revision.trim().length > 0 ? revision.trim() : null
59-
} catch {
60-
return null
61-
}
62-
}
63-
64-
const probeHealth = (apiBaseUrl: string): Effect.Effect<HealthProbeResult, ControllerBootstrapError> =>
65-
Effect.gen(function*(_) {
66-
const client = yield* _(HttpClient.HttpClient)
67-
const response = yield* _(client.get(`${apiBaseUrl}/health`, { headers: { accept: "application/json" } }))
68-
const bodyText = yield* _(response.text)
69-
70-
if (response.status >= 200 && response.status < 300) {
71-
return {
72-
apiBaseUrl,
73-
revision: parseHealthRevision(bodyText)
74-
}
75-
}
76-
77-
return yield* _(
78-
Effect.fail(
79-
controllerBootstrapError(
80-
`docker-git controller health returned ${response.status} at ${apiBaseUrl}/health`
81-
)
82-
)
83-
)
84-
}).pipe(
85-
Effect.provide(FetchHttpClient.layer),
86-
Effect.mapError((error): ControllerBootstrapError =>
87-
error._tag === "ControllerBootstrapError"
88-
? error
89-
: {
90-
_tag: "ControllerBootstrapError",
91-
message: `docker-git controller health probe failed at ${apiBaseUrl}/health\nDetails: ${String(error)}`
92-
}
93-
)
94-
)
95-
96-
const findReachableApiBaseUrl = (
97-
candidateUrls: ReadonlyArray<string>
98-
): Effect.Effect<string, ControllerBootstrapError> =>
99-
findReachableHealthProbe(candidateUrls).pipe(Effect.map(({ apiBaseUrl }) => apiBaseUrl))
100-
101-
const findReachableHealthProbe = (
102-
candidateUrls: ReadonlyArray<string>
103-
): Effect.Effect<HealthProbeResult, ControllerBootstrapError> =>
104-
Effect.gen(function*(_) {
105-
if (candidateUrls.length === 0) {
106-
return yield* _(
107-
Effect.fail(controllerBootstrapError("No docker-git controller endpoint candidates were generated."))
108-
)
109-
}
110-
111-
for (const candidateUrl of candidateUrls) {
112-
const healthy = yield* _(
113-
probeHealth(candidateUrl).pipe(
114-
Effect.match({
115-
onFailure: () => undefined,
116-
onSuccess: (result) => result
117-
})
118-
)
119-
)
120-
121-
if (healthy !== undefined) {
122-
return healthy
123-
}
124-
}
125-
126-
return yield* _(Effect.fail(controllerBootstrapError("No docker-git controller endpoint responded to /health.")))
127-
})
128-
12946
const collectReachabilityDiagnostics = (
13047
candidateUrls: ReadonlyArray<string>,
13148
currentContainerNetworks: DockerNetworkIps,
@@ -193,40 +110,16 @@ const failIfRemoteDockerWithoutApiUrl = (): Effect.Effect<void, ControllerBootst
193110
)
194111
}
195112

196-
const findReachableApiBaseUrlOption = (
113+
const findReachableApiBaseUrlOrNull = (
197114
candidateUrls: ReadonlyArray<string>
198-
): Effect.Effect<string | undefined, ControllerBootstrapError> =>
115+
): Effect.Effect<string | null> =>
199116
findReachableApiBaseUrl(candidateUrls).pipe(
200117
Effect.match({
201-
onFailure: (): string | undefined => undefined,
118+
onFailure: () => null,
202119
onSuccess: (apiBaseUrl) => apiBaseUrl
203120
})
204121
)
205122

206-
const findReachableHealthProbeOption = (
207-
candidateUrls: ReadonlyArray<string>
208-
): Effect.Effect<HealthProbeResult | undefined, ControllerBootstrapError> =>
209-
findReachableHealthProbe(candidateUrls).pipe(
210-
Effect.match({
211-
onFailure: (): HealthProbeResult | undefined => undefined,
212-
onSuccess: (probe) => probe
213-
})
214-
)
215-
216-
const findReachableDirectHealthProbe = (
217-
explicitApiBaseUrl: string | undefined
218-
): Effect.Effect<HealthProbeResult | undefined, ControllerBootstrapError> =>
219-
findReachableHealthProbeOption(
220-
buildApiBaseUrlCandidates({
221-
explicitApiBaseUrl,
222-
cachedApiBaseUrl: selectedApiBaseUrl,
223-
defaultApiBaseUrl: resolveConfiguredApiBaseUrl(),
224-
currentContainerNetworks: {},
225-
controllerNetworks: {},
226-
port: resolveApiPort()
227-
})
228-
)
229-
230123
const failIfExplicitApiUrlIsUnreachable = (
231124
explicitApiBaseUrl: string | undefined
232125
): Effect.Effect<void, ControllerBootstrapError> =>
@@ -294,15 +187,15 @@ const buildBootstrapCandidateUrls = (
294187
const reuseReachableControllerIfPossible = (
295188
context: ControllerBootstrapContext
296189
): Effect.Effect<boolean, ControllerBootstrapError> =>
297-
findReachableApiBaseUrlOption(
190+
findReachableApiBaseUrlOrNull(
298191
buildBootstrapCandidateUrls(
299192
context.explicitApiBaseUrl,
300193
context.currentContainerNetworks,
301194
context.initialControllerNetworks
302195
)
303196
).pipe(
304197
Effect.map((reachableApiBaseUrl) => {
305-
if (reachableApiBaseUrl === undefined || context.forceRecreateController) {
198+
if (reachableApiBaseUrl === null || context.forceRecreateController) {
306199
return false
307200
}
308201
rememberSelectedApiBaseUrl(reachableApiBaseUrl)
@@ -362,22 +255,32 @@ export const ensureControllerReady = (): Effect.Effect<void, ControllerBootstrap
362255
yield* _(failIfRemoteDockerWithoutApiUrl())
363256
const explicitApiBaseUrl = resolveExplicitApiBaseUrl()
364257
const localControllerRevision = yield* _(prepareLocalControllerRevision())
365-
if (explicitApiBaseUrl !== undefined) {
366-
const reachableBeforeDocker = yield* _(findReachableDirectHealthProbe(explicitApiBaseUrl))
367-
if (reachableBeforeDocker !== undefined) {
258+
if (explicitApiBaseUrl === undefined) {
259+
const reachableBeforeDocker = yield* _(
260+
findReachableDirectHealthProbe({
261+
explicitApiBaseUrl,
262+
cachedApiBaseUrl: selectedApiBaseUrl
263+
})
264+
)
265+
if (
266+
reachableBeforeDocker !== null &&
267+
reachableBeforeDocker.revision === localControllerRevision
268+
) {
368269
rememberSelectedApiBaseUrl(reachableBeforeDocker.apiBaseUrl)
369270
return
370271
}
371-
yield* _(failIfExplicitApiUrlIsUnreachable(explicitApiBaseUrl))
372272
} else {
373-
const reachableBeforeDocker = yield* _(findReachableDirectHealthProbe(undefined))
374-
if (
375-
reachableBeforeDocker !== undefined &&
376-
reachableBeforeDocker.revision === localControllerRevision
377-
) {
273+
const reachableBeforeDocker = yield* _(
274+
findReachableDirectHealthProbe({
275+
explicitApiBaseUrl,
276+
cachedApiBaseUrl: selectedApiBaseUrl
277+
})
278+
)
279+
if (reachableBeforeDocker !== null) {
378280
rememberSelectedApiBaseUrl(reachableBeforeDocker.apiBaseUrl)
379281
return
380282
}
283+
yield* _(failIfExplicitApiUrlIsUnreachable(explicitApiBaseUrl))
381284
}
382285

383286
const bootstrapContext = yield* _(loadControllerBootstrapContext())

packages/lib/tests/usecases/auth-sync.test.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import * as nodeFs from "node:fs"
21
import * as FileSystem from "@effect/platform/FileSystem"
32
import * as Path from "@effect/platform/Path"
43
import { NodeContext } from "@effect/platform-node"
@@ -197,11 +196,7 @@ describe("syncGithubAuthKeys", () => {
197196
yield* _(fs.makeDirectory(sourceCodexDir, { recursive: true }))
198197
yield* _(fs.makeDirectory(targetCodexDir, { recursive: true }))
199198
yield* _(fs.writeFileString(path.join(sourceCodexDir, "auth.json"), authText))
200-
yield* _(
201-
Effect.sync(() => {
202-
nodeFs.symlinkSync(missingSharedAuthPath, targetAuthPath)
203-
})
204-
)
199+
yield* _(fs.symlink(missingSharedAuthPath, targetAuthPath))
205200

206201
yield* _(
207202
syncAuthArtifacts({
@@ -223,11 +218,8 @@ describe("syncGithubAuthKeys", () => {
223218
)
224219

225220
expect(yield* _(fs.readFileString(targetAuthPath))).toBe(authText)
226-
yield* _(
227-
Effect.sync(() => {
228-
expect(nodeFs.lstatSync(targetAuthPath).isSymbolicLink()).toBe(false)
229-
})
230-
)
221+
const targetInfo = yield* _(fs.stat(targetAuthPath))
222+
expect(targetInfo.type).toBe("File")
231223
})
232224
).pipe(Effect.provide(NodeContext.layer)))
233225

packages/lib/tests/usecases/shared-volume-seed.test.ts

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import * as fs from "node:fs"
2-
31
import * as FileSystem from "@effect/platform/FileSystem"
42
import * as Path from "@effect/platform/Path"
53
import { NodeContext } from "@effect/platform-node"
@@ -64,21 +62,19 @@ describe("stageBootstrapSnapshot", () => {
6462
yield* _(fileSystem.writeFileString(path.join(sharedCodexDir, "auth.json"), "{\"shared\":true}\n"))
6563
yield* _(fileSystem.writeFileString(path.join(sharedCodexLabelDir, "auth.json"), "{\"shared\":\"team-a\"}\n"))
6664

65+
const brokenShimDir = path.join(sharedCodexDir, "tmp", "arg0", "codex-arg0broken")
66+
yield* _(fileSystem.makeDirectory(brokenShimDir, { recursive: true }))
6767
yield* _(
68-
Effect.sync(() => {
69-
const brokenShimDir = path.join(sharedCodexDir, "tmp", "arg0", "codex-arg0broken")
70-
fs.mkdirSync(brokenShimDir, { recursive: true })
71-
fs.symlinkSync(
72-
"/usr/local/bun/install/global/node_modules/@openai/codex-linux-x64/vendor/x86_64-unknown-linux-musl/codex/codex",
73-
path.join(brokenShimDir, "apply_patch")
74-
)
75-
fs.writeFileSync(path.join(brokenShimDir, ".lock"), "")
76-
fs.mkdirSync(path.join(sharedCodexDir, "log"), { recursive: true })
77-
fs.writeFileSync(path.join(sharedCodexDir, "log", "codex-login.log"), "transient log\n")
78-
fs.mkdirSync(path.join(sharedCodexDir, ".image"), { recursive: true })
79-
fs.writeFileSync(path.join(sharedCodexDir, ".image", "Dockerfile"), "FROM scratch\n")
80-
})
68+
fileSystem.symlink(
69+
"/usr/local/bun/install/global/node_modules/@openai/codex-linux-x64/vendor/x86_64-unknown-linux-musl/codex/codex",
70+
path.join(brokenShimDir, "apply_patch")
71+
)
8172
)
73+
yield* _(fileSystem.writeFileString(path.join(brokenShimDir, ".lock"), ""))
74+
yield* _(fileSystem.makeDirectory(path.join(sharedCodexDir, "log"), { recursive: true }))
75+
yield* _(fileSystem.writeFileString(path.join(sharedCodexDir, "log", "codex-login.log"), "transient log\n"))
76+
yield* _(fileSystem.makeDirectory(path.join(sharedCodexDir, ".image"), { recursive: true }))
77+
yield* _(fileSystem.writeFileString(path.join(sharedCodexDir, ".image", "Dockerfile"), "FROM scratch\n"))
8278

8379
yield* _(
8480
stageBootstrapSnapshot(stagingDir, projectDir, {

0 commit comments

Comments
 (0)