129 lines
4.3 KiB
TypeScript
129 lines
4.3 KiB
TypeScript
import { afterEach, describe, expect, test, vi } from 'vitest';
|
|
|
|
const loadVolumeSpec = async () => {
|
|
vi.resetModules();
|
|
process.env.SPOON_WORKER_TOKEN = 'test-worker-token';
|
|
process.env.GITHUB_APP_ID = '123';
|
|
process.env.GITHUB_APP_PRIVATE_KEY =
|
|
'-----BEGIN PRIVATE KEY-----\\ntest\\n-----END PRIVATE KEY-----';
|
|
return await import('../../src/runtime/docker');
|
|
};
|
|
|
|
describe('Docker runtime', () => {
|
|
afterEach(() => {
|
|
delete process.env.SPOON_AGENT_CONTAINER_RUNTIME;
|
|
delete process.env.SPOON_AGENT_CONTAINER_VOLUME_OPTIONS;
|
|
vi.resetModules();
|
|
});
|
|
|
|
test('adds SELinux relabel option for Podman workspace mounts by default', async () => {
|
|
process.env.SPOON_AGENT_CONTAINER_RUNTIME = 'podman';
|
|
const { jobWorkspaceVolumeSpec } = await loadVolumeSpec();
|
|
|
|
expect(jobWorkspaceVolumeSpec('/tmp/spoon-job')).toBe(
|
|
'/tmp/spoon-job:/workspace:Z',
|
|
);
|
|
});
|
|
|
|
test('does not add Podman volume options for Docker by default', async () => {
|
|
process.env.SPOON_AGENT_CONTAINER_RUNTIME = 'docker';
|
|
const { jobWorkspaceVolumeSpec } = await loadVolumeSpec();
|
|
|
|
expect(jobWorkspaceVolumeSpec('/tmp/spoon-job')).toBe(
|
|
'/tmp/spoon-job:/workspace',
|
|
);
|
|
});
|
|
|
|
test('allows explicit workspace mount options', async () => {
|
|
process.env.SPOON_AGENT_CONTAINER_RUNTIME = 'podman';
|
|
process.env.SPOON_AGENT_CONTAINER_VOLUME_OPTIONS = 'z';
|
|
const { jobWorkspaceVolumeSpec } = await loadVolumeSpec();
|
|
|
|
expect(jobWorkspaceVolumeSpec('/tmp/spoon-job')).toBe(
|
|
'/tmp/spoon-job:/workspace:z',
|
|
);
|
|
});
|
|
|
|
test('boxUsernsArgs maps the host user onto the box uid under podman', async () => {
|
|
process.env.SPOON_AGENT_CONTAINER_RUNTIME = 'podman';
|
|
const { boxUsernsArgs } = await loadVolumeSpec();
|
|
expect(boxUsernsArgs()).toEqual(['--userns=keep-id:uid=1000,gid=1000']);
|
|
});
|
|
|
|
test('boxUsernsArgs adds nothing under docker', async () => {
|
|
process.env.SPOON_AGENT_CONTAINER_RUNTIME = 'docker';
|
|
const { boxUsernsArgs } = await loadVolumeSpec();
|
|
expect(boxUsernsArgs()).toEqual([]);
|
|
});
|
|
|
|
test('SPOON_AGENT_BOX_USERNS overrides: value passes through, empty disables', async () => {
|
|
process.env.SPOON_AGENT_CONTAINER_RUNTIME = 'podman';
|
|
process.env.SPOON_AGENT_BOX_USERNS = 'host';
|
|
let { boxUsernsArgs } = await loadVolumeSpec();
|
|
expect(boxUsernsArgs()).toEqual(['--userns=host']);
|
|
|
|
process.env.SPOON_AGENT_BOX_USERNS = '';
|
|
({ boxUsernsArgs } = await loadVolumeSpec());
|
|
expect(boxUsernsArgs()).toEqual([]);
|
|
delete process.env.SPOON_AGENT_BOX_USERNS;
|
|
});
|
|
|
|
test('execArgv places -u after the workdir and before the container name', async () => {
|
|
const { execArgv } = await loadVolumeSpec();
|
|
expect(
|
|
execArgv({
|
|
containerName: 'spoon-box-gabriel',
|
|
command: ['ls', '-la'],
|
|
environment: { HOME: '/home/gabriel' },
|
|
containerCwd: '/home/gabriel',
|
|
user: 'gabriel',
|
|
}),
|
|
).toEqual([
|
|
'exec',
|
|
'-e',
|
|
'HOME=/home/gabriel',
|
|
'-w',
|
|
'/home/gabriel',
|
|
'-u',
|
|
'gabriel',
|
|
'spoon-box-gabriel',
|
|
'ls',
|
|
'-la',
|
|
]);
|
|
});
|
|
|
|
test('execArgv omits -u when no user is given', async () => {
|
|
const { execArgv } = await loadVolumeSpec();
|
|
const argv = execArgv({
|
|
containerName: 'c',
|
|
command: ['true'],
|
|
environment: {},
|
|
containerCwd: '/',
|
|
});
|
|
expect(argv).not.toContain('-u');
|
|
});
|
|
|
|
test('treats a spawn failure (no exitCode) as a non-zero exit, not empty success', async () => {
|
|
const { normalizeRunResult } = await loadVolumeSpec();
|
|
// This is what execa returns with `reject: false` when the runtime binary is
|
|
// missing (e.g. no `docker` CLI in the worker image): exitCode is undefined.
|
|
const result = normalizeRunResult(
|
|
{ exitCode: undefined, shortMessage: 'spawn docker ENOENT' },
|
|
undefined,
|
|
(value) => value,
|
|
);
|
|
expect(result.exitCode).toBe(1);
|
|
expect(result.output).toContain('spawn docker ENOENT');
|
|
});
|
|
|
|
test('passes through a normal command result unchanged', async () => {
|
|
const { normalizeRunResult } = await loadVolumeSpec();
|
|
const result = normalizeRunResult(
|
|
{ exitCode: 0, shortMessage: undefined },
|
|
'hello',
|
|
(value) => value,
|
|
);
|
|
expect(result).toEqual({ exitCode: 0, output: 'hello' });
|
|
});
|
|
});
|