1- import { FetchHttpClient , HttpClient } from "@effect/platform"
21import { Duration , Effect , pipe , Schedule } from "effect"
32
43import {
@@ -13,6 +12,7 @@ import {
1312 resolveCurrentContainerNetworks ,
1413 runCompose
1514} from "./controller-docker.js"
15+ import { findReachableApiBaseUrl , findReachableDirectHealthProbe } from "./controller-health.js"
1616import {
1717 buildApiBaseUrlCandidates ,
1818 type DockerNetworkIps ,
@@ -31,11 +31,6 @@ export { buildApiBaseUrlCandidates, isRemoteDockerHost } from "./controller-reac
3131
3232let selectedApiBaseUrl : string | undefined
3333
34- type HealthProbeResult = {
35- readonly apiBaseUrl : string
36- readonly revision : string | null
37- }
38-
3934const controllerBootstrapError = ( message : string ) : ControllerBootstrapError => ( {
4035 _tag : "ControllerBootstrapError" ,
4136 message
@@ -48,84 +43,6 @@ const rememberSelectedApiBaseUrl = (value: string): void => {
4843export 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-
12946const 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-
230123const failIfExplicitApiUrlIsUnreachable = (
231124 explicitApiBaseUrl : string | undefined
232125) : Effect . Effect < void , ControllerBootstrapError > =>
@@ -294,15 +187,15 @@ const buildBootstrapCandidateUrls = (
294187const 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 ( ) )
0 commit comments