Unified · Split
| @@ -350,7 +350,10 @@ export const resolveSshTarget = Effect.fn("ssh/command.resolveSshTarget")(functi · 349 unmodified lines | |||
| 350 | Effect.logDebug("ssh.target.resolve.succeeded", sshTargetLogFields(target)), | 350 | Effect.logDebug("ssh.target.resolve.succeeded", sshTargetLogFields(target)), |
| 351 | ), | 351 | ), |
| 352 | Effect.catch((cause) => | 352 | Effect.catch((cause) => |
| 353 | Effect.logDebug("ssh.target.resolve.fallback", { alias: trimmedAlias, cause }).pipe( | 353 | Effect.logDebug("ssh.target.resolve.fallback", { |
| 354 | alias: trimmedAlias, | ||
| 355 | failureTag: cause._tag, | ||
| 356 | }).pipe( | ||
| 354 | Effect.as({ | 357 | Effect.as({ |
| 355 | alias: trimmedAlias, | 358 | alias: trimmedAlias, |
| 356 | hostname: trimmedAlias, | 359 | hostname: trimmedAlias, |
| @@ -5,12 +5,14 @@ import * as Duration from "effect/Duration"; · 4 unmodified lines | |||
| 5 | import * as Effect from "effect/Effect"; | 5 | import * as Effect from "effect/Effect"; |
| 6 | import * as Fiber from "effect/Fiber"; | 6 | import * as Fiber from "effect/Fiber"; |
| 7 | import * as Layer from "effect/Layer"; | 7 | import * as Layer from "effect/Layer"; |
| 8 | import * as Logger from "effect/Logger"; | ||
| 8 | import * as PlatformError from "effect/PlatformError"; | 9 | import * as PlatformError from "effect/PlatformError"; |
| 9 | import * as Result from "effect/Result"; | 10 | import * as Result from "effect/Result"; |
| 10 | import * as Sink from "effect/Sink"; | 11 | import * as Sink from "effect/Sink"; |
| 11 | import * as Stream from "effect/Stream"; | 12 | import * as Stream from "effect/Stream"; |
| 12 | import * as TestClock from "effect/testing/TestClock"; | 13 | import * as TestClock from "effect/testing/TestClock"; |
| 13 | import * as HttpClient from "effect/unstable/http/HttpClient"; | 14 | import * as HttpClient from "effect/unstable/http/HttpClient"; |
| 15 | import * as HttpClientError from "effect/unstable/http/HttpClientError"; | ||
| 14 | import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse"; | 16 | import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse"; |
| 15 | import * as ChildProcess from "effect/unstable/process/ChildProcess"; | 17 | import * as ChildProcess from "effect/unstable/process/ChildProcess"; |
| 16 | import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner"; | 18 | import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner"; |
| @@ -21,6 +23,7 @@ import { · 4 unmodified lines | |||
| 21 | SshHttpBridgeMissingUrlError, | 23 | SshHttpBridgeMissingUrlError, |
| 22 | SshHttpBridgeNonLoopbackUrlError, | 24 | SshHttpBridgeNonLoopbackUrlError, |
| 23 | SshPairingOutputParseError, | 25 | SshPairingOutputParseError, |
| 26 | SshReadinessProbeError, | ||
| 24 | SshReadinessProbeTimeoutError, | 27 | SshReadinessProbeTimeoutError, |
| 25 | SshReadinessTimeoutError, | 28 | SshReadinessTimeoutError, |
| 26 | } from "./errors.ts"; | 29 | } from "./errors.ts"; |
| @@ -88,6 +91,14 @@ function commandArgs(command: ChildProcess.Command): ReadonlyArray<string> { · 61 unmodified lines | |||
| 88 | return command._tag === "StandardCommand" ? command.args : []; | 91 | return command._tag === "StandardCommand" ? command.args : []; |
| 89 | } | 92 | } |
| 90 | 93 | ||
| 94 | function flattenedLogText(value: unknown): string { | ||
| 95 | if (typeof value === "string") return value; | ||
| 96 | if (typeof value !== "object" || value === null) return String(value); | ||
| 97 | return Object.entries(value) | ||
| 98 | .flatMap(([key, nested]) => [key, flattenedLogText(nested)]) | ||
| 99 | .join("\n"); | ||
| 100 | } | ||
| 101 | |||
| 91 | describe("ssh tunnel scripts", () => { | 102 | describe("ssh tunnel scripts", () => { |
| 92 | it("builds the remote t3 runner with npx and npm fallbacks", () => { | 103 | it("builds the remote t3 runner with npx and npm fallbacks", () => { |
| 93 | const script = SshTunnel.buildRemoteT3RunnerScript({ nodeEngineRange: TEST_NODE_ENGINE_RANGE }); | 104 | const script = SshTunnel.buildRemoteT3RunnerScript({ nodeEngineRange: TEST_NODE_ENGINE_RANGE }); |
| @@ -255,13 +266,22 @@ describe("ssh tunnel scripts", () => { · 161 unmodified lines | |||
| 255 | assert.include(SshTunnel.REMOTE_PICK_PORT_SCRIPT, 'const filePath = process.argv[2] ?? "";'); | 266 | assert.include(SshTunnel.REMOTE_PICK_PORT_SCRIPT, 'const filePath = process.argv[2] ?? "";'); |
| 256 | }); | 267 | }); |
| 257 | 268 | ||
| 258 | it.effect("bounds each HTTP readiness probe so retries cannot hang on one request", () => | 269 | it.effect("bounds each HTTP readiness probe so retries cannot hang on one request", () => { |
| 259 | Effect.gen(function* () { | 270 | const baseUrl = |
| 271 | "http://ssh-user:ssh-password@127.0.0.1:41773/private/base?token=base-secret#base-fragment"; | ||
| 272 | const path = "/ready/private?credential=request-secret#request-fragment"; | ||
| 273 | const requestUrl = new URL(path, baseUrl).toString(); | ||
| 274 | const capturedLogs: Array<ReadonlyArray<unknown>> = []; | ||
| 275 | const logger = Logger.make(({ message }) => { | ||
| 276 | capturedLogs.push(Array.isArray(message) ? message : [message]); | ||
| 277 | }); | ||
| 278 | |||
| 279 | return Effect.gen(function* () { | ||
| 260 | const fiber = yield* Effect.forkChild( | 280 | const fiber = yield* Effect.forkChild( |
| 261 | Effect.result( | 281 | Effect.result( |
| 262 | SshTunnel.waitForHttpReady({ | 282 | SshTunnel.waitForHttpReady({ |
| 263 | baseUrl: "http://127.0.0.1:41773/?token=base-secret#fragment", | 283 | baseUrl, |
| 264 | path: "/ready?credential=request-secret#fragment", | 284 | path, |
| 265 | timeoutMs: 1_000, | 285 | timeoutMs: 1_000, |
| 266 | intervalMs: 100, | 286 | intervalMs: 100, |
| 267 | probeTimeoutMs: 250, | 287 | probeTimeoutMs: 250, |
| @@ -281,8 +301,8 @@ describe("ssh tunnel scripts", () => { · 13 unmodified lines | |||
| 281 | protocol: "http:", | 301 | protocol: "http:", |
| 282 | hostname: "127.0.0.1", | 302 | hostname: "127.0.0.1", |
| 283 | port: "41773", | 303 | port: "41773", |
| 284 | urlLength: 50, | 304 | urlLength: baseUrl.length, |
| 285 | pathnameLength: 1, | 305 | pathnameLength: new TextEncoder().encode(new URL(baseUrl).pathname).length, |
| 286 | hasQuery: true, | 306 | hasQuery: true, |
| 287 | hasFragment: true, | 307 | hasFragment: true, |
| 288 | }); | 308 | }); |
| @@ -290,8 +310,8 @@ describe("ssh tunnel scripts", () => { · 1 unmodified line | |||
| 290 | protocol: "http:", | 310 | protocol: "http:", |
| 291 | hostname: "127.0.0.1", | 311 | hostname: "127.0.0.1", |
| 292 | port: "41773", | 312 | port: "41773", |
| 293 | urlLength: 63, | 313 | urlLength: requestUrl.length, |
| 294 | pathnameLength: 6, | 314 | pathnameLength: new TextEncoder().encode(new URL(requestUrl).pathname).length, |
| 295 | hasQuery: true, | 315 | hasQuery: true, |
| 296 | hasFragment: true, | 316 | hasFragment: true, |
| 297 | }); | 317 | }); |
| @@ -311,13 +331,91 @@ describe("ssh tunnel scripts", () => { · 13 unmodified lines | |||
| 311 | const probeTimeout = timeoutError.cause as SshReadinessProbeTimeoutError; | 331 | const probeTimeout = timeoutError.cause as SshReadinessProbeTimeoutError; |
| 312 | assert.equal(probeTimeout.attempt, timeoutError.attempts); | 332 | assert.equal(probeTimeout.attempt, timeoutError.attempts); |
| 313 | assert.isFalse("cause" in probeTimeout); | 333 | assert.isFalse("cause" in probeTimeout); |
| 334 | |||
| 335 | const logText = flattenedLogText(capturedLogs); | ||
| 336 | assert.include(logText, "127.0.0.1"); | ||
| 337 | assert.notInclude(logText, "ssh-user"); | ||
| 338 | assert.notInclude(logText, "ssh-password"); | ||
| 339 | assert.notInclude(logText, "/private/base"); | ||
| 340 | assert.notInclude(logText, "base-secret"); | ||
| 341 | assert.notInclude(logText, "/ready/private"); | ||
| 342 | assert.notInclude(logText, "request-secret"); | ||
| 343 | assert.notInclude(logText, "base-fragment"); | ||
| 344 | assert.notInclude(logText, "request-fragment"); | ||
| 314 | } | 345 | } |
| 315 | }).pipe( | 346 | }).pipe( |
| 316 | Effect.provide( | 347 | Effect.provide( |
| 317 | Layer.merge(TestClock.layer(), Layer.succeed(HttpClient.HttpClient, hangingHttpClient)), | 348 | Layer.mergeAll( |
| 349 | TestClock.layer(), | ||
| 350 | Layer.succeed(HttpClient.HttpClient, hangingHttpClient), | ||
| 351 | Logger.layer([logger], { mergeWithExisting: false }), | ||
| 352 | ), | ||
| 318 | ), | 353 | ), |
| 319 | ), | 354 | ); |
| 320 | ); | 355 | }); |
| 356 | |||
| 357 | it.effect("preserves the exact HTTP transport cause without logging request secrets", () => { | ||
| 358 | const baseUrl = | ||
| 359 | "http://transport-user:transport-password@127.0.0.1:41773/private?token=transport-secret#fragment"; | ||
| 360 | const transportCause = new Error("socket closed"); | ||
| 361 | const clientErrors: Array<HttpClientError.HttpClientError> = []; | ||
| 362 | const failingHttpClient = HttpClient.make((request) => { | ||
| 363 | const error = new HttpClientError.HttpClientError({ | ||
| 364 | reason: new HttpClientError.TransportError({ request, cause: transportCause }), | ||
| 365 | }); | ||
| 366 | clientErrors.push(error); | ||
| 367 | return Effect.fail(error); | ||
| 368 | }); | ||
| 369 | const capturedLogs: Array<ReadonlyArray<unknown>> = []; | ||
| 370 | const logger = Logger.make(({ message }) => { | ||
| 371 | capturedLogs.push(Array.isArray(message) ? message : [message]); | ||
| 372 | }); | ||
| 373 | |||
| 374 | return Effect.gen(function* () { | ||
| 375 | const fiber = yield* Effect.forkChild( | ||
| 376 | Effect.result( | ||
| 377 | SshTunnel.waitForHttpReady({ | ||
| 378 | baseUrl, | ||
| 379 | timeoutMs: 300, | ||
| 380 | intervalMs: 100, | ||
| 381 | probeTimeoutMs: 250, | ||
| 382 | }), | ||
| 383 | ), | ||
| 384 | ); | ||
| 385 | yield* Effect.yieldNow; | ||
| 386 | yield* TestClock.adjust(Duration.millis(300)); | ||
| 387 | |||
| 388 | const result = yield* Fiber.join(fiber); | ||
| 389 | assert.isTrue(Result.isFailure(result)); | ||
| 390 | if (Result.isFailure(result)) { | ||
| 391 | assert.instanceOf(result.failure, SshReadinessTimeoutError); | ||
| 392 | const timeoutError = result.failure as SshReadinessTimeoutError; | ||
| 393 | assert.instanceOf(timeoutError.cause, SshReadinessProbeError); | ||
| 394 | const probeError = timeoutError.cause as SshReadinessProbeError; | ||
| 395 | assert.strictEqual(probeError.cause, clientErrors.at(-1)); | ||
| 396 | assert.strictEqual( | ||
| 397 | (probeError.cause as HttpClientError.HttpClientError).reason.cause, | ||
| 398 | transportCause, | ||
| 399 | ); | ||
| 400 | |||
| 401 | const logText = flattenedLogText(capturedLogs); | ||
| 402 | assert.include(logText, "HttpClientError"); | ||
| 403 | assert.include(logText, "TransportError"); | ||
| 404 | assert.notInclude(logText, "transport-user"); | ||
| 405 | assert.notInclude(logText, "transport-password"); | ||
| 406 | assert.notInclude(logText, "/private"); | ||
| 407 | assert.notInclude(logText, "transport-secret"); | ||
| 408 | } | ||
| 409 | }).pipe( | ||
| 410 | Effect.provide( | ||
| 411 | Layer.mergeAll( | ||
| 412 | TestClock.layer(), | ||
| 413 | Layer.succeed(HttpClient.HttpClient, failingHttpClient), | ||
| 414 | Logger.layer([logger], { mergeWithExisting: false }), | ||
| 415 | ), | ||
| 416 | ), | ||
| 417 | ); | ||
| 418 | }); | ||
| 321 | 419 | ||
| 322 | it.effect("preserves forwarded HTTP URL error messages", () => | 420 | it.effect("preserves forwarded HTTP URL error messages", () => |
| 323 | Effect.gen(function* () { | 421 | Effect.gen(function* () { |
| @@ -363,6 +461,10 @@ describe("ssh tunnel scripts", () => { · 39 unmodified lines | |||
| 363 | cause: { type: "string" }, | 461 | cause: { type: "string" }, |
| 364 | }, | 462 | }, |
| 365 | ); | 463 | ); |
| 464 | |||
| 465 | const cyclicCause: { readonly _tag: string; cause?: unknown } = { _tag: "CyclicCause" }; | ||
| 466 | cyclicCause.cause = cyclicCause; | ||
| 467 | assert.include(flattenedLogText(SshTunnel.describeReadinessCause(cyclicCause)), "truncated"); | ||
| 366 | }); | 468 | }); |
| 367 | 469 | ||
| 368 | it("preserves structured readiness attributes in diagnostic output", () => { | 470 | it("preserves structured readiness attributes in diagnostic output", () => { |
| @@ -542,6 +644,10 @@ describe("ssh tunnel scripts", () => { · 173 unmodified lines | |||
| 542 | const spawnedCommands: Array<ReadonlyArray<string>> = []; | 644 | const spawnedCommands: Array<ReadonlyArray<string>> = []; |
| 543 | let tunnelKillCount = 0; | 645 | let tunnelKillCount = 0; |
| 544 | let stopCommandCount = 0; | 646 | let stopCommandCount = 0; |
| 647 | const capturedLogs: Array<ReadonlyArray<unknown>> = []; | ||
| 648 | const logger = Logger.make(({ message }) => { | ||
| 649 | capturedLogs.push(Array.isArray(message) ? message : [message]); | ||
| 650 | }); | ||
| 545 | const spawner = ChildProcessSpawner.make((command) => | 651 | const spawner = ChildProcessSpawner.make((command) => |
| 546 | Effect.sync(() => { | 652 | Effect.sync(() => { |
| 547 | const args = commandArgs(command); | 653 | const args = commandArgs(command); |
| @@ -568,6 +674,7 @@ describe("ssh tunnel scripts", () => { · 20 unmodified lines | |||
| 568 | Layer.succeed(NetService.NetService, testNetService), | 674 | Layer.succeed(NetService.NetService, testNetService), |
| 569 | SshAuth.disabledLayer, | 675 | SshAuth.disabledLayer, |
| 570 | SshTunnel.layer(), | 676 | SshTunnel.layer(), |
| 677 | Logger.layer([logger], { mergeWithExisting: false }), | ||
| 571 | ); | 678 | ); |
| 572 | const target = { | 679 | const target = { |
| 573 | alias: "devbox", | 680 | alias: "devbox", |
| @@ -590,6 +697,10 @@ describe("ssh tunnel scripts", () => { · 16 unmodified lines | |||
| 590 | 697 | ||
| 591 | assert.equal(spawnedCommands.filter((args) => args.includes("-N")).length, 2); | 698 | assert.equal(spawnedCommands.filter((args) => args.includes("-N")).length, 2); |
| 592 | assert.equal(tunnelKillCount, 1); | 699 | assert.equal(tunnelKillCount, 1); |
| 700 | |||
| 701 | const logText = flattenedLogText(capturedLogs); | ||
| 702 | assert.include(logText, "httpBaseUrlDiagnostics"); | ||
| 703 | assert.notInclude(logText, "httpBaseUrl\nhttp://"); | ||
| 593 | }).pipe(Effect.provide(layer), Effect.scoped); | 704 | }).pipe(Effect.provide(layer), Effect.scoped); |
| 594 | }); | 705 | }); |
| 595 | }); | 706 | }); |
| @@ -5,6 +5,8 @@ import type { · 4 unmodified lines | |||
| 5 | import * as NetService from "@t3tools/shared/Net"; | 5 | import * as NetService from "@t3tools/shared/Net"; |
| 6 | import { extractJsonObject, fromLenientJson } from "@t3tools/shared/schemaJson"; | 6 | import { extractJsonObject, fromLenientJson } from "@t3tools/shared/schemaJson"; |
| 7 | import { satisfiesSemverRange } from "@t3tools/shared/semver"; | 7 | import { satisfiesSemverRange } from "@t3tools/shared/semver"; |
| 8 | import { getUrlDiagnostics } from "@t3tools/shared/urlDiagnostics"; | ||
| 9 | import * as Cause from "effect/Cause"; | ||
| 8 | import * as Context from "effect/Context"; | 10 | import * as Context from "effect/Context"; |
| 9 | import * as Deferred from "effect/Deferred"; | 11 | import * as Deferred from "effect/Deferred"; |
| 10 | import * as Duration from "effect/Duration"; | 12 | import * as Duration from "effect/Duration"; |
| @@ -243,7 +245,24 @@ function applyScriptPlaceholders( · 232 unmodified lines | |||
| 243 | return result; | 245 | return result; |
| 244 | } | 246 | } |
| 245 | 247 | ||
| 246 | export function describeReadinessCause(cause: unknown): unknown { | 248 | const SSH_FAILURE_DIAGNOSTIC_DEPTH = 6; |
| 249 | |||
| 250 | function describeReadinessCauseAtDepth(cause: unknown, depth: number): unknown { | ||
| 251 | if (depth >= SSH_FAILURE_DIAGNOSTIC_DEPTH) return { truncated: true }; | ||
| 252 | const describeNested = (nested: unknown) => describeReadinessCauseAtDepth(nested, depth + 1); | ||
| 253 | if (Cause.isCause(cause)) { | ||
| 254 | return { | ||
| 255 | reasons: cause.reasons.map((reason) => { | ||
| 256 | if (Cause.isFailReason(reason)) { | ||
| 257 | return { _tag: reason._tag, error: describeNested(reason.error) }; | ||
| 258 | } | ||
| 259 | if (Cause.isDieReason(reason)) { | ||
| 260 | return { _tag: reason._tag, defect: describeNested(reason.defect) }; | ||
| 261 | } | ||
| 262 | return { _tag: reason._tag, fiberId: reason.fiberId }; | ||
| 263 | }), | ||
| 264 | }; | ||
| 265 | } | ||
| 247 | if (isSshReadinessError(cause)) { | 266 | if (isSshReadinessError(cause)) { |
| 248 | const { cause: nestedCause, ...attributes } = cause as SshReadinessError & { | 267 | const { cause: nestedCause, ...attributes } = cause as SshReadinessError & { |
| 249 | readonly cause?: unknown; | 268 | readonly cause?: unknown; |
| @@ -251,13 +270,15 @@ export function describeReadinessCause(cause: unknown): unknown { · 1 unmodified line | |||
| 251 | return { | 270 | return { |
| 252 | ...attributes, | 271 | ...attributes, |
| 253 | message: cause.message, | 272 | message: cause.message, |
| 254 | ...(nestedCause === undefined ? {} : { cause: describeReadinessCause(nestedCause) }), | 273 | ...(nestedCause === undefined ? {} : { cause: describeNested(nestedCause) }), |
| 255 | }; | 274 | }; |
| 256 | } | 275 | } |
| 257 | if (cause instanceof Error) { | 276 | if (cause instanceof Error) { |
| 277 | const tagged = cause as Error & { readonly _tag?: unknown; readonly reason?: unknown }; | ||
| 258 | return { | 278 | return { |
| 259 | name: cause.name, | 279 | ...(typeof tagged._tag === "string" ? { _tag: tagged._tag } : { name: cause.name }), |
| 260 | ...(cause.cause === undefined ? {} : { cause: describeReadinessCause(cause.cause) }), | 280 | ...(tagged.reason === undefined ? {} : { reason: describeNested(tagged.reason) }), |
| 281 | ...(cause.cause === undefined ? {} : { cause: describeNested(cause.cause) }), | ||
| 261 | }; | 282 | }; |
| 262 | } | 283 | } |
| 263 | if (typeof cause !== "object" || cause === null) { | 284 | if (typeof cause !== "object" || cause === null) { |
| @@ -267,17 +288,23 @@ export function describeReadinessCause(cause: unknown): unknown { · 3 unmodified lines | |||
| 267 | const record = cause as Readonly<Record<string, unknown>>; | 288 | const record = cause as Readonly<Record<string, unknown>>; |
| 268 | return { | 289 | return { |
| 269 | ...(typeof record._tag === "string" ? { _tag: record._tag } : {}), | 290 | ...(typeof record._tag === "string" ? { _tag: record._tag } : {}), |
| 270 | ...(record.reason === undefined ? {} : { reason: describeReadinessCause(record.reason) }), | 291 | ...(record.reason === undefined ? {} : { reason: describeNested(record.reason) }), |
| 271 | ...(record.cause === undefined ? {} : { cause: describeReadinessCause(record.cause) }), | 292 | ...(record.cause === undefined ? {} : { cause: describeNested(record.cause) }), |
| 272 | }; | 293 | }; |
| 273 | } | 294 | } |
| 274 | 295 | ||
| 275 | function readinessUrlDiagnostics(url: URL, urlLength: number) { | 296 | export function describeReadinessCause(cause: unknown): unknown { |
| 297 | return describeReadinessCauseAtDepth(cause, 0); | ||
| 298 | } | ||
| 299 | |||
| 300 | function readinessUrlDiagnostics(input: string) { | ||
| 301 | const url = new URL(input); | ||
| 302 | const diagnostics = getUrlDiagnostics(input); | ||
| 276 | return { | 303 | return { |
| 277 | protocol: url.protocol, | 304 | protocol: diagnostics.protocol ?? url.protocol, |
| 278 | hostname: url.hostname, | 305 | hostname: diagnostics.hostname ?? url.hostname, |
| 279 | ...(url.port === "" ? {} : { port: url.port }), | 306 | ...(url.port === "" ? {} : { port: url.port }), |
| 280 | urlLength, | 307 | urlLength: diagnostics.inputLength, |
| 281 | pathnameLength: utf8ByteLength(url.pathname), | 308 | pathnameLength: utf8ByteLength(url.pathname), |
| 282 | hasQuery: url.search !== "", | 309 | hasQuery: url.search !== "", |
| 283 | hasFragment: url.hash !== "", | 310 | hasFragment: url.hash !== "", |
| @@ -900,8 +927,8 @@ export const waitForHttpReady = Effect.fn("ssh/tunnel.waitForHttpReady")(functio · 616 unmodified lines | |||
| 900 | const baseUrl = new URL(input.baseUrl); | 927 | const baseUrl = new URL(input.baseUrl); |
| 901 | const request = new URL(input.path ?? "/", baseUrl); | 928 | const request = new URL(input.path ?? "/", baseUrl); |
| 902 | const requestUrl = request.toString(); | 929 | const requestUrl = request.toString(); |
| 903 | const base = readinessUrlDiagnostics(baseUrl, utf8ByteLength(input.baseUrl)); | 930 | const base = readinessUrlDiagnostics(input.baseUrl); |
| 904 | const requestDiagnostics = readinessUrlDiagnostics(request, utf8ByteLength(requestUrl)); | 931 | const requestDiagnostics = readinessUrlDiagnostics(requestUrl); |
| 905 | const client = yield* HttpClient.HttpClient; | 932 | const client = yield* HttpClient.HttpClient; |
| 906 | const lastProbeFailure = yield* Ref.make<unknown>(null); | 933 | const lastProbeFailure = yield* Ref.make<unknown>(null); |
| 907 | let attempt = 0; | 934 | let attempt = 0; |
| @@ -1088,6 +1115,7 @@ const startSshTunnel = Effect.fn("ssh/tunnel.startSshTunnel")(function* (input: · 180 unmodified lines | |||
| 1088 | const sshCommand = yield* resolveSshCommand; | 1115 | const sshCommand = yield* resolveSshCommand; |
| 1089 | const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; | 1116 | const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; |
| 1090 | const scope = yield* Scope.Scope; | 1117 | const scope = yield* Scope.Scope; |
| 1118 | const httpBaseUrlDiagnostics = getUrlDiagnostics(input.httpBaseUrl); | ||
| 1091 | yield* Effect.logDebug("ssh.tunnel.spawn.start", { | 1119 | yield* Effect.logDebug("ssh.tunnel.spawn.start", { |
| 1092 | ...sshTargetLogFields(input.resolvedTarget), | 1120 | ...sshTargetLogFields(input.resolvedTarget), |
| 1093 | command: sshCommand, | 1121 | command: sshCommand, |
| @@ -1095,7 +1123,7 @@ const startSshTunnel = Effect.fn("ssh/tunnel.startSshTunnel")(function* (input: · 1 unmodified line | |||
| 1095 | localPort: input.localPort, | 1123 | localPort: input.localPort, |
| 1096 | remotePort: input.remotePort, | 1124 | remotePort: input.remotePort, |
| 1097 | remoteServerKind: input.remoteServerKind, | 1125 | remoteServerKind: input.remoteServerKind, |
| 1098 | httpBaseUrl: input.httpBaseUrl, | 1126 | httpBaseUrlDiagnostics, |
| 1099 | }); | 1127 | }); |
| 1100 | const child = yield* spawner | 1128 | const child = yield* spawner |
| 1101 | .spawn( | 1129 | .spawn( |
| @@ -1128,7 +1156,7 @@ const startSshTunnel = Effect.fn("ssh/tunnel.startSshTunnel")(function* (input: · 26 unmodified lines | |||
| 1128 | pid: child.pid, | 1156 | pid: child.pid, |
| 1129 | localPort: input.localPort, | 1157 | localPort: input.localPort, |
| 1130 | remotePort: input.remotePort, | 1158 | remotePort: input.remotePort, |
| 1131 | httpBaseUrl: input.httpBaseUrl, | 1159 | httpBaseUrlDiagnostics, |
| 1132 | }); | 1160 | }); |
| 1133 | const tunnelEntry: SshTunnelEntry = { | 1161 | const tunnelEntry: SshTunnelEntry = { |
| 1134 | key: input.key, | 1162 | key: input.key, |
| @@ -1171,7 +1199,7 @@ const startSshTunnel = Effect.fn("ssh/tunnel.startSshTunnel")(function* (input: · 36 unmodified lines | |||
| 1171 | pid: child.pid, | 1199 | pid: child.pid, |
| 1172 | localPort: input.localPort, | 1200 | localPort: input.localPort, |
| 1173 | remotePort: input.remotePort, | 1201 | remotePort: input.remotePort, |
| 1174 | httpBaseUrl: input.httpBaseUrl, | 1202 | httpBaseUrlDiagnostics, |
| 1175 | exitCode, | 1203 | exitCode, |
| 1176 | stderrBytes: utf8ByteLength(stderr), | 1204 | stderrBytes: utf8ByteLength(stderr), |
| 1177 | }).pipe(Effect.andThen(Effect.fail(error))); | 1205 | }).pipe(Effect.andThen(Effect.fail(error))); |
| @@ -1192,7 +1220,7 @@ const startSshTunnel = Effect.fn("ssh/tunnel.startSshTunnel")(function* (input: · 14 unmodified lines | |||
| 1192 | pid: child.pid, | 1220 | pid: child.pid, |
| 1193 | localPort: input.localPort, | 1221 | localPort: input.localPort, |
| 1194 | remotePort: input.remotePort, | 1222 | remotePort: input.remotePort, |
| 1195 | httpBaseUrl: input.httpBaseUrl, | 1223 | httpBaseUrlDiagnostics, |
| 1196 | }), | 1224 | }), |
| 1197 | ), | 1225 | ), |
| 1198 | Effect.tapError((cause) => | 1226 | Effect.tapError((cause) => |
| @@ -1220,19 +1248,19 @@ const startSshTunnel = Effect.fn("ssh/tunnel.startSshTunnel")(function* (input: · 21 unmodified lines | |||
| 1220 | processRunning, | 1248 | processRunning, |
| 1221 | ...(Exit.isSuccess(processRunningExit) | 1249 | ...(Exit.isSuccess(processRunningExit) |
| 1222 | ? {} | 1250 | ? {} |
| 1223 | : { processRunningError: processRunningExit.cause }), | 1251 | : { processRunningFailure: describeReadinessCause(processRunningExit.cause) }), |
| 1224 | localPort: input.localPort, | 1252 | localPort: input.localPort, |
| 1225 | localPortListening: localPortAvailable === null ? null : !localPortAvailable, | 1253 | localPortListening: localPortAvailable === null ? null : !localPortAvailable, |
| 1226 | remotePort: input.remotePort, | 1254 | remotePort: input.remotePort, |
| 1227 | httpBaseUrl: input.httpBaseUrl, | 1255 | httpBaseUrlDiagnostics, |
| 1228 | ...(Exit.isSuccess(localPortAvailableExit) | 1256 | ...(Exit.isSuccess(localPortAvailableExit) |
| 1229 | ? {} | 1257 | ? {} |
| 1230 | : { localPortProbeError: localPortAvailableExit.cause }), | 1258 | : { localPortProbeFailure: describeReadinessCause(localPortAvailableExit.cause) }), |
| 1231 | ...(remoteLogTail === null ? {} : { remoteLogTailBytes: utf8ByteLength(remoteLogTail) }), | 1259 | ...(remoteLogTail === null ? {} : { remoteLogTailBytes: utf8ByteLength(remoteLogTail) }), |
| 1232 | ...(Exit.isSuccess(remoteLogTailExit) | 1260 | ...(Exit.isSuccess(remoteLogTailExit) |
| 1233 | ? {} | 1261 | ? {} |
| 1234 | : { remoteLogTailError: remoteLogTailExit.cause }), | 1262 | : { remoteLogTailFailure: describeReadinessCause(remoteLogTailExit.cause) }), |
| 1235 | cause, | 1263 | failure: describeReadinessCause(cause), |
| 1236 | }); | 1264 | }); |
| 1237 | }), | 1265 | }), |
| 1238 | ), | 1266 | ), |
| @@ -1370,7 +1398,7 @@ export const make = Effect.fn("ssh/tunnel.SshEnvironmentManager.make")(function* · 131 unmodified lines | |||
| 1370 | ...sshTargetLogFields(input.target), | 1398 | ...sshTargetLogFields(input.target), |
| 1371 | key: input.key, | 1399 | key: input.key, |
| 1372 | promptCount: input.promptCount, | 1400 | promptCount: input.promptCount, |
| 1373 | cause: input.error, | 1401 | failure: describeReadinessCause(input.error), |
| 1374 | }); | 1402 | }); |
| 1375 | const promptService = yield* SshAuth.SshPasswordPrompt; | 1403 | const promptService = yield* SshAuth.SshPasswordPrompt; |
| 1376 | if (!promptService.isAvailable) { | 1404 | if (!promptService.isAvailable) { |
| @@ -1569,7 +1597,7 @@ export const make = Effect.fn("ssh/tunnel.SshEnvironmentManager.make")(function* · 192 unmodified lines | |||
| 1569 | key, | 1597 | key, |
| 1570 | localPort: entry.localPort, | 1598 | localPort: entry.localPort, |
| 1571 | remotePort: entry.remotePort, | 1599 | remotePort: entry.remotePort, |
| 1572 | cause: readinessExit.cause, | 1600 | failure: describeReadinessCause(readinessExit.cause), |
| 1573 | }); | 1601 | }); |
| 1574 | yield* closeTunnelEntry(entry); | 1602 | yield* closeTunnelEntry(entry); |
| 1575 | yield* cancelPendingTunnelEntry(key, resolvedTarget); | 1603 | yield* cancelPendingTunnelEntry(key, resolvedTarget); |
| @@ -1597,7 +1625,7 @@ export const make = Effect.fn("ssh/tunnel.SshEnvironmentManager.make")(function* · 21 unmodified lines | |||
| 1597 | Effect.logWarning("ssh.environment.tunnel.create.failed", { | 1625 | Effect.logWarning("ssh.environment.tunnel.create.failed", { |
| 1598 | ...sshTargetLogFields(resolvedTarget), | 1626 | ...sshTargetLogFields(resolvedTarget), |
| 1599 | key, | 1627 | key, |
| 1600 | cause, | 1628 | failure: describeReadinessCause(cause), |
| 1601 | }), | 1629 | }), |
| 1602 | ), | 1630 | ), |
| 1603 | Effect.onExit((exit) => | 1631 | Effect.onExit((exit) => |