mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 00:03:45 +00:00
@@ -102,6 +102,57 @@ describe('SSH relay artifact selector', () => {
|
||||
).toBe('selected')
|
||||
})
|
||||
|
||||
it.each(['4.18.0-553.5.1.el8_10.x86_64', '5.15.0-107-generic', '6.6.15-0-lts'])(
|
||||
'accepts a supported Linux distro kernel release %s',
|
||||
(kernelVersion) => {
|
||||
expect(
|
||||
selectSshRelayArtifact(verifiedManifest(), {
|
||||
...compatibleLinuxHost,
|
||||
kernelVersion
|
||||
}).kind
|
||||
).toBe('selected')
|
||||
}
|
||||
)
|
||||
|
||||
it.each([
|
||||
['4.18.0-553.5.1.el8_10.x86_64', 'selected'],
|
||||
['4.17.99-553.5.1.el8_10.x86_64', 'kernel-too-old'],
|
||||
['4.18.0 bad', 'unknown-kernel'],
|
||||
['4.18.0/bad', 'unknown-kernel'],
|
||||
['4.18.0:bad', 'unknown-kernel'],
|
||||
['4.18.0@bad', 'unknown-kernel']
|
||||
] as const)('classifies the kernel suffix boundary for %s', (kernelVersion, expected) => {
|
||||
const result = selectSshRelayArtifact(verifiedManifest(), {
|
||||
...compatibleLinuxHost,
|
||||
kernelVersion
|
||||
})
|
||||
|
||||
expect(result.kind === 'selected' ? result.kind : result.reason).toBe(expected)
|
||||
})
|
||||
|
||||
it('keeps non-kernel version grammars strict', () => {
|
||||
expect(
|
||||
selectSshRelayArtifact(verifiedManifest(), {
|
||||
...compatibleLinuxHost,
|
||||
libc: { family: 'glibc', version: '2.28_bad' }
|
||||
})
|
||||
).toEqual({ kind: 'legacy', reason: 'unknown-libc' })
|
||||
expect(
|
||||
selectSshRelayArtifact(verifiedManifest(createSshRelayDarwinArtifactTestManifest()), {
|
||||
os: 'darwin',
|
||||
architecture: 'arm64',
|
||||
processTranslated: false,
|
||||
version: '13.5_bad'
|
||||
})
|
||||
).toEqual({ kind: 'legacy', reason: 'unknown-os-version' })
|
||||
expect(
|
||||
selectSshRelayArtifact(verifiedManifest(windowsManifest()), {
|
||||
...compatibleWindowsHost,
|
||||
powerShellVersion: '5.1_bad'
|
||||
})
|
||||
).toEqual({ kind: 'legacy', reason: 'unknown-powershell' })
|
||||
})
|
||||
|
||||
it('selects the detected libc family when both Linux variants exist', () => {
|
||||
const manifest = createSshRelayArtifactTestManifest()
|
||||
const musl = structuredClone(manifest.tuples[0])
|
||||
|
||||
@@ -3,6 +3,8 @@ import { sshRelayRuntimeDownloadUrl } from './ssh-relay-release-asset'
|
||||
|
||||
type VerifiedSshRelayRuntimeTuple = VerifiedSshRelayArtifactManifest['tuples'][number]
|
||||
|
||||
const LINUX_KERNEL_RELEASE_MAX_CHARS = 256
|
||||
|
||||
type SshRelayHostBase = {
|
||||
architecture: 'x64' | 'arm64'
|
||||
processTranslated: boolean
|
||||
@@ -100,6 +102,24 @@ function parseNumericVersion(value: string | undefined): number[] | null {
|
||||
return components.every(Number.isSafeInteger) ? components : null
|
||||
}
|
||||
|
||||
function parseLinuxKernelVersion(value: string | undefined): number[] | null {
|
||||
if (!value || value.length > LINUX_KERNEL_RELEASE_MAX_CHARS) {
|
||||
return null
|
||||
}
|
||||
// Why: real distro kernels include `_`, `+`, and `~`; other version fields keep the stricter
|
||||
// generic grammar so this compatibility exception cannot weaken unrelated host evidence.
|
||||
const match = /^(\d+(?:\.\d+){1,3})(?:-[0-9A-Za-z._+~-]+)?$/.exec(value)
|
||||
if (!match) {
|
||||
return null
|
||||
}
|
||||
const components = match[1].split('.').map(Number)
|
||||
return components.every(Number.isSafeInteger) ? components : null
|
||||
}
|
||||
|
||||
export function isSshRelayLinuxKernelRelease(value: string | undefined): value is string {
|
||||
return parseLinuxKernelVersion(value) !== null
|
||||
}
|
||||
|
||||
function parseOpenSshVersion(value: string | undefined): number[] | null {
|
||||
if (!value) {
|
||||
return null
|
||||
@@ -144,7 +164,11 @@ function selectLinux(
|
||||
if (tuple.compatibility.kind !== 'linux') {
|
||||
return { kind: 'legacy', reason: 'tuple-inconsistent' }
|
||||
}
|
||||
const kernel = meetsVersion(host.kernelVersion, tuple.compatibility.minimumKernelVersion)
|
||||
const kernel = meetsVersion(
|
||||
host.kernelVersion,
|
||||
tuple.compatibility.minimumKernelVersion,
|
||||
parseLinuxKernelVersion
|
||||
)
|
||||
if (kernel === null) {
|
||||
return { kind: 'legacy', reason: 'unknown-kernel' }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { SshConnection } from './ssh-connection'
|
||||
|
||||
const execCommandMock = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('./ssh-relay-deploy-helpers', () => ({
|
||||
execCommand: execCommandMock
|
||||
}))
|
||||
|
||||
const { detectSshRelayLinuxKernelRelease } = await import('./ssh-relay-linux-kernel-detection')
|
||||
|
||||
const conn = {} as SshConnection
|
||||
const marker = '__ORCA_SSH_RELAY_KERNEL__'
|
||||
|
||||
function segment(...lines: string[]): string {
|
||||
return [`${marker} BEGIN`, ...lines, `${marker} END`].join('\n')
|
||||
}
|
||||
|
||||
describe('detectSshRelayLinuxKernelRelease', () => {
|
||||
beforeEach(() => {
|
||||
execCommandMock.mockReset()
|
||||
})
|
||||
|
||||
it.each(['4.18.0-553.5.1.el8_10.x86_64', '5.15.0-107-generic', '6.6.15-0-lts'])(
|
||||
'returns the exact marked distro kernel release %s',
|
||||
async (release) => {
|
||||
execCommandMock.mockResolvedValueOnce(
|
||||
['Last login: ignored', segment(release), 'post-command noise'].join('\n')
|
||||
)
|
||||
|
||||
await expect(detectSshRelayLinuxKernelRelease(conn)).resolves.toBe(release)
|
||||
}
|
||||
)
|
||||
|
||||
it.each([
|
||||
['', 'empty segment'],
|
||||
['6.8.0\nsecond-line', 'multiple marked lines'],
|
||||
[`${marker} BEGIN\n6.8.0`, 'unterminated segment'],
|
||||
[segment('x'.repeat(257)), 'oversized release'],
|
||||
[segment('release-6.8.0'), 'malformed numeric prefix'],
|
||||
[segment('6.8.0 generic'), 'suffix whitespace'],
|
||||
[segment('6.8.0/generic'), 'suffix slash'],
|
||||
[segment('6.8.0:generic'), 'suffix colon'],
|
||||
[segment('6.8.0@generic'), 'unsupported suffix punctuation'],
|
||||
[segment('6.8.0\tgeneric'), 'control whitespace'],
|
||||
['6.8.0', 'unmarked evidence'],
|
||||
[[segment('6.8.0'), segment('6.8.0')].join('\n'), 'duplicate complete segments'],
|
||||
[`startup${marker} BEGIN\n6.8.0\n${marker} END`, 'marker concatenated to startup noise']
|
||||
])('returns unknown for %s', async (output) => {
|
||||
execCommandMock.mockResolvedValueOnce(output)
|
||||
|
||||
await expect(detectSshRelayLinuxKernelRelease(conn)).resolves.toBeUndefined()
|
||||
})
|
||||
|
||||
it('classifies an unavailable probe as unknown without inventing fallback policy', async () => {
|
||||
execCommandMock.mockRejectedValueOnce(new Error('remote command unavailable'))
|
||||
|
||||
await expect(detectSshRelayLinuxKernelRelease(conn)).resolves.toBeUndefined()
|
||||
})
|
||||
|
||||
it('propagates cancellation and passes the signal into the single bounded probe', async () => {
|
||||
const signal = new AbortController().signal
|
||||
const abortError = Object.assign(new Error('cancelled'), { name: 'AbortError' })
|
||||
execCommandMock.mockRejectedValueOnce(abortError)
|
||||
|
||||
await expect(detectSshRelayLinuxKernelRelease(conn, { signal })).rejects.toBe(abortError)
|
||||
expect(execCommandMock).toHaveBeenCalledTimes(1)
|
||||
expect(execCommandMock).toHaveBeenCalledWith(conn, expect.any(String), {
|
||||
signal,
|
||||
timeoutMs: 15_000
|
||||
})
|
||||
})
|
||||
|
||||
it('constructs one marked POSIX-shell uname probe without runtime dependencies', async () => {
|
||||
execCommandMock.mockResolvedValueOnce(segment('6.8.0'))
|
||||
|
||||
await detectSshRelayLinuxKernelRelease(conn)
|
||||
|
||||
const command = execCommandMock.mock.calls[0]?.[1] ?? ''
|
||||
expect(command).toContain('command -v uname')
|
||||
expect(command).toContain('uname -r')
|
||||
expect(command.match(/uname -r/gu)).toHaveLength(1)
|
||||
expect(command).toContain(`${marker} BEGIN`)
|
||||
expect(command).toContain(`${marker} END`)
|
||||
expect(command).not.toMatch(/\b(?:node|npm|python|perl|tar|sha256sum|shasum)\b/u)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,86 @@
|
||||
import {
|
||||
getProcessOutputFields,
|
||||
iterateProcessOutputLines
|
||||
} from '../../shared/process-output-field-scanner'
|
||||
import type { SshConnection } from './ssh-connection'
|
||||
import { isSshRelayLinuxKernelRelease } from './ssh-relay-artifact-selector'
|
||||
import { execCommand } from './ssh-relay-deploy-helpers'
|
||||
|
||||
const PROBE_MARKER = '__ORCA_SSH_RELAY_KERNEL__'
|
||||
const PROBE_TIMEOUT_MS = 15_000
|
||||
|
||||
const PROBE_COMMAND = [
|
||||
`printf '\n%s\n' '${PROBE_MARKER} BEGIN'`,
|
||||
'if command -v uname >/dev/null 2>&1; then uname -r 2>&1 || :; fi',
|
||||
`printf '%s\n' '${PROBE_MARKER} END'`
|
||||
].join('\n')
|
||||
|
||||
function parseMarker(line: string): 'BEGIN' | 'END' | null {
|
||||
const fields = getProcessOutputFields(line, 3)
|
||||
if (fields.length !== 2 || fields[0] !== PROBE_MARKER) {
|
||||
return null
|
||||
}
|
||||
return fields[1] === 'BEGIN' || fields[1] === 'END' ? fields[1] : null
|
||||
}
|
||||
|
||||
function parseKernelRelease(output: string): string | undefined {
|
||||
let activeLines: string[] | null = null
|
||||
let release: string | undefined
|
||||
let invalid = false
|
||||
|
||||
for (const line of iterateProcessOutputLines(output)) {
|
||||
const marker = parseMarker(line)
|
||||
if (marker === 'BEGIN') {
|
||||
if (activeLines || release !== undefined) {
|
||||
invalid = true
|
||||
}
|
||||
activeLines = []
|
||||
continue
|
||||
}
|
||||
if (marker === 'END') {
|
||||
if (!activeLines || activeLines.length !== 1 || release !== undefined) {
|
||||
invalid = true
|
||||
activeLines = null
|
||||
continue
|
||||
}
|
||||
release = activeLines[0]
|
||||
activeLines = null
|
||||
continue
|
||||
}
|
||||
if (activeLines) {
|
||||
if (activeLines.length === 1) {
|
||||
invalid = true
|
||||
} else {
|
||||
activeLines.push(line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (invalid || activeLines || !isSshRelayLinuxKernelRelease(release)) {
|
||||
return undefined
|
||||
}
|
||||
return release
|
||||
}
|
||||
|
||||
function isAbortError(error: unknown): boolean {
|
||||
return error instanceof Error && error.name === 'AbortError'
|
||||
}
|
||||
|
||||
export async function detectSshRelayLinuxKernelRelease(
|
||||
conn: SshConnection,
|
||||
{ signal }: { signal?: AbortSignal } = {}
|
||||
): Promise<string | undefined> {
|
||||
try {
|
||||
const output = await execCommand(conn, PROBE_COMMAND, {
|
||||
signal,
|
||||
timeoutMs: PROBE_TIMEOUT_MS
|
||||
})
|
||||
return parseKernelRelease(output)
|
||||
} catch (error) {
|
||||
// Why: cancellation must settle the caller's work instead of becoming compatibility evidence.
|
||||
if (isAbortError(error)) {
|
||||
throw error
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user