mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
refactor(ssh): remove the ssh-relay-deploy-helpers re-export module (#27961)
Co-authored-by: m4air <m4air@Mac.localdomain>
This commit is contained in:
@@ -5462,7 +5462,7 @@
|
||||
"src/main/ssh/ssh-reconnect-error-classification.test.ts": 123,
|
||||
"src/main/ssh/ssh-reconnect-ladder.test.ts": 96,
|
||||
"src/main/ssh/ssh-relay-answered-failure-producers.test.ts": 301,
|
||||
"src/main/ssh/ssh-relay-deploy-helpers.test.ts": 571,
|
||||
"src/main/ssh/ssh-relay-sentinel.test.ts": 571,
|
||||
"src/main/ssh/ssh-relay-endpoint-incumbent-shell.integration.test.ts": 2228,
|
||||
"src/main/ssh/ssh-relay-endpoint-incumbent.test.ts": 115,
|
||||
"src/main/ssh/ssh-relay-endpoint-pty-count.test.ts": 695,
|
||||
|
||||
@@ -62,7 +62,7 @@ describe('a connect cancelled during its server decision', () => {
|
||||
// The census dials after the teardown, opening a transport no one else holds.
|
||||
recordSshConnectionOpened(asTransport(opened))
|
||||
mockConnectionManager.getConnection.mockReturnValue(opened)
|
||||
return { route: 'relay', reason: 'failed' }
|
||||
return { route: 'unserved', reason: 'failed' }
|
||||
})
|
||||
await expect(
|
||||
Promise.resolve(handlers.get('ssh:connect')!(null, { targetId: 'ssh-1' }))
|
||||
@@ -90,7 +90,7 @@ describe('a connect cancelled during its server decision', () => {
|
||||
recordSshConnectionOpened(asTransport(opened))
|
||||
mockConnectionManager.getConnection.mockReturnValue(opened)
|
||||
await new Promise<void>((resolve) => (resumeStale = resolve))
|
||||
return { route: 'relay', reason: 'orcad_unavailable', detail: 'unsupported_host' }
|
||||
return { route: 'unserved', reason: 'orcad_unavailable', detail: 'unsupported_host' }
|
||||
})
|
||||
const stale = Promise.resolve(handlers.get('ssh:connect')!(null, { targetId: 'ssh-1' }))
|
||||
await vi.waitFor(() => expect(staleDecided).toBe(true))
|
||||
|
||||
@@ -99,7 +99,7 @@ export function publishManagedServerConnect(
|
||||
*/
|
||||
export function publishUnservedHostServer(
|
||||
target: SshTarget,
|
||||
decision: Extract<HostServerOnConnectResult, { route: 'relay' }>
|
||||
decision: Extract<HostServerOnConnectResult, { route: 'unserved' }>
|
||||
): Error {
|
||||
const managedServer = unservedServerStatus(decision)
|
||||
setSshHostServerStatus(target.id, managedServer)
|
||||
|
||||
@@ -80,7 +80,11 @@ describe('connect-time server decision against the real profile', () => {
|
||||
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
|
||||
await expect(
|
||||
resolveHostServerOnConnect(target(), hostServerOnConnectDeps(userDataPath))
|
||||
).resolves.toEqual({ route: 'relay', reason: 'orcad_unavailable', detail: 'unsupported_host' })
|
||||
).resolves.toEqual({
|
||||
route: 'unserved',
|
||||
reason: 'orcad_unavailable',
|
||||
detail: 'unsupported_host'
|
||||
})
|
||||
warn.mockRestore()
|
||||
expect(target().orcadFence).toBeUndefined()
|
||||
expect(target().managedServerUnavailable).toEqual({
|
||||
|
||||
@@ -70,7 +70,7 @@ describe('SSH IPC handlers', () => {
|
||||
|
||||
it('fails a host the managed server does not serve, with no fallback', async () => {
|
||||
const decision = {
|
||||
route: 'relay' as const,
|
||||
route: 'unserved' as const,
|
||||
reason: 'orcad_unavailable' as const,
|
||||
detail: 'unsupported_host'
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* are installed side by side as `~/.orca-remote/orcad-<version>/`, launched and stopped with
|
||||
* the client's own deploy commands, and share one data root.
|
||||
*/
|
||||
import type * as ExecCommandModule from '../ssh/ssh-relay-exec-command'
|
||||
import { build } from 'esbuild'
|
||||
import {
|
||||
copyFileSync,
|
||||
@@ -59,7 +60,10 @@ import {
|
||||
|
||||
// GC runs its real commands, against this machine instead of an SSH host.
|
||||
const { execMock } = vi.hoisted(() => ({ execMock: vi.fn() }))
|
||||
vi.mock('../ssh/ssh-relay-deploy-helpers', () => ({ execCommand: execMock }))
|
||||
vi.mock('../ssh/ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: execMock
|
||||
}))
|
||||
|
||||
type Runtime = 'Bun' | 'Node'
|
||||
type Slot = { runtime: Runtime; dir: string; version: string }
|
||||
|
||||
@@ -10,7 +10,7 @@ import type {
|
||||
import { parseAppSshPtyId } from '../../../shared/ssh-pty-id'
|
||||
import type { SshRemotePtyLease } from '../../../shared/ssh-types'
|
||||
import type { SshConnection } from '../ssh-connection'
|
||||
import { waitForSentinel } from '../ssh-relay-deploy-helpers'
|
||||
import { waitForSentinel } from '../ssh-relay-sentinel'
|
||||
import { probeRelayEndpointIncumbent } from '../ssh-relay-endpoint-incumbent'
|
||||
import { isWindowsRemoteHost } from '../ssh-remote-platform'
|
||||
import { LegacyRelayClient } from './legacy-relay-client'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type * as ExecCommandModule from '../ssh-relay-exec-command'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import type { SshConnection } from '../ssh-connection'
|
||||
import { relaySocketNameForInstanceId } from '../ssh-relay-instance-id'
|
||||
@@ -5,7 +6,10 @@ import { windowsRelayPipePathsForSocketName } from '../ssh-relay-endpoints'
|
||||
import { getRemoteHostPlatform, joinRemotePath } from '../ssh-remote-platform'
|
||||
|
||||
const { execCommand } = vi.hoisted(() => ({ execCommand: vi.fn() }))
|
||||
vi.mock('../ssh-relay-deploy-helpers', () => ({ execCommand }))
|
||||
vi.mock('../ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand
|
||||
}))
|
||||
vi.mock('../ssh-remote-platform-detection', () => ({
|
||||
detectRemoteHostPlatform: async () => getRemoteHostPlatform('win32-x64')
|
||||
}))
|
||||
|
||||
@@ -9,7 +9,7 @@ import type { SshConnection } from '../ssh-connection'
|
||||
import { shellEscape } from '../ssh-connection-utils'
|
||||
import { RELAY_REMOTE_DIR } from '../relay-protocol'
|
||||
import { SHORT_RELAY_SOCKET_DIR_PREFIX } from '../relay-socket-path-limit'
|
||||
import { execCommand } from '../ssh-relay-deploy-helpers'
|
||||
import { execCommand } from '../ssh-relay-exec-command'
|
||||
import { readRelayDaemonRuntimes } from '../ssh-relay-endpoint-runtime'
|
||||
import { relaySocketNameForInstanceId } from '../ssh-relay-instance-id'
|
||||
import { readRemoteHomeCommand } from '../ssh-remote-commands'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type * as DeployHelpers from './ssh-relay-deploy-helpers'
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
import type * as RecordFile from './orcad-remote-record-file'
|
||||
import type * as InstallLock from './ssh-relay-install-lock'
|
||||
import type * as VersionedInstall from './remote-versioned-install'
|
||||
@@ -11,8 +11,8 @@ vi.mock('node:crypto', async (importOriginal) => {
|
||||
return { ...actual, randomUUID: () => uuid.fixed ?? actual.randomUUID() }
|
||||
})
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof DeployHelpers>()),
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: vi.fn()
|
||||
}))
|
||||
vi.mock('./ssh-connection-utils', () => ({
|
||||
@@ -38,7 +38,7 @@ vi.mock('./orcad-local-build-hash', () => ({
|
||||
computeLocalOrcadBuildHash: () => 'abc123def4567890'
|
||||
}))
|
||||
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import { acquireInstallLock } from './ssh-relay-install-lock'
|
||||
import { writeAtomicOrcadRemoteRecord } from './orcad-remote-record-file'
|
||||
import { deployOrcad } from './orcad-remote-deploy'
|
||||
|
||||
@@ -11,14 +11,14 @@ import {
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import type * as DeployHelpers from './ssh-relay-deploy-helpers'
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
|
||||
// Every remote command runs in a real local shell, so the host-side checks are the real ones.
|
||||
const shell = vi.hoisted((): { env: NodeJS.ProcessEnv | undefined } => ({ env: undefined }))
|
||||
vi.mock('./ssh-relay-deploy-helpers', async (importOriginal) => {
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => {
|
||||
const { runProcess } = await import('@orca/process-host')
|
||||
return {
|
||||
...(await importOriginal<typeof DeployHelpers>()),
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: async (_conn: unknown, command: string) => {
|
||||
const result = await runProcess({ program: '/bin/sh', args: ['-c', command], env: shell.env })
|
||||
if (result.code !== 0) {
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
withoutAbortSignal,
|
||||
type OrcadRemoteExecTarget
|
||||
} from './orcad-remote-runtime-control'
|
||||
import { isUnconfirmedSshCommandTermination } from './ssh-relay-deploy-helpers'
|
||||
import { isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
|
||||
import { acquireInstallLock, RemoteInstallLockBusyError } from './ssh-relay-install-lock'
|
||||
import {
|
||||
orphanInstallLockCommand,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import type * as DeployHelpers from './ssh-relay-deploy-helpers'
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
import type * as InstallLock from './ssh-relay-install-lock'
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof DeployHelpers>()),
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: vi.fn().mockResolvedValue('')
|
||||
}))
|
||||
vi.mock('./ssh-relay-install-lock', async (importOriginal) => ({
|
||||
@@ -11,7 +11,7 @@ vi.mock('./ssh-relay-install-lock', async (importOriginal) => ({
|
||||
acquireInstallLock: vi.fn().mockResolvedValue(undefined)
|
||||
}))
|
||||
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import { acquireInstallLock, RemoteInstallLockBusyError } from './ssh-relay-install-lock'
|
||||
import {
|
||||
parseOrcadActivationTransaction,
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
launchOrcadAndAwaitReadiness,
|
||||
type OrcadRemoteExecTarget
|
||||
} from './orcad-remote-runtime-control'
|
||||
import { isUnconfirmedSshCommandTermination } from './ssh-relay-deploy-helpers'
|
||||
import { isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
|
||||
|
||||
type CandidateLaunchTarget = Parameters<typeof launchOrcadAndAwaitReadiness>[0] &
|
||||
OrcadRemoteExecTarget & {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* lost answer is `unverifiable`, never exit; cancelling is how a stop that did not finish is
|
||||
* withdrawn, and only an orcad-confirmed cancellation lets the fence go.
|
||||
*/
|
||||
import { isUnconfirmedSshCommandTermination } from './ssh-relay-deploy-helpers'
|
||||
import { isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
|
||||
import {
|
||||
cancelRemoteOrcadManagedStop,
|
||||
completeRemoteOrcadManagedStop
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
parseGlibcVersion,
|
||||
@@ -6,10 +7,13 @@ import {
|
||||
} from './orcad-deployment-target'
|
||||
import { SshConnection } from './ssh-connection'
|
||||
import { createCallbacks, createTarget } from './ssh-connection-test-fixtures'
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import { getRemoteHostPlatform } from './ssh-remote-platform'
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', () => ({ execCommand: vi.fn() }))
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: vi.fn()
|
||||
}))
|
||||
|
||||
async function resolveOrcadDeploymentTarget(
|
||||
options: Parameters<typeof resolveOrcadDeploymentTargetFacts>[0]
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from '../../shared/node-runtime-pin'
|
||||
import { ORCAD_SERVER_TARGET_FILENAME } from '../../shared/orcad-artifacts'
|
||||
import type { RemoteInstallTransport } from './remote-install-transport'
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import type { RemoteHostPlatform } from './ssh-remote-platform'
|
||||
|
||||
/** The host answered the libc probe, but with nothing this client recognises. */
|
||||
|
||||
@@ -11,13 +11,13 @@ import { hostname, tmpdir, uptime } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { runProcess, spawnProcess } from '@orca/process-host'
|
||||
import type * as DeployHelpers from './ssh-relay-deploy-helpers'
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
|
||||
// The proof's remote commands run in a real local shell.
|
||||
vi.mock('./ssh-relay-deploy-helpers', async (importOriginal) => {
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => {
|
||||
const { runProcess: run } = await import('@orca/process-host')
|
||||
return {
|
||||
...(await importOriginal<typeof DeployHelpers>()),
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: async (_conn: unknown, command: string) =>
|
||||
(await run({ program: '/bin/sh', args: ['-c', command] })).stdout
|
||||
}
|
||||
|
||||
@@ -11,15 +11,15 @@ import {
|
||||
import { hostname, tmpdir, uptime } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import type * as DeployHelpers from './ssh-relay-deploy-helpers'
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
|
||||
// Every remote command runs in a real local shell, so the host-side checks are the real ones.
|
||||
const shell = vi.hoisted((): { env: NodeJS.ProcessEnv | undefined } => ({ env: undefined }))
|
||||
vi.mock('./ssh-relay-deploy-helpers', async (importOriginal) => {
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => {
|
||||
const { runProcess } = await import('@orca/process-host')
|
||||
const { sshCommandExitError } = await import('./ssh-relay-exec-command')
|
||||
return {
|
||||
...(await importOriginal<typeof DeployHelpers>()),
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: async (_conn: unknown, command: string) => {
|
||||
const result = await runProcess({ program: '/bin/sh', args: ['-c', command], env: shell.env })
|
||||
if (result.code !== 0) {
|
||||
|
||||
@@ -11,14 +11,14 @@ import {
|
||||
import { hostname, tmpdir, uptime } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import type * as DeployHelpers from './ssh-relay-deploy-helpers'
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
|
||||
// Every remote command runs in a real local shell, so the host-side checks are the real ones.
|
||||
vi.mock('./ssh-relay-deploy-helpers', async (importOriginal) => {
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => {
|
||||
const { runProcess } = await import('@orca/process-host')
|
||||
const { sshCommandExitError } = await import('./ssh-relay-exec-command')
|
||||
return {
|
||||
...(await importOriginal<typeof DeployHelpers>()),
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: async (_conn: unknown, command: string) => {
|
||||
const result = await runProcess({ program: '/bin/sh', args: ['-c', command] })
|
||||
if (result.code !== 0) {
|
||||
|
||||
@@ -2,15 +2,15 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { hostname, tmpdir, uptime } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type * as DeployHelpers from './ssh-relay-deploy-helpers'
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
|
||||
// The host script's own checks are covered against the real script; here only the client's part.
|
||||
const remote = vi.hoisted((): { commands: string[]; reply: string } => ({
|
||||
commands: [],
|
||||
reply: ''
|
||||
}))
|
||||
vi.mock('./ssh-relay-deploy-helpers', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof DeployHelpers>()),
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: async (_conn: unknown, command: string) => {
|
||||
remote.commands.push(command)
|
||||
return command.includes('script-present') ? 'ORCAD_HOST_SCRIPT_PRESENT\n' : remote.reply
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import { orcadRemoteBaseDir } from './orcad-remote-windows-node'
|
||||
import { randomUUID } from 'node:crypto'
|
||||
import type { OrcadDeployOptions, OrcadDeployResult } from './orcad-remote-deploy'
|
||||
import { isUnconfirmedSshCommandTermination } from './ssh-relay-deploy-helpers'
|
||||
import { isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
|
||||
import {
|
||||
withActivatedVersion,
|
||||
type OrcadActivationRecord,
|
||||
|
||||
@@ -16,7 +16,7 @@ import { execOrcadRemote } from './orcad-remote-runtime-control'
|
||||
import { readBoundedOrcadRemoteRecord } from './orcad-remote-record-file'
|
||||
import { orcadSlotDir, type OrcadSlotOptions } from './orcad-recovery-slot'
|
||||
import { isWindowsRemoteHost, joinRemotePath } from './ssh-remote-platform'
|
||||
import { isUnconfirmedSshCommandTermination } from './ssh-relay-deploy-helpers'
|
||||
import { isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
|
||||
import {
|
||||
orcadWindowsNodeCommandLine,
|
||||
orcadWindowsSlotOpCommand,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type * as DeployHelpers from './ssh-relay-deploy-helpers'
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
import type * as InstallLock from './ssh-relay-install-lock'
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof DeployHelpers>()),
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: vi.fn()
|
||||
}))
|
||||
vi.mock('./ssh-connection-utils', () => ({ shellEscape: (s: string) => `'${s}'` }))
|
||||
@@ -12,7 +12,7 @@ vi.mock('./ssh-relay-install-lock', async (importOriginal) => ({
|
||||
acquireInstallLock: vi.fn()
|
||||
}))
|
||||
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import { acquireInstallLock, RemoteInstallLockBusyError } from './ssh-relay-install-lock'
|
||||
import { wakeStoppedManagedOrcad } from './orcad-managed-wake'
|
||||
import { getRemoteHostPlatform } from './ssh-remote-platform'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Astra pass 4 §2: desktop A leases nothing, but desktop B's live shell, under another target id on
|
||||
// the same account, must never read as exited. COMPAT(legacy-relay-bridge): the host converts anyway
|
||||
// and leaves B's relay alone, so the census reads `bridged` with nothing of A's to serve.
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { SshConnection } from './ssh-connection'
|
||||
import { relaySocketNameForInstanceId } from './ssh-relay-instance-id'
|
||||
@@ -19,7 +20,10 @@ const {
|
||||
countRelayPtysOverBridge: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', () => ({ execCommand }))
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand
|
||||
}))
|
||||
vi.mock('./ssh-relay-endpoint-incumbent', async (importOriginal) => ({
|
||||
...(await importOriginal<Record<string, unknown>>()),
|
||||
probeRelayEndpointIncumbent
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type * as DeployHelpers from './ssh-relay-deploy-helpers'
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
import type * as RecordFile from './orcad-remote-record-file'
|
||||
import type * as InstallLock from './ssh-relay-install-lock'
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof DeployHelpers>()),
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: vi.fn()
|
||||
}))
|
||||
vi.mock('./ssh-connection-utils', () => ({ shellEscape: (s: string) => `'${s}'` }))
|
||||
@@ -17,7 +17,7 @@ vi.mock('./orcad-remote-record-file', async (importOriginal) => ({
|
||||
writeAtomicOrcadRemoteRecord: vi.fn()
|
||||
}))
|
||||
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import { acquireInstallLock } from './ssh-relay-install-lock'
|
||||
import { writeAtomicOrcadRemoteRecord } from './orcad-remote-record-file'
|
||||
import { decommissionRemoteOrcad } from './orcad-remote-decommission'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
import { chmodSync, mkdirSync, mkdtempSync, rmSync, statSync, writeFileSync } from 'node:fs'
|
||||
import type * as RecordFile from './orcad-remote-record-file'
|
||||
import { tmpdir } from 'node:os'
|
||||
@@ -5,9 +6,9 @@ import { dirname, join } from 'node:path'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { runProcess } from '@orca/process-host'
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', () => ({
|
||||
execCommand: vi.fn(),
|
||||
isUnconfirmedSshCommandTermination: () => false
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: vi.fn()
|
||||
}))
|
||||
vi.mock('./ssh-connection-utils', () => ({ shellEscape: (s: string) => `'${s}'` }))
|
||||
vi.mock('./ssh-relay-install-lock', () => ({
|
||||
@@ -31,7 +32,7 @@ vi.mock('./orcad-local-build-hash', () => ({
|
||||
computeLocalOrcadBuildHash: () => 'abc123def4567890'
|
||||
}))
|
||||
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import { acquireInstallLock, isRelayInstallLockStale } from './ssh-relay-install-lock'
|
||||
import { uploadRelayDirectory } from './ssh-relay-install-transfers'
|
||||
import { writeAtomicOrcadRemoteRecord } from './orcad-remote-record-file'
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', () => ({
|
||||
execCommand: vi.fn(),
|
||||
isUnconfirmedSshCommandTermination: () => false
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: vi.fn()
|
||||
}))
|
||||
vi.mock('./ssh-connection-utils', () => ({ shellEscape: (s: string) => `'${s}'` }))
|
||||
vi.mock('./ssh-relay-gc-claim', () => ({
|
||||
@@ -20,7 +21,7 @@ vi.mock('./ssh-relay-install-lock', () => ({
|
||||
isRelayInstallLockStale: vi.fn().mockResolvedValue(false)
|
||||
}))
|
||||
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import { gcOldOrcadVersions } from './orcad-remote-gc'
|
||||
import { emptyOrcadActivationRecord } from './orcad-activation-record'
|
||||
import { serializeOrcadActivationTransaction } from './orcad-activation-transaction'
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
* pinned too, and an unreadable journal skips the pass entirely.
|
||||
*/
|
||||
import type { OrcadHostTransport } from './orcad-host-transport'
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
|
||||
import { execCommand, isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
|
||||
import { ORCAD_INSTALL_MODEL } from './remote-install-model'
|
||||
import { gcOldRemoteInstallVersions } from './remote-install-gc'
|
||||
import { orcadGcPinnedDirNames, type OrcadActivationRecord } from './orcad-activation-record'
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', () => ({ execCommand: vi.fn() }))
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: vi.fn()
|
||||
}))
|
||||
vi.mock('./ssh-relay-install-lock', () => ({
|
||||
acquireInstallLock: vi.fn().mockResolvedValue(undefined),
|
||||
RELAY_INSTALL_LOCK_NAME: '.install-lock'
|
||||
@@ -14,7 +18,7 @@ vi.mock('./orcad-remote-node-runtime', () => ({
|
||||
}))
|
||||
|
||||
import type { OrcadHostTransport } from './orcad-host-transport'
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import { uploadRelayDirectory, writeRelayFile } from './ssh-relay-install-transfers'
|
||||
import { installOrcadBundle } from './orcad-remote-install'
|
||||
import { getRemoteHostPlatform } from './ssh-remote-platform'
|
||||
|
||||
@@ -3,8 +3,7 @@ import { orcadNodePtyNativeArtifacts, orcadRipgrepArtifact } from '../../shared/
|
||||
import { orcadAgentBrowserNativeName } from '../../shared/orcad-agent-browser-name'
|
||||
import type { NodeRuntimeTarget } from '../../shared/node-runtime-pin'
|
||||
import { ensureRemoteOrcadNodeRuntime } from './orcad-remote-node-runtime'
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
|
||||
import { execCommand, isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
|
||||
import { shellEscape } from './ssh-connection-utils'
|
||||
import type { OrcadHostTransport } from './orcad-host-transport'
|
||||
import { joinRemotePath, type RemoteHostPlatform } from './ssh-remote-platform'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
import type * as TerminalBarrier from './orcad-rollback-terminal-barrier'
|
||||
/**
|
||||
* The deploy and rollback drivers end to end against a Windows host whose every host op is answered by a
|
||||
@@ -6,9 +7,9 @@ import type * as TerminalBarrier from './orcad-rollback-terminal-barrier'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type * as RecordFile from './orcad-remote-record-file'
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', () => ({
|
||||
execCommand: vi.fn(),
|
||||
isUnconfirmedSshCommandTermination: () => false
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: vi.fn()
|
||||
}))
|
||||
vi.mock('./ssh-relay-install-lock', () => ({
|
||||
acquireInstallLock: vi.fn().mockResolvedValue(undefined),
|
||||
@@ -49,7 +50,7 @@ vi.mock('./remote-versioned-install', async (importOriginal) => ({
|
||||
abandonInstall: vi.fn().mockResolvedValue(undefined)
|
||||
}))
|
||||
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import { deployOrcad, type OrcadDeployOptions } from './orcad-remote-deploy'
|
||||
import { rollbackOrcad } from './orcad-remote-rollback'
|
||||
import { emptyOrcadActivationRecord } from './orcad-activation-record'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
@@ -14,7 +15,10 @@ import { ORCAD_WINDOWS_HOST_SCRIPT } from './orcad-windows-host-script'
|
||||
import { getRemoteHostPlatform } from './ssh-remote-platform'
|
||||
|
||||
const execCommand = vi.hoisted(() => vi.fn())
|
||||
vi.mock('./ssh-relay-deploy-helpers', () => ({ execCommand }))
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand
|
||||
}))
|
||||
|
||||
const linux = getRemoteHostPlatform('linux-x64')
|
||||
const windows = getRemoteHostPlatform('win32-x64')
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { redactString } from '../observability/redactor'
|
||||
import { shellEscape } from './ssh-connection-utils'
|
||||
import { ORCAD_LOG_FILENAME } from './orcad-remote-host-support'
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import { isWindowsRemoteHost, joinRemotePath, type RemoteHostPlatform } from './ssh-remote-platform'
|
||||
import {
|
||||
orcadWindowsSlotOpCommand,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
@@ -10,10 +11,13 @@ import {
|
||||
REMOTE_NODE_RUNTIME_READY
|
||||
} from './orcad-remote-node-runtime'
|
||||
import { RUNTIME_STORE_LOCK_NAME } from './remote-node-runtime-store-lock'
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import { getRemoteHostPlatform } from './ssh-remote-platform'
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', () => ({ execCommand: vi.fn() }))
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: vi.fn()
|
||||
}))
|
||||
vi.mock('./ssh-relay-install-transfers', () => ({ uploadRelayDirectory: vi.fn(async () => {}) }))
|
||||
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: every remote call goes through the mocked execCommand.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
@@ -13,11 +14,14 @@ import {
|
||||
} from './orcad-remote-node-runtime'
|
||||
import { REMOTE_NODE_RUNTIME_VERIFIED_MARKER } from './orcad-remote-node-runtime-report'
|
||||
import { RUNTIME_STORE_LOCK_NAME, withRuntimeStoreLock } from './remote-node-runtime-store-lock'
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import { uploadRelayDirectory } from './ssh-relay-install-transfers'
|
||||
import { getRemoteHostPlatform } from './ssh-remote-platform'
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', () => ({ execCommand: vi.fn() }))
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: vi.fn()
|
||||
}))
|
||||
vi.mock('./ssh-relay-install-transfers', () => ({ uploadRelayDirectory: vi.fn() }))
|
||||
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: every remote call goes through the mocked execCommand, which runs it in a local shell.
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
import path from 'node:path'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { NODE_RUNTIME_ASSETS, NODE_RUNTIME_PIN } from '../../shared/node-runtime-pin'
|
||||
import type { OrcadHostTransport } from './orcad-host-transport'
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import { uploadRelayDirectory } from './ssh-relay-install-transfers'
|
||||
import {
|
||||
ensureRemoteOrcadNodeRuntime,
|
||||
@@ -22,7 +23,10 @@ import {
|
||||
import { getRemoteHostPlatform } from './ssh-remote-platform'
|
||||
import { decodeRemotePowerShellScript } from './ssh-remote-powershell'
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', () => ({ execCommand: vi.fn() }))
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: vi.fn()
|
||||
}))
|
||||
vi.mock('./ssh-relay-install-transfers', () => ({ uploadRelayDirectory: vi.fn() }))
|
||||
|
||||
const host = getRemoteHostPlatform('win32-x64')
|
||||
|
||||
@@ -24,8 +24,7 @@ import {
|
||||
} from '../../shared/orcad-artifacts'
|
||||
import type { OrcadHostTransport } from './orcad-host-transport'
|
||||
import { shellEscape } from './ssh-connection-utils'
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
|
||||
import { execCommand, isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
|
||||
import { uploadRelayDirectory } from './ssh-relay-install-transfers'
|
||||
import {
|
||||
isWindowsRemoteHost,
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
import { readWindowsOrcadSlotEntry } from './orcad-remote-launch-windows'
|
||||
import { ORCAD_WINDOWS_RUNTIME_MARKER } from './orcad-windows-host-script'
|
||||
import { orcadNodeSlotRuntimeCommand, selectOrcadSlotEntryCommand } from './orcad-remote-runtime'
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import { shellEscape } from './ssh-connection-utils'
|
||||
import { isWindowsRemoteHost, joinRemotePath, type RemoteHostPlatform } from './ssh-remote-platform'
|
||||
import type { OrcadHostTransport } from './orcad-host-transport'
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', () => ({
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: vi.fn(),
|
||||
isUnconfirmedSshCommandTermination: (error: unknown) =>
|
||||
error instanceof Error && error.message === 'channel lost'
|
||||
@@ -9,7 +11,7 @@ vi.mock('./ssh-relay-deploy-helpers', () => ({
|
||||
vi.mock('./ssh-remote-platform-detection', () => ({ detectRemoteHostPlatform: vi.fn() }))
|
||||
vi.mock('./orcad-windows-host-preparation', () => ({ prepareWindowsOrcadHost: vi.fn() }))
|
||||
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import { detectRemoteHostPlatform } from './ssh-remote-platform-detection'
|
||||
import { prepareWindowsOrcadHost } from './orcad-windows-host-preparation'
|
||||
import { resolveOrcadRemoteContext } from './orcad-remote-context'
|
||||
|
||||
@@ -8,7 +8,7 @@ import { randomUUID } from 'node:crypto'
|
||||
import { shellEscape } from './ssh-connection-utils'
|
||||
import { removeRemoteFileCommand } from './ssh-remote-commands'
|
||||
import { execOrcadRemote, type OrcadRemoteExecTarget } from './orcad-remote-runtime-control'
|
||||
import { isUnconfirmedSshCommandTermination } from './ssh-relay-deploy-helpers'
|
||||
import { isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
|
||||
import { isWindowsRemoteHost } from './ssh-remote-platform'
|
||||
import {
|
||||
orcadRemoteBaseDir,
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type * as RecordFile from './orcad-remote-record-file'
|
||||
import type * as InstallLock from './ssh-relay-install-lock'
|
||||
import type * as TerminalBarrier from './orcad-rollback-terminal-barrier'
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', () => ({
|
||||
execCommand: vi.fn(),
|
||||
isUnconfirmedSshCommandTermination: () => false
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: vi.fn()
|
||||
}))
|
||||
vi.mock('./ssh-connection-utils', () => ({ shellEscape: (s: string) => `'${s}'` }))
|
||||
vi.mock('./ssh-relay-install-transfers', () => ({
|
||||
@@ -51,7 +52,7 @@ vi.mock('./orcad-rollback-terminal-barrier', async (importOriginal) => ({
|
||||
}
|
||||
}))
|
||||
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import { writeAtomicOrcadRemoteRecord } from './orcad-remote-record-file'
|
||||
import { rollbackOrcad, type OrcadRollbackOptions } from './orcad-remote-rollback'
|
||||
import { emptyOrcadActivationRecord, type OrcadActivationRecord } from './orcad-activation-record'
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', () => ({
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: vi.fn(),
|
||||
isUnconfirmedSshCommandTermination: (error: unknown) =>
|
||||
error instanceof Error && error.message === 'unconfirmed'
|
||||
}))
|
||||
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import { launchOrcadAndAwaitReadiness } from './orcad-remote-runtime-control'
|
||||
import { getRemoteHostPlatform } from './ssh-remote-platform'
|
||||
import type { OrcadLaunchSpec } from './orcad-remote-launch'
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
} from './orcad-remote-readiness-wait'
|
||||
import type { OrcadHostTransport } from './orcad-host-transport'
|
||||
import { errorMessage } from '../../shared/error-message'
|
||||
import { execCommand, isUnconfirmedSshCommandTermination } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand, isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
|
||||
import {
|
||||
currentOrcadFence,
|
||||
isOrcadFenceLost,
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', () => ({
|
||||
execCommand: vi.fn(),
|
||||
isUnconfirmedSshCommandTermination: () => false
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: vi.fn()
|
||||
}))
|
||||
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import type { OrcadHostTransport } from './orcad-host-transport'
|
||||
import { getRemoteHostPlatform } from './ssh-remote-platform'
|
||||
import { orcadLivenessProbeCommand, type OrcadLaunchSpec } from './orcad-remote-launch'
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
* a staged managed request run by the slot's node.exe, and GC screens every candidate in one
|
||||
* node.exe instead of one per version dir.
|
||||
*/
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type * as RecordFile from './orcad-remote-record-file'
|
||||
import type * as InstallLock from './ssh-relay-install-lock'
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', () => ({
|
||||
execCommand: vi.fn(),
|
||||
isUnconfirmedSshCommandTermination: () => false
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: vi.fn()
|
||||
}))
|
||||
vi.mock('./ssh-relay-install-lock', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof InstallLock>()),
|
||||
@@ -24,7 +25,7 @@ vi.mock('./remote-install-gc', async (importOriginal) => ({
|
||||
gcOldRemoteInstallVersions: vi.fn().mockResolvedValue(undefined)
|
||||
}))
|
||||
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import { writeAtomicOrcadRemoteRecord } from './orcad-remote-record-file'
|
||||
import { gcOldRemoteInstallVersions } from './remote-install-gc'
|
||||
import { decommissionRemoteOrcad } from './orcad-remote-decommission'
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
import { pinnedNodeRuntimeAsset, type NodeRuntimeTarget } from '../../shared/node-runtime-pin'
|
||||
import { randomUUID } from 'node:crypto'
|
||||
import type { OrcadHostTransport } from './orcad-host-transport'
|
||||
import { execCommand, isUnconfirmedSshCommandTermination } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand, isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
|
||||
import { RELAY_REMOTE_DIR } from './relay-protocol'
|
||||
import { joinRemotePath, remoteDirname, type RemoteHostPlatform } from './ssh-remote-platform'
|
||||
import { powerShellLiteral } from './ssh-remote-powershell'
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { orcadRemoteBaseDir } from './orcad-remote-windows-node'
|
||||
import { randomUUID } from 'node:crypto'
|
||||
import type { OrcadRollbackOptions, OrcadRollbackResult } from './orcad-remote-rollback'
|
||||
import { isUnconfirmedSshCommandTermination } from './ssh-relay-deploy-helpers'
|
||||
import { isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
|
||||
import { withRolledBackVersion } from './orcad-activation-record'
|
||||
import { writeOrcadActivationRecord } from './orcad-activation-record-store'
|
||||
import { launchAndJudgeOrcadSlot } from './orcad-candidate-launch-verdict'
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { resolveOrcadRuntimeTarget } from './orcad-runtime-target'
|
||||
import { SshConnection } from './ssh-connection'
|
||||
import { createCallbacks, createTarget } from './ssh-connection-test-fixtures'
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import { getRemoteHostPlatform } from './ssh-remote-platform'
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', () => ({ execCommand: vi.fn() }))
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: vi.fn()
|
||||
}))
|
||||
beforeEach(() => vi.mocked(execCommand).mockReset())
|
||||
|
||||
function resolveOn(platform: Parameters<typeof getRemoteHostPlatform>[0], ldd: string) {
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
ORCAD_STATE_MUTATION_DEADLINE
|
||||
} from './orcad-state-snapshot-members'
|
||||
import type { OrcadRemoteExecTarget } from './orcad-remote-runtime-control'
|
||||
import { execCommand, isUnconfirmedSshCommandTermination } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand, isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
|
||||
import { isOrcadFenceLost, OrcadFenceLostError } from './orcad-activation-fence-scope'
|
||||
|
||||
// Longer than the host's own deadline, so the host kills the work before the client gives up.
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
* command shape) runs beside it, as the two restores did on a real host.
|
||||
*/
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type * as DeployHelpers from './ssh-relay-deploy-helpers'
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
import type * as RecordFile from './orcad-remote-record-file'
|
||||
import type * as InstallLock from './ssh-relay-install-lock'
|
||||
import type * as VersionedInstall from './remote-versioned-install'
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof DeployHelpers>()),
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: vi.fn()
|
||||
}))
|
||||
vi.mock('./ssh-connection-utils', () => ({ shellEscape: (s: string) => `'${s}'` }))
|
||||
@@ -33,14 +33,17 @@ vi.mock('./orcad-local-build-hash', () => ({
|
||||
computeLocalOrcadBuildHash: () => 'abc123def4567890'
|
||||
}))
|
||||
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import {
|
||||
execCommand,
|
||||
SSH_EXEC_TIMEOUT_CODE,
|
||||
isUnconfirmedSshCommandTermination
|
||||
} from './ssh-relay-exec-command'
|
||||
import { acquireInstallLock } from './ssh-relay-install-lock'
|
||||
import { writeAtomicOrcadRemoteRecord } from './orcad-remote-record-file'
|
||||
import { deployOrcad } from './orcad-remote-deploy'
|
||||
import { rollbackOrcad } from './orcad-remote-rollback'
|
||||
import { recoverInterruptedOrcadActivation } from './orcad-activation-recovery'
|
||||
import { getRemoteHostPlatform } from './ssh-remote-platform'
|
||||
import { SSH_EXEC_TIMEOUT_CODE, isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
|
||||
import type { OrcadHostTransport } from './orcad-host-transport'
|
||||
import { BUILD_HASH, FakeOrcadHost, NEW, OLD } from './orcad-activation-host-test-harness'
|
||||
import { isSnapshotCaptureCommand } from './orcad-snapshot-capture-command'
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type * as DeployHelpers from './ssh-relay-deploy-helpers'
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof DeployHelpers>()),
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: vi.fn()
|
||||
}))
|
||||
|
||||
import type { SshConnection } from './ssh-connection'
|
||||
import { gcOldRemoteInstallVersions } from './remote-install-gc'
|
||||
import { ORCAD_INSTALL_MODEL } from './remote-install-model'
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import { getRemoteHostPlatform } from './ssh-remote-platform'
|
||||
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: all connection access is replaced by execCommand's mock.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { RemoteInstallTransport } from './remote-install-transport'
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
|
||||
import { execCommand, isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
|
||||
import { remoteInstallGcTombstoneRegex, type RemoteInstallModel } from './remote-install-model'
|
||||
import { removeRemoteTreeCommand } from './ssh-remote-commands'
|
||||
import {
|
||||
|
||||
@@ -13,10 +13,10 @@ import { once } from 'node:events'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type * as DeployHelpers from './ssh-relay-deploy-helpers'
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof DeployHelpers>()),
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: vi.fn()
|
||||
}))
|
||||
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
runtimeStoreInventoryCommand,
|
||||
type RuntimeStoreInventory
|
||||
} from './remote-node-runtime-store-inventory'
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import { getRemoteHostPlatform } from './ssh-remote-platform'
|
||||
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: all connection access is replaced by execCommand's mock.
|
||||
|
||||
@@ -28,9 +28,8 @@ import {
|
||||
tryWithRuntimeStoreLock
|
||||
} from './remote-node-runtime-store-lock'
|
||||
import { shellEscape } from './ssh-connection-utils'
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand, isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
|
||||
import { INSTALL_LOCK_STALE_MS } from './ssh-relay-install-lock'
|
||||
import { isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
|
||||
import {
|
||||
moveRemoteTreeCommand,
|
||||
removeRemoteTreeCommand,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
@@ -9,11 +10,14 @@ import {
|
||||
RUNTIME_STORE_LOCK_OWNER_FILENAME,
|
||||
withRuntimeStoreLock
|
||||
} from './remote-node-runtime-store-lock'
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import { tryCreateInstallLockCommand } from './ssh-relay-install-lock-commands'
|
||||
import { getRemoteHostPlatform } from './ssh-remote-platform'
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', () => ({ execCommand: vi.fn() }))
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: vi.fn()
|
||||
}))
|
||||
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: every remote call goes through the mocked execCommand, which runs it in a local shell.
|
||||
const conn = {} as SshConnection
|
||||
|
||||
@@ -3,10 +3,10 @@ import { copyFileSync, mkdirSync, mkdtempSync, utimesSync, writeFileSync } from
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type * as DeployHelpers from './ssh-relay-deploy-helpers'
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof DeployHelpers>()),
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: vi.fn()
|
||||
}))
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
windowsRuntimeStoreInventoryCommand,
|
||||
windowsSweepStaleRuntimeStagesCommand
|
||||
} from './remote-node-runtime-store-windows'
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import { getRemoteHostPlatform } from './ssh-remote-platform'
|
||||
import { decodeRemotePowerShellScript } from './ssh-remote-powershell'
|
||||
import { removeTreeSync } from '../../shared/windows-transient-lock-removal'
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type * as DeployHelpers from './ssh-relay-deploy-helpers'
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
|
||||
vi.mock('fs', () => ({
|
||||
existsSync: vi.fn(),
|
||||
readFileSync: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof DeployHelpers>()),
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: vi.fn()
|
||||
}))
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
} from './remote-versioned-install'
|
||||
import { gcOldRemoteInstallVersions } from './remote-install-gc'
|
||||
import { ORCAD_INSTALL_MODEL } from './remote-install-model'
|
||||
import { isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
|
||||
import { isUnconfirmedSshCommandTermination, execCommand } from './ssh-relay-exec-command'
|
||||
import { acquireInstallLock } from './ssh-relay-install-lock'
|
||||
import {
|
||||
isRelayGcClaimed,
|
||||
@@ -33,7 +33,6 @@ import {
|
||||
tryAcquireRelayGcClaim,
|
||||
waitForRelayGcClaimRelease
|
||||
} from './ssh-relay-gc-claim'
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { getRemoteHostPlatform, type RemoteHostPlatform } from './ssh-remote-platform'
|
||||
import type { SshConnection } from './ssh-connection'
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ export async function uploadSshDirectory(
|
||||
): Promise<void> {
|
||||
await withLinkedSftpTransfer(host, remoteDir, options, {
|
||||
sftp: async (sftp, targetDir, signal) => {
|
||||
const { uploadDirectory } = await import('./ssh-relay-deploy-helpers')
|
||||
const { uploadDirectory } = await import('./sftp-upload')
|
||||
await uploadDirectory(sftp, localDir, targetDir, localDir, { signal })
|
||||
},
|
||||
system: (signal) =>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { SshConnection } from './ssh-connection'
|
||||
import {
|
||||
@@ -12,7 +13,10 @@ const { execCommand, probeRelayEndpointIncumbent, countRelayEndpointPtys } = vi.
|
||||
countRelayEndpointPtys: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', () => ({ execCommand }))
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand
|
||||
}))
|
||||
vi.mock('./ssh-relay-endpoint-pty-count', () => ({ countRelayEndpointPtys }))
|
||||
vi.mock('./ssh-relay-endpoint-incumbent', async (importOriginal) => ({
|
||||
...(await importOriginal<Record<string, unknown>>()),
|
||||
|
||||
@@ -10,7 +10,7 @@ import type { SshConnection } from './ssh-connection'
|
||||
import { shellEscape } from './ssh-connection-utils'
|
||||
import { RELAY_REMOTE_DIR } from './relay-protocol'
|
||||
import { SHORT_RELAY_SOCKET_DIR_PREFIX } from './relay-socket-path-limit'
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import {
|
||||
isReapableRelayHusk,
|
||||
probeRelayEndpointIncumbent,
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from './ssh-host-relay-endpoint-census'
|
||||
import type { SshConnection } from './ssh-connection'
|
||||
import { censusWindowsHostRelays, windowsCensusNodePath } from './ssh-host-relay-windows-census'
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import { readRemoteHomeCommand } from './ssh-remote-commands'
|
||||
import { resolveRemoteNodePath } from './ssh-remote-node-resolution'
|
||||
import { detectRemoteHostPlatform } from './ssh-remote-platform-detection'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { SshConnection } from './ssh-connection'
|
||||
import { relaySocketNameForInstanceId } from './ssh-relay-instance-id'
|
||||
@@ -9,7 +10,10 @@ const { execCommand, countRelayPtysOverBridge } = vi.hoisted(() => ({
|
||||
countRelayPtysOverBridge: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', () => ({ execCommand }))
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand
|
||||
}))
|
||||
vi.mock('./ssh-relay-endpoint-pty-count', () => ({ countRelayPtysOverBridge }))
|
||||
vi.mock('./ssh-remote-commands', async (importOriginal) => ({
|
||||
...(await importOriginal<Record<string, unknown>>()),
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
windowsRelayInventoryCommand
|
||||
} from './ssh-host-relay-windows-inventory'
|
||||
import { countRelayPtysOverBridge } from './ssh-relay-endpoint-pty-count'
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import { relaySocketNameForInstanceId } from './ssh-relay-instance-id'
|
||||
import { windowsRelayConnectCommand } from './ssh-relay-windows-launch-command'
|
||||
import { listRemoteInstallBaseDirsCommand } from './ssh-remote-commands'
|
||||
|
||||
@@ -155,7 +155,7 @@ describe('connect-decision telemetry', () => {
|
||||
})
|
||||
})
|
||||
await expect(resolveHostServerOnConnect(target, d)).resolves.toEqual({
|
||||
route: 'relay',
|
||||
route: 'unserved',
|
||||
reason: 'deferred',
|
||||
detail: reason
|
||||
})
|
||||
|
||||
@@ -67,7 +67,7 @@ describe('which server an SSH host runs on connect', () => {
|
||||
for (const verdict of ['live', 'unverifiable'] as const) {
|
||||
const d = deps({ relayTerminals: async () => ({ verdict, count: 2 }) })
|
||||
await expect(resolveHostServerOnConnect(target, d)).resolves.toEqual({
|
||||
route: 'relay',
|
||||
route: 'unserved',
|
||||
reason: verdict === 'live' ? 'relay_terminals_live' : 'relay_terminals_unverifiable',
|
||||
terminals: 2
|
||||
})
|
||||
@@ -77,7 +77,7 @@ describe('which server an SSH host runs on connect', () => {
|
||||
relayTerminals: async () => ({ verdict: 'live', count: 1, elsewhere: true })
|
||||
})
|
||||
await expect(resolveHostServerOnConnect(target, elsewhere)).resolves.toEqual({
|
||||
route: 'relay',
|
||||
route: 'unserved',
|
||||
reason: 'relay_terminals_live',
|
||||
terminals: 1,
|
||||
terminalsElsewhere: true
|
||||
@@ -91,7 +91,7 @@ describe('which server an SSH host runs on connect', () => {
|
||||
})
|
||||
})
|
||||
await expect(resolveHostServerOnConnect(target, raced)).resolves.toEqual({
|
||||
route: 'relay',
|
||||
route: 'unserved',
|
||||
reason: 'relay_terminals_live'
|
||||
})
|
||||
})
|
||||
@@ -118,7 +118,7 @@ describe('which server an SSH host runs on connect', () => {
|
||||
})
|
||||
})
|
||||
await expect(resolveHostServerOnConnect(target, d)).resolves.toEqual({
|
||||
route: 'relay',
|
||||
route: 'unserved',
|
||||
reason: 'refused',
|
||||
detail: 'An automation still runs on this host.'
|
||||
})
|
||||
@@ -132,7 +132,7 @@ describe('which server an SSH host runs on connect', () => {
|
||||
}
|
||||
})
|
||||
await expect(resolveHostServerOnConnect(target, d)).resolves.toEqual({
|
||||
route: 'relay',
|
||||
route: 'unserved',
|
||||
reason: 'orcad_unavailable',
|
||||
detail: 'unsupported_host'
|
||||
})
|
||||
@@ -173,7 +173,7 @@ describe('which server an SSH host runs on connect', () => {
|
||||
},
|
||||
changed
|
||||
)
|
||||
).resolves.toEqual({ route: 'relay', reason: 'source_changed' })
|
||||
).resolves.toEqual({ route: 'unserved', reason: 'source_changed' })
|
||||
expect(changed.ensureTunnel).not.toHaveBeenCalled()
|
||||
|
||||
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
|
||||
@@ -194,7 +194,7 @@ describe('which server an SSH host runs on connect', () => {
|
||||
it('touches no host when this build carries no orcad template', async () => {
|
||||
const d = deps({ hasTemplate: () => false, isEmptyHost: () => true })
|
||||
await expect(resolveHostServerOnConnect(target, d)).resolves.toEqual({
|
||||
route: 'relay',
|
||||
route: 'unserved',
|
||||
reason: 'orcad_unavailable',
|
||||
detail: 'artifacts_unavailable'
|
||||
})
|
||||
@@ -209,7 +209,7 @@ describe('which server an SSH host runs on connect', () => {
|
||||
}
|
||||
})
|
||||
await expect(resolveHostServerOnConnect(target, d)).resolves.toEqual({
|
||||
route: 'relay',
|
||||
route: 'unserved',
|
||||
reason: 'failed',
|
||||
detail: 'Connection lost'
|
||||
})
|
||||
@@ -226,7 +226,7 @@ describe('which server an SSH host runs on connect', () => {
|
||||
})
|
||||
})
|
||||
await expect(resolveHostServerOnConnect(target, d)).resolves.toEqual({
|
||||
route: 'relay',
|
||||
route: 'unserved',
|
||||
reason: 'orcad_unavailable',
|
||||
detail: 'ssh_tunnel_unavailable'
|
||||
})
|
||||
@@ -244,7 +244,7 @@ describe('which server an SSH host runs on connect', () => {
|
||||
})
|
||||
})
|
||||
await expect(resolveHostServerOnConnect(target, d)).resolves.toEqual({
|
||||
route: 'relay',
|
||||
route: 'unserved',
|
||||
reason: 'orcad_unavailable',
|
||||
detail: 'ssh_tunnel_unavailable'
|
||||
})
|
||||
@@ -285,7 +285,7 @@ describe('which server an SSH host runs on connect', () => {
|
||||
})
|
||||
})
|
||||
await expect(resolveHostServerOnConnect(target, d)).resolves.toMatchObject({
|
||||
route: 'relay',
|
||||
route: 'unserved',
|
||||
reason: 'failed',
|
||||
detail: 'orcad_migration_source_changed'
|
||||
})
|
||||
|
||||
@@ -52,7 +52,7 @@ export type HostServerOnConnectResult =
|
||||
fenceHeld?: true
|
||||
}
|
||||
| {
|
||||
route: 'relay'
|
||||
route: 'unserved'
|
||||
reason: SshManagedServerRelayReason
|
||||
/** A blocker the user must act on, or why orcad can't run on this host. */
|
||||
detail?: string
|
||||
@@ -129,7 +129,7 @@ async function decide(
|
||||
trace: HostServerDecisionTrace
|
||||
): Promise<HostServerOnConnectResult> {
|
||||
if (target.orcadFence?.sourceChangedAt) {
|
||||
return { route: 'relay', reason: 'source_changed' }
|
||||
return { route: 'unserved', reason: 'source_changed' }
|
||||
}
|
||||
const existing = deps.managedEnvironmentId(target)
|
||||
if (existing) {
|
||||
@@ -146,7 +146,7 @@ async function decide(
|
||||
await deps.releaseUnreachableSetup(target)
|
||||
deps.recordUnavailable(target, ORCAD_TUNNEL_UNAVAILABLE_REASON)
|
||||
return {
|
||||
route: 'relay',
|
||||
route: 'unserved',
|
||||
reason: 'orcad_unavailable',
|
||||
detail: ORCAD_TUNNEL_UNAVAILABLE_REASON
|
||||
}
|
||||
@@ -177,10 +177,10 @@ async function decide(
|
||||
const recorded = deps.recordedUnavailable(target)
|
||||
if (recorded) {
|
||||
trace.recorded = true
|
||||
return { route: 'relay', reason: 'orcad_unavailable', detail: recorded }
|
||||
return { route: 'unserved', reason: 'orcad_unavailable', detail: recorded }
|
||||
}
|
||||
if (!deps.hasTemplate()) {
|
||||
return { route: 'relay', reason: 'orcad_unavailable', detail: 'artifacts_unavailable' }
|
||||
return { route: 'unserved', reason: 'orcad_unavailable', detail: 'artifacts_unavailable' }
|
||||
}
|
||||
if (deps.isEmptyHost(target)) {
|
||||
try {
|
||||
@@ -207,7 +207,7 @@ async function convertHost(
|
||||
if (terminals && terminals.verdict !== 'exited' && !terminals.bridged) {
|
||||
// Why unverifiable too: loss of contact is never evidence that a relay terminal exited.
|
||||
return {
|
||||
route: 'relay',
|
||||
route: 'unserved',
|
||||
reason: terminalReason(terminals.verdict),
|
||||
terminals: terminals.count,
|
||||
...(terminals.elsewhere ? { terminalsElsewhere: true } : {})
|
||||
@@ -269,9 +269,9 @@ async function afterConversion(
|
||||
return deferred(target, deps, result, false)
|
||||
case 'refused':
|
||||
if (result.code === 'orcad_migration_terminals') {
|
||||
return { route: 'relay', reason: terminalReason(result.verdict) }
|
||||
return { route: 'unserved', reason: terminalReason(result.verdict) }
|
||||
}
|
||||
return { route: 'relay', reason: 'refused', detail: result.reason }
|
||||
return { route: 'unserved', reason: 'refused', detail: result.reason }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,10 +306,10 @@ async function unfinished(
|
||||
}
|
||||
if (!unavailable) {
|
||||
// Shown under the SSH host, with the host's orcad.log tail when the failure carries one.
|
||||
return { route: 'relay', reason, detail: boundedDetail(failure) }
|
||||
return { route: 'unserved', reason, detail: boundedDetail(failure) }
|
||||
}
|
||||
deps.recordUnavailable(target, unavailable)
|
||||
return { route: 'relay', reason: 'orcad_unavailable', detail: unavailable }
|
||||
return { route: 'unserved', reason: 'orcad_unavailable', detail: unavailable }
|
||||
}
|
||||
|
||||
function boundedDetail(text: string): string {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { SshManagedServerStatus } from '../../shared/ssh-types'
|
||||
import type { HostServerOnConnectResult } from './ssh-host-server-on-connect'
|
||||
|
||||
type UnservedDecision = Extract<HostServerOnConnectResult, { route: 'relay' }>
|
||||
type UnservedDecision = Extract<HostServerOnConnectResult, { route: 'unserved' }>
|
||||
|
||||
// Why `kind: 'relay'`: the published status keeps its shipped shape; it now means "not served".
|
||||
export function unservedServerStatus(
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { RELAY_LSOF_PROBE_JS } from '@orca/process-host/posix-lsof-probe'
|
||||
|
||||
const execCommand = vi.fn()
|
||||
vi.mock('./ssh-relay-deploy-helpers', () => ({
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: (...args: unknown[]) => execCommand(...args),
|
||||
isUnconfirmedSshCommandTermination: (error: unknown) =>
|
||||
(error as { sshChannelCloseConfirmed?: boolean } | null)?.sshChannelCloseConfirmed === false
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
RELAY_UNRECOGNIZED_CHILD_COUNT_VAR,
|
||||
relayDaemonChildCensusShell
|
||||
} from './relay-daemon-service-children'
|
||||
import { execCommand, isUnconfirmedSshCommandTermination } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand, isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
|
||||
import { isWindowsRemoteHost, type RemoteHostPlatform } from './ssh-remote-platform'
|
||||
|
||||
type RelayEndpointVerdict = 'live' | 'unverifiable' | 'exited'
|
||||
|
||||
@@ -7,7 +7,7 @@ const { waitForSentinel, request, dispose } = vi.hoisted(() => ({
|
||||
dispose: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', () => ({ waitForSentinel }))
|
||||
vi.mock('./ssh-relay-sentinel', () => ({ waitForSentinel }))
|
||||
vi.mock('./ssh-channel-multiplexer', () => ({
|
||||
SshChannelMultiplexer: class {
|
||||
request = request
|
||||
|
||||
@@ -10,7 +10,7 @@ import type { SshConnection } from './ssh-connection'
|
||||
import { SshChannelMultiplexer } from './ssh-channel-multiplexer'
|
||||
import { shellEscape } from './ssh-connection-utils'
|
||||
import { SHORT_RELAY_SOCKET_DIR_PREFIX } from './relay-socket-path-limit'
|
||||
import { waitForSentinel } from './ssh-relay-deploy-helpers'
|
||||
import { waitForSentinel } from './ssh-relay-sentinel'
|
||||
|
||||
const RELAY_PTY_COUNT_TIMEOUT_MS = 10_000
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* host with no PATH Node from reading every relay as unverifiable.
|
||||
*/
|
||||
import type { SshConnection } from './ssh-connection'
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
|
||||
const RELAY_DAEMON_MARKER = ' relay.js --detached '
|
||||
|
||||
|
||||
+1
-233
@@ -1,16 +1,7 @@
|
||||
import { EventEmitter } from 'node:events'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import type { ClientChannel } from 'ssh2'
|
||||
import { execCommand, waitForSentinel } from './ssh-relay-deploy-helpers'
|
||||
import { RELAY_SENTINEL } from './relay-protocol'
|
||||
import {
|
||||
RelayVersionMismatchError,
|
||||
RELAY_EXIT_CODE_VERSION_MISMATCH
|
||||
} from './ssh-relay-version-mismatch-error'
|
||||
import {
|
||||
RelayCredentialMismatchError,
|
||||
RELAY_EXIT_CODE_CREDENTIAL_MISMATCH
|
||||
} from './ssh-relay-credential-mismatch-error'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
|
||||
type MockChannel = ClientChannel & {
|
||||
stdin: EventEmitter & { write: ReturnType<typeof vi.fn> }
|
||||
@@ -36,229 +27,6 @@ async function execCommandRejection(promise: Promise<string>): Promise<Error> {
|
||||
)
|
||||
}
|
||||
|
||||
describe('waitForSentinel', () => {
|
||||
it('closes and rejects with AbortError while clearing startup resources on abort', async () => {
|
||||
vi.useFakeTimers()
|
||||
try {
|
||||
const channel = createMockChannel()
|
||||
const controller = new AbortController()
|
||||
const removeListener = vi.spyOn(controller.signal, 'removeEventListener')
|
||||
const transportPromise = waitForSentinel(channel, controller.signal)
|
||||
|
||||
expect(vi.getTimerCount()).toBe(1)
|
||||
controller.abort()
|
||||
|
||||
await expect(transportPromise).rejects.toMatchObject({ name: 'AbortError' })
|
||||
expect(channel.close).toHaveBeenCalledTimes(1)
|
||||
expect(removeListener).toHaveBeenCalledWith('abort', expect.any(Function))
|
||||
expect(vi.getTimerCount()).toBe(0)
|
||||
} finally {
|
||||
vi.useRealTimers()
|
||||
}
|
||||
})
|
||||
|
||||
it('removes the abort listener and timers when startup fails before the sentinel', async () => {
|
||||
vi.useFakeTimers()
|
||||
try {
|
||||
const channel = createMockChannel()
|
||||
const controller = new AbortController()
|
||||
const removeListener = vi.spyOn(controller.signal, 'removeEventListener')
|
||||
const transportPromise = waitForSentinel(channel, controller.signal)
|
||||
|
||||
channel.emit('error', new Error('remote host rebooted'))
|
||||
|
||||
await expect(transportPromise).rejects.toThrow('remote host rebooted')
|
||||
expect(removeListener).toHaveBeenCalledWith('abort', expect.any(Function))
|
||||
expect(vi.getTimerCount()).toBe(0)
|
||||
} finally {
|
||||
vi.useRealTimers()
|
||||
}
|
||||
})
|
||||
|
||||
it('buffers post-sentinel chunks until the transport subscribes', async () => {
|
||||
const channel = createMockChannel()
|
||||
const transportPromise = waitForSentinel(channel)
|
||||
|
||||
channel.emit('data', Buffer.from(RELAY_SENTINEL))
|
||||
channel.emit('data', Buffer.from('first-frame-after-sentinel'))
|
||||
|
||||
const transport = await transportPromise
|
||||
const chunks: string[] = []
|
||||
transport.onData((chunk) => chunks.push(chunk.toString('utf-8')))
|
||||
|
||||
expect(chunks).toEqual(['first-frame-after-sentinel'])
|
||||
})
|
||||
|
||||
it('buffers post-sentinel bytes from the sentinel chunk', async () => {
|
||||
const channel = createMockChannel()
|
||||
const transportPromise = waitForSentinel(channel)
|
||||
|
||||
channel.emit('data', Buffer.from(`${RELAY_SENTINEL}same-chunk-frame`))
|
||||
|
||||
const transport = await transportPromise
|
||||
const chunks: string[] = []
|
||||
transport.onData((chunk) => chunks.push(chunk.toString('utf-8')))
|
||||
|
||||
expect(chunks).toEqual(['same-chunk-frame'])
|
||||
})
|
||||
|
||||
it('rejects on relay channel errors before the sentinel instead of emitting uncaught errors', async () => {
|
||||
const channel = createMockChannel()
|
||||
const transportPromise = waitForSentinel(channel)
|
||||
|
||||
expect(() => channel.emit('error', new Error('remote host rebooted'))).not.toThrow()
|
||||
await expect(transportPromise).rejects.toThrow('remote host rebooted')
|
||||
})
|
||||
|
||||
it('notifies transport close subscribers on relay channel errors after the sentinel', async () => {
|
||||
const channel = createMockChannel()
|
||||
const transportPromise = waitForSentinel(channel)
|
||||
|
||||
channel.emit('data', Buffer.from(RELAY_SENTINEL))
|
||||
const transport = await transportPromise
|
||||
const onClose = vi.fn()
|
||||
transport.onClose(onClose)
|
||||
|
||||
expect(() => channel.emit('error', new Error('remote host rebooted'))).not.toThrow()
|
||||
expect(onClose).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('translates a pre-sentinel exit-42 + close into RelayVersionMismatchError', async () => {
|
||||
const channel = createMockChannel()
|
||||
const transportPromise = waitForSentinel(channel)
|
||||
|
||||
channel.stderr.emit(
|
||||
'data',
|
||||
Buffer.from(
|
||||
'[relay-connect] Handshake mismatch: expected=0.1.0+aaa, daemon=0.1.0+bbb; exiting 42\n'
|
||||
)
|
||||
)
|
||||
channel.emit('exit', RELAY_EXIT_CODE_VERSION_MISMATCH)
|
||||
channel.emit('close')
|
||||
|
||||
await expect(transportPromise).rejects.toBeInstanceOf(RelayVersionMismatchError)
|
||||
await transportPromise.catch((err: RelayVersionMismatchError) => {
|
||||
expect(err.expected).toBe('0.1.0+aaa')
|
||||
expect(err.got).toBe('0.1.0+bbb')
|
||||
})
|
||||
})
|
||||
|
||||
it('translates a pre-sentinel exit-42 even when the version detail is missing', async () => {
|
||||
const channel = createMockChannel()
|
||||
const transportPromise = waitForSentinel(channel)
|
||||
|
||||
channel.stderr.emit('data', Buffer.from('boom\n'))
|
||||
channel.emit('exit', RELAY_EXIT_CODE_VERSION_MISMATCH)
|
||||
channel.emit('close')
|
||||
|
||||
await expect(transportPromise).rejects.toBeInstanceOf(RelayVersionMismatchError)
|
||||
})
|
||||
|
||||
it('translates a pre-sentinel exit-43 + close into RelayCredentialMismatchError', async () => {
|
||||
const channel = createMockChannel()
|
||||
const transportPromise = waitForSentinel(channel)
|
||||
|
||||
channel.stderr.emit(
|
||||
'data',
|
||||
Buffer.from('[relay-connect] Endpoint credential refused by daemon; exiting 43\n')
|
||||
)
|
||||
channel.emit('exit', RELAY_EXIT_CODE_CREDENTIAL_MISMATCH)
|
||||
channel.emit('close')
|
||||
|
||||
await expect(transportPromise).rejects.toBeInstanceOf(RelayCredentialMismatchError)
|
||||
await transportPromise.catch((err: unknown) => {
|
||||
expect(err).not.toBeInstanceOf(RelayVersionMismatchError)
|
||||
})
|
||||
})
|
||||
|
||||
it('rejects with a generic error (not RelayVersionMismatchError) on a non-42 exit code', async () => {
|
||||
const channel = createMockChannel()
|
||||
const transportPromise = waitForSentinel(channel)
|
||||
|
||||
channel.stderr.emit('data', Buffer.from('node: bad bytecode\n'))
|
||||
channel.emit('exit', 1)
|
||||
channel.emit('close')
|
||||
|
||||
await expect(transportPromise).rejects.toThrow(/Relay process exited before ready/)
|
||||
await transportPromise.catch((err: unknown) => {
|
||||
expect(err).not.toBeInstanceOf(RelayVersionMismatchError)
|
||||
})
|
||||
})
|
||||
|
||||
it('rejects and closes the channel when pre-sentinel stdout exceeds the startup buffer cap', async () => {
|
||||
const channel = createMockChannel()
|
||||
const transportPromise = waitForSentinel(channel)
|
||||
|
||||
channel.emit('data', Buffer.alloc(65 * 1024, 'a'))
|
||||
|
||||
const outcome = await Promise.race([
|
||||
transportPromise.then(
|
||||
() => 'resolved',
|
||||
(err: Error) => `rejected:${err.message}`
|
||||
),
|
||||
new Promise<string>((resolve) => setTimeout(() => resolve('pending'), 0))
|
||||
])
|
||||
if (outcome === 'pending') {
|
||||
channel.emit('close')
|
||||
await transportPromise.catch(() => {})
|
||||
}
|
||||
|
||||
expect(outcome).toContain('Relay startup output exceeded')
|
||||
expect(channel.close).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('does not count same-chunk post-sentinel payload against the startup buffer cap', async () => {
|
||||
const channel = createMockChannel()
|
||||
const transportPromise = waitForSentinel(channel)
|
||||
const postSentinelPayload = Buffer.alloc(70 * 1024, 'b')
|
||||
|
||||
channel.emit(
|
||||
'data',
|
||||
Buffer.concat([
|
||||
Buffer.alloc(64 * 1024, 'a'),
|
||||
Buffer.from(RELAY_SENTINEL),
|
||||
postSentinelPayload
|
||||
])
|
||||
)
|
||||
|
||||
const transport = await transportPromise
|
||||
const chunks: Buffer[] = []
|
||||
transport.onData((chunk) => chunks.push(chunk))
|
||||
|
||||
expect(Buffer.concat(chunks)).toEqual(postSentinelPayload)
|
||||
expect(channel.close).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it.each(['ssh2 channel', 'system-SSH child stdio'])(
|
||||
'forwards write(false), callback settlement, and drain for a %s',
|
||||
async (channelKind) => {
|
||||
const channel = createMockChannel()
|
||||
if (channelKind.startsWith('system')) {
|
||||
Object.assign(channel, { _process: new EventEmitter() })
|
||||
}
|
||||
const callback = vi.fn()
|
||||
const drain = vi.fn()
|
||||
channel.stdin.write.mockImplementation((...args: unknown[]) => {
|
||||
const onWritten = args.find((arg) => typeof arg === 'function') as (
|
||||
error?: Error | null
|
||||
) => void
|
||||
onWritten(null)
|
||||
return false
|
||||
})
|
||||
const transportPromise = waitForSentinel(channel)
|
||||
channel.emit('data', Buffer.from(RELAY_SENTINEL))
|
||||
const transport = await transportPromise
|
||||
|
||||
transport.onDrain?.(drain)
|
||||
expect(transport.write(Buffer.from('frame'), callback)).toBe(false)
|
||||
expect(callback).toHaveBeenCalledWith({ ok: true })
|
||||
channel.stdin.emit('drain')
|
||||
expect(drain).toHaveBeenCalledOnce()
|
||||
expect(transport.supportsWriteSettlement).toBe(true)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
describe('execCommand', () => {
|
||||
const installMarkerToken = 'a'.repeat(32)
|
||||
const installMarkerPath = `/home/u/.install-lock/.sftp-namespace-${installMarkerToken}`
|
||||
@@ -1,9 +1,13 @@
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', () => ({ execCommand: vi.fn() }))
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: vi.fn()
|
||||
}))
|
||||
|
||||
import type { SshConnection } from './ssh-connection'
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import {
|
||||
isRelayGcClaimOwned,
|
||||
releaseRelayGcClaim,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { RemoteInstallTransport } from './remote-install-transport'
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import type { RemoteHostPlatform } from './ssh-remote-platform'
|
||||
|
||||
export function execHostCommand(
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
import { mkdtempSync, writeFileSync, rmSync, readFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { createServer } from 'node:net'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { runProcess } from '@orca/process-host'
|
||||
vi.mock('./ssh-relay-deploy-helpers', () => ({
|
||||
execCommand: vi.fn(),
|
||||
isUnconfirmedSshCommandTermination: () => false
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: vi.fn()
|
||||
}))
|
||||
import {
|
||||
relayEndpointIncumbentProbeCommand,
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', () => ({ execCommand: vi.fn() }))
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: vi.fn()
|
||||
}))
|
||||
|
||||
import type { SshConnection } from './ssh-connection'
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import { acquireInstallLock, isRelayInstallLockStale } from './ssh-relay-install-lock'
|
||||
import { getRemoteHostPlatform } from './ssh-remote-platform'
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { SshConnection } from './ssh-connection'
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', () => ({
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: vi.fn(),
|
||||
isUnconfirmedSshCommandTermination: (error: unknown) =>
|
||||
error instanceof Error &&
|
||||
@@ -13,7 +15,7 @@ vi.mock('./ssh-relay-gc-claim', () => ({
|
||||
waitForRelayGcClaimRelease: vi.fn().mockResolvedValue(undefined)
|
||||
}))
|
||||
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import { acquireInstallLock, RemoteInstallLockBusyError } from './ssh-relay-install-lock'
|
||||
import { getRemoteHostPlatform } from './ssh-remote-platform'
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { EventEmitter } from 'node:events'
|
||||
import type { ClientChannel } from 'ssh2'
|
||||
import { expect, it, vi } from 'vitest'
|
||||
import { RELAY_SENTINEL } from './relay-protocol'
|
||||
import { waitForSentinel } from './ssh-relay-deploy-helpers'
|
||||
import { waitForSentinel } from './ssh-relay-sentinel'
|
||||
|
||||
it.each([1, 256])('copies each startup prefix once across %i chunks', async (chunks) => {
|
||||
const channel = Object.assign(new EventEmitter(), {
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
import { EventEmitter } from 'node:events'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import type { ClientChannel } from 'ssh2'
|
||||
import { waitForSentinel } from './ssh-relay-sentinel'
|
||||
import { RELAY_SENTINEL } from './relay-protocol'
|
||||
import {
|
||||
RelayVersionMismatchError,
|
||||
RELAY_EXIT_CODE_VERSION_MISMATCH
|
||||
} from './ssh-relay-version-mismatch-error'
|
||||
import {
|
||||
RelayCredentialMismatchError,
|
||||
RELAY_EXIT_CODE_CREDENTIAL_MISMATCH
|
||||
} from './ssh-relay-credential-mismatch-error'
|
||||
|
||||
type MockChannel = ClientChannel & {
|
||||
stdin: EventEmitter & { write: ReturnType<typeof vi.fn> }
|
||||
}
|
||||
|
||||
function createMockChannel(): MockChannel {
|
||||
return Object.assign(new EventEmitter(), {
|
||||
stderr: Object.assign(new EventEmitter(), { resume: vi.fn() }),
|
||||
stdin: Object.assign(new EventEmitter(), { write: vi.fn(() => true) }),
|
||||
close: vi.fn(),
|
||||
resume: vi.fn()
|
||||
}) as unknown as MockChannel
|
||||
}
|
||||
|
||||
describe('waitForSentinel', () => {
|
||||
it('closes and rejects with AbortError while clearing startup resources on abort', async () => {
|
||||
vi.useFakeTimers()
|
||||
try {
|
||||
const channel = createMockChannel()
|
||||
const controller = new AbortController()
|
||||
const removeListener = vi.spyOn(controller.signal, 'removeEventListener')
|
||||
const transportPromise = waitForSentinel(channel, controller.signal)
|
||||
|
||||
expect(vi.getTimerCount()).toBe(1)
|
||||
controller.abort()
|
||||
|
||||
await expect(transportPromise).rejects.toMatchObject({ name: 'AbortError' })
|
||||
expect(channel.close).toHaveBeenCalledTimes(1)
|
||||
expect(removeListener).toHaveBeenCalledWith('abort', expect.any(Function))
|
||||
expect(vi.getTimerCount()).toBe(0)
|
||||
} finally {
|
||||
vi.useRealTimers()
|
||||
}
|
||||
})
|
||||
|
||||
it('removes the abort listener and timers when startup fails before the sentinel', async () => {
|
||||
vi.useFakeTimers()
|
||||
try {
|
||||
const channel = createMockChannel()
|
||||
const controller = new AbortController()
|
||||
const removeListener = vi.spyOn(controller.signal, 'removeEventListener')
|
||||
const transportPromise = waitForSentinel(channel, controller.signal)
|
||||
|
||||
channel.emit('error', new Error('remote host rebooted'))
|
||||
|
||||
await expect(transportPromise).rejects.toThrow('remote host rebooted')
|
||||
expect(removeListener).toHaveBeenCalledWith('abort', expect.any(Function))
|
||||
expect(vi.getTimerCount()).toBe(0)
|
||||
} finally {
|
||||
vi.useRealTimers()
|
||||
}
|
||||
})
|
||||
|
||||
it('buffers post-sentinel chunks until the transport subscribes', async () => {
|
||||
const channel = createMockChannel()
|
||||
const transportPromise = waitForSentinel(channel)
|
||||
|
||||
channel.emit('data', Buffer.from(RELAY_SENTINEL))
|
||||
channel.emit('data', Buffer.from('first-frame-after-sentinel'))
|
||||
|
||||
const transport = await transportPromise
|
||||
const chunks: string[] = []
|
||||
transport.onData((chunk) => chunks.push(chunk.toString('utf-8')))
|
||||
|
||||
expect(chunks).toEqual(['first-frame-after-sentinel'])
|
||||
})
|
||||
|
||||
it('buffers post-sentinel bytes from the sentinel chunk', async () => {
|
||||
const channel = createMockChannel()
|
||||
const transportPromise = waitForSentinel(channel)
|
||||
|
||||
channel.emit('data', Buffer.from(`${RELAY_SENTINEL}same-chunk-frame`))
|
||||
|
||||
const transport = await transportPromise
|
||||
const chunks: string[] = []
|
||||
transport.onData((chunk) => chunks.push(chunk.toString('utf-8')))
|
||||
|
||||
expect(chunks).toEqual(['same-chunk-frame'])
|
||||
})
|
||||
|
||||
it('rejects on relay channel errors before the sentinel instead of emitting uncaught errors', async () => {
|
||||
const channel = createMockChannel()
|
||||
const transportPromise = waitForSentinel(channel)
|
||||
|
||||
expect(() => channel.emit('error', new Error('remote host rebooted'))).not.toThrow()
|
||||
await expect(transportPromise).rejects.toThrow('remote host rebooted')
|
||||
})
|
||||
|
||||
it('notifies transport close subscribers on relay channel errors after the sentinel', async () => {
|
||||
const channel = createMockChannel()
|
||||
const transportPromise = waitForSentinel(channel)
|
||||
|
||||
channel.emit('data', Buffer.from(RELAY_SENTINEL))
|
||||
const transport = await transportPromise
|
||||
const onClose = vi.fn()
|
||||
transport.onClose(onClose)
|
||||
|
||||
expect(() => channel.emit('error', new Error('remote host rebooted'))).not.toThrow()
|
||||
expect(onClose).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('translates a pre-sentinel exit-42 + close into RelayVersionMismatchError', async () => {
|
||||
const channel = createMockChannel()
|
||||
const transportPromise = waitForSentinel(channel)
|
||||
|
||||
channel.stderr.emit(
|
||||
'data',
|
||||
Buffer.from(
|
||||
'[relay-connect] Handshake mismatch: expected=0.1.0+aaa, daemon=0.1.0+bbb; exiting 42\n'
|
||||
)
|
||||
)
|
||||
channel.emit('exit', RELAY_EXIT_CODE_VERSION_MISMATCH)
|
||||
channel.emit('close')
|
||||
|
||||
await expect(transportPromise).rejects.toBeInstanceOf(RelayVersionMismatchError)
|
||||
await transportPromise.catch((err: RelayVersionMismatchError) => {
|
||||
expect(err.expected).toBe('0.1.0+aaa')
|
||||
expect(err.got).toBe('0.1.0+bbb')
|
||||
})
|
||||
})
|
||||
|
||||
it('translates a pre-sentinel exit-42 even when the version detail is missing', async () => {
|
||||
const channel = createMockChannel()
|
||||
const transportPromise = waitForSentinel(channel)
|
||||
|
||||
channel.stderr.emit('data', Buffer.from('boom\n'))
|
||||
channel.emit('exit', RELAY_EXIT_CODE_VERSION_MISMATCH)
|
||||
channel.emit('close')
|
||||
|
||||
await expect(transportPromise).rejects.toBeInstanceOf(RelayVersionMismatchError)
|
||||
})
|
||||
|
||||
it('translates a pre-sentinel exit-43 + close into RelayCredentialMismatchError', async () => {
|
||||
const channel = createMockChannel()
|
||||
const transportPromise = waitForSentinel(channel)
|
||||
|
||||
channel.stderr.emit(
|
||||
'data',
|
||||
Buffer.from('[relay-connect] Endpoint credential refused by daemon; exiting 43\n')
|
||||
)
|
||||
channel.emit('exit', RELAY_EXIT_CODE_CREDENTIAL_MISMATCH)
|
||||
channel.emit('close')
|
||||
|
||||
await expect(transportPromise).rejects.toBeInstanceOf(RelayCredentialMismatchError)
|
||||
await transportPromise.catch((err: unknown) => {
|
||||
expect(err).not.toBeInstanceOf(RelayVersionMismatchError)
|
||||
})
|
||||
})
|
||||
|
||||
it('rejects with a generic error (not RelayVersionMismatchError) on a non-42 exit code', async () => {
|
||||
const channel = createMockChannel()
|
||||
const transportPromise = waitForSentinel(channel)
|
||||
|
||||
channel.stderr.emit('data', Buffer.from('node: bad bytecode\n'))
|
||||
channel.emit('exit', 1)
|
||||
channel.emit('close')
|
||||
|
||||
await expect(transportPromise).rejects.toThrow(/Relay process exited before ready/)
|
||||
await transportPromise.catch((err: unknown) => {
|
||||
expect(err).not.toBeInstanceOf(RelayVersionMismatchError)
|
||||
})
|
||||
})
|
||||
|
||||
it('rejects and closes the channel when pre-sentinel stdout exceeds the startup buffer cap', async () => {
|
||||
const channel = createMockChannel()
|
||||
const transportPromise = waitForSentinel(channel)
|
||||
|
||||
channel.emit('data', Buffer.alloc(65 * 1024, 'a'))
|
||||
|
||||
const outcome = await Promise.race([
|
||||
transportPromise.then(
|
||||
() => 'resolved',
|
||||
(err: Error) => `rejected:${err.message}`
|
||||
),
|
||||
new Promise<string>((resolve) => setTimeout(() => resolve('pending'), 0))
|
||||
])
|
||||
if (outcome === 'pending') {
|
||||
channel.emit('close')
|
||||
await transportPromise.catch(() => {})
|
||||
}
|
||||
|
||||
expect(outcome).toContain('Relay startup output exceeded')
|
||||
expect(channel.close).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('does not count same-chunk post-sentinel payload against the startup buffer cap', async () => {
|
||||
const channel = createMockChannel()
|
||||
const transportPromise = waitForSentinel(channel)
|
||||
const postSentinelPayload = Buffer.alloc(70 * 1024, 'b')
|
||||
|
||||
channel.emit(
|
||||
'data',
|
||||
Buffer.concat([
|
||||
Buffer.alloc(64 * 1024, 'a'),
|
||||
Buffer.from(RELAY_SENTINEL),
|
||||
postSentinelPayload
|
||||
])
|
||||
)
|
||||
|
||||
const transport = await transportPromise
|
||||
const chunks: Buffer[] = []
|
||||
transport.onData((chunk) => chunks.push(chunk))
|
||||
|
||||
expect(Buffer.concat(chunks)).toEqual(postSentinelPayload)
|
||||
expect(channel.close).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it.each(['ssh2 channel', 'system-SSH child stdio'])(
|
||||
'forwards write(false), callback settlement, and drain for a %s',
|
||||
async (channelKind) => {
|
||||
const channel = createMockChannel()
|
||||
if (channelKind.startsWith('system')) {
|
||||
Object.assign(channel, { _process: new EventEmitter() })
|
||||
}
|
||||
const callback = vi.fn()
|
||||
const drain = vi.fn()
|
||||
channel.stdin.write.mockImplementation((...args: unknown[]) => {
|
||||
const onWritten = args.find((arg) => typeof arg === 'function') as (
|
||||
error?: Error | null
|
||||
) => void
|
||||
onWritten(null)
|
||||
return false
|
||||
})
|
||||
const transportPromise = waitForSentinel(channel)
|
||||
channel.emit('data', Buffer.from(RELAY_SENTINEL))
|
||||
const transport = await transportPromise
|
||||
|
||||
transport.onDrain?.(drain)
|
||||
expect(transport.write(Buffer.from('frame'), callback)).toBe(false)
|
||||
expect(callback).toHaveBeenCalledWith({ ok: true })
|
||||
channel.stdin.emit('drain')
|
||||
expect(drain).toHaveBeenCalledOnce()
|
||||
expect(transport.supportsWriteSettlement).toBe(true)
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -4,11 +4,6 @@ import { RELAY_SENTINEL, RELAY_SENTINEL_TIMEOUT_MS } from './relay-protocol'
|
||||
import type { MultiplexerTransport } from './ssh-channel-multiplexer'
|
||||
import { buildRelayHandshakeRefusalError } from './ssh-relay-handshake-mismatch'
|
||||
|
||||
export { uploadFile, uploadDirectory, mkdirSftp } from './sftp-upload'
|
||||
export { execCommand, isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
|
||||
|
||||
// ── Sentinel detection ────────────────────────────────────────────────
|
||||
|
||||
const MAX_RELAY_STARTUP_BUFFER_BYTES = 64 * 1024
|
||||
const RELAY_SENTINEL_BUFFER = Buffer.from(RELAY_SENTINEL, 'utf-8')
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', () => ({ execCommand: vi.fn() }))
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: vi.fn()
|
||||
}))
|
||||
|
||||
import type { SshConnection } from './ssh-connection'
|
||||
import { REMOTE_INSTALL_MODELS } from './remote-install-model'
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import {
|
||||
abandonInstall,
|
||||
finalizeInstall,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { SshConnection } from './ssh-connection'
|
||||
import { createSshOperationAbortError } from './ssh-connection-utils'
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { execCommand } from './ssh-relay-exec-command'
|
||||
import { isSshSessionLimitError } from './ssh-session-limit-error'
|
||||
|
||||
export type RemoteNodeResolutionOptions = {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import { chmodSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import os from 'node:os'
|
||||
@@ -8,7 +9,8 @@ import { getRemoteHostPlatform } from './ssh-remote-platform'
|
||||
|
||||
const execCommandMock = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', () => ({
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: execCommandMock
|
||||
}))
|
||||
|
||||
|
||||
@@ -7,8 +7,7 @@ import {
|
||||
buildPosixNodeInstallGuidance,
|
||||
type RemoteNodeResolutionOptions
|
||||
} from './ssh-remote-node-install-guidance'
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { isSshCommandExitError } from './ssh-relay-exec-command'
|
||||
import { execCommand, isSshCommandExitError } from './ssh-relay-exec-command'
|
||||
import {
|
||||
buildPosixNodeToolchainProbe,
|
||||
buildWindowsNodeToolchainProbe,
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import type * as ExecCommandModule from './ssh-relay-exec-command'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { SshConnection } from './ssh-connection'
|
||||
import { SSH_EXEC_TIMEOUT_CODE } from './ssh-relay-exec-command'
|
||||
|
||||
const execCommandMock = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', () => ({
|
||||
vi.mock('./ssh-relay-exec-command', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof ExecCommandModule>()),
|
||||
execCommand: execCommandMock
|
||||
}))
|
||||
|
||||
|
||||
@@ -4,8 +4,11 @@ import {
|
||||
iterateProcessOutputLines
|
||||
} from '../../shared/process-output-field-scanner'
|
||||
import { parseUnameToRelayPlatform, type RelayPlatform } from './relay-protocol'
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
import { isSshExecTimeout, isUnconfirmedSshCommandTermination } from './ssh-relay-exec-command'
|
||||
import {
|
||||
execCommand,
|
||||
isSshExecTimeout,
|
||||
isUnconfirmedSshCommandTermination
|
||||
} from './ssh-relay-exec-command'
|
||||
import { isSshSessionLimitError } from './ssh-session-limit-error'
|
||||
import { getRemoteHostPlatform, type RemoteHostPlatform } from './ssh-remote-platform'
|
||||
import { powerShellCommand } from './ssh-remote-powershell'
|
||||
|
||||
@@ -28,7 +28,7 @@ export class SystemSshPortForwardProvider implements SshPortForwardProvider {
|
||||
// Why: this stderr stays attached for the forward's whole lifetime but is
|
||||
// only used to build the exit-error detail, so keep a bounded tail — a chatty
|
||||
// remote sshd could otherwise grow it unbounded on a long-lived forward
|
||||
// (mirrors MAX_RELAY_STARTUP_BUFFER_BYTES in ssh-relay-deploy-helpers).
|
||||
// (mirrors MAX_RELAY_STARTUP_BUFFER_BYTES in ssh-relay-sentinel).
|
||||
const MAX_STDERR_TAIL_BYTES = 64 * 1024
|
||||
let stderr = ''
|
||||
const onStderr = (chunk: Buffer): void => {
|
||||
|
||||
Reference in New Issue
Block a user