Skip to content
This repository has been archived by the owner on May 15, 2024. It is now read-only.

Feature/mount user one app directory #574

Merged
merged 2 commits into from
Oct 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 148 additions & 1 deletion packages/one-app-runner/__tests__/src/startApp.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const path = require('node:path');
const fs = require('node:fs');
const childProcess = require('child_process');
const { Writable } = require('node:stream');
const os = require('node:os');
const Docker = require('dockerode');
const makeMockSpawn = require('mock-spawn');
const startApp = require('../../src/startApp');
Expand All @@ -30,6 +31,9 @@ describe('startApp', () => {
const createWriteStreamSpy = jest.spyOn(fs, 'createWriteStream');
const stdoutSpy = jest.spyOn(process.stdout, 'write');
const stderrSpy = jest.spyOn(process.stderr, 'write');
jest.spyOn(fs.promises, 'mkdir');
jest.spyOn(os, 'homedir');
jest.spyOn(console, 'warn');

beforeEach(() => {
jest.resetAllMocks();
Expand All @@ -42,6 +46,9 @@ describe('startApp', () => {
delete process.env.HTTP_METRICS_PORT;
delete process.env.NODE_EXTRA_CA_CERTS;
delete process.env.HTTP_ONE_APP_DEBUG_PORT;

os.homedir.mockImplementation(() => '/home/user');
fs.promises.mkdir.mockImplementation(() => {});
});

it('pulls one app docker image and starts one app', async () => {
Expand All @@ -67,12 +74,13 @@ describe('startApp', () => {
"-p=3005:3005",
"-p=9229:9229",
"-e=NODE_ENV=development",
"-v=/home/user/.one-app:/home/node/.one-app",
"one-app:5.0.0",
"/bin/sh",
"-c",
" node lib/server/index.js --root-module-name=frank-lloyd-root --module-map-url=https://example.com/module-map.json ",
]
`);
`);
});

it('runs docker run with environment variables', async () => {
Expand Down Expand Up @@ -128,6 +136,7 @@ describe('startApp', () => {
expect(mockSpawn.calls[1].args.filter((arg) => arg.startsWith('-v'))).toEqual([
'-v=/path/to/module-a:/opt/module-workspace/module-a',
'-v=/path/to-module-b:/opt/module-workspace/to-module-b',
'-v=/home/user/.one-app:/home/node/.one-app',
]);
expect(mockSpawn.calls[1].args[mockSpawn.calls[1].args.indexOf('-c') + 1]).toMatchInlineSnapshot(
'"npm run serve-module \'/opt/module-workspace/module-a\' &&npm run serve-module \'/opt/module-workspace/to-module-b\' && node lib/server/index.js --root-module-name=frank-lloyd-root --module-map-url=https://example.com/module-map.json "'
Expand All @@ -144,6 +153,7 @@ describe('startApp', () => {
expect(mockSpawn.calls[1].args.filter((arg) => arg.startsWith('-v'))).toEqual([
'-v=/path/to/module-a:/opt/module-workspace/module-a',
'-v=/path/to-module-b:/opt/module-workspace/to-module-b',
'-v=/home/user/.one-app:/home/node/.one-app',
]);
expect(mockSpawn.calls[1].args[mockSpawn.calls[1].args.indexOf('-c') + 1]).toMatchInlineSnapshot(
'"npm run serve-module \'/opt/module-workspace/module-a\' &&npm run serve-module \'/opt/module-workspace/to-module-b\' && node lib/server/index.js --root-module-name=frank-lloyd-root "'
Expand Down Expand Up @@ -315,6 +325,7 @@ describe('startApp', () => {
]);
expect(mockSpawn.calls[1].args.filter((arg) => arg.startsWith('-v='))).toEqual([
'-v=/process/env/location/extra_certs.pem:/opt/certs.pem',
'-v=/home/user/.one-app:/home/node/.one-app',
]);
});

Expand All @@ -331,6 +342,7 @@ describe('startApp', () => {
]);
expect(mockSpawn.calls[1].args.filter((arg) => arg.startsWith('-v='))).toEqual([
'-v=/envVar/location/cert.pem:/opt/certs.pem',
'-v=/home/user/.one-app:/home/node/.one-app',
]);
});

Expand Down Expand Up @@ -359,4 +371,139 @@ describe('startApp', () => {
'--inspect=0.0.0.0:9221'
);
});

it('ensures the user\'s One App directory exists', async () => {
expect.assertions(1);

const mockSpawn = makeMockSpawn();
childProcess.spawn.mockImplementation(mockSpawn);
await startApp({
moduleMapUrl: 'https://example.com/module-map.json',
rootModuleName: 'frank-lloyd-root',
appDockerImage: 'one-app:5.0.0',
modulesToServe: ['/path/to/module-a'],
});
expect(fs.promises.mkdir.mock.calls[0]).toEqual(['/home/user/.one-app']);
});

it('mounts the user\'s One App directory', async () => {
expect.assertions(1);

const mockSpawn = makeMockSpawn();
childProcess.spawn.mockImplementation(mockSpawn);
await startApp({
moduleMapUrl: 'https://example.com/module-map.json',
rootModuleName: 'frank-lloyd-root',
appDockerImage: 'one-app:5.0.0',
modulesToServe: ['/path/to/module-a'],
});
expect(mockSpawn.calls[1].args.filter((arg) => arg.startsWith('-v=/home/user/.one-app'))).toEqual([
'-v=/home/user/.one-app:/home/node/.one-app',
]);
});

it('shows a warning when there was an error creating the user\'s One App directory', async () => {
expect.assertions(2);

const mockSpawn = makeMockSpawn();
childProcess.spawn.mockImplementation(mockSpawn);
fs.promises.mkdir.mockImplementation(() => {
throw Object.assign(
new Error('EROFS: read-only file system, mkdir \'/home/user/.one-app\''),
{
errno: -30,
code: 'EROFS',
syscall: 'mkdir',
path: '/home/user/.one-app',
});
});
console.warn.mockClear();
await startApp({
moduleMapUrl: 'https://example.com/module-map.json',
rootModuleName: 'frank-lloyd-root',
appDockerImage: 'one-app:5.0.0',
modulesToServe: ['/path/to/module-a'],
});
expect(console.warn).toHaveBeenCalled();
expect(console.warn.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"Unable to ensure ~/.one-app exists, the module cache will not be used (EROFS: read-only file system, mkdir '/home/user/.one-app')",
]
`);
});

it('does not mount the user\'s One App directory when there was an error creating it', async () => {
expect.assertions(1);

const mockSpawn = makeMockSpawn();
childProcess.spawn.mockImplementation(mockSpawn);
fs.promises.mkdir.mockImplementation(() => {
throw Object.assign(
new Error('EROFS: read-only file system, mkdir \'/home/user/.one-app\''),
{
errno: -30,
code: 'EROFS',
syscall: 'mkdir',
path: '/home/user/.one-app',
});
});
await startApp({
moduleMapUrl: 'https://example.com/module-map.json',
rootModuleName: 'frank-lloyd-root',
appDockerImage: 'one-app:5.0.0',
modulesToServe: ['/path/to/module-a'],
});
expect(mockSpawn.calls[1].args.filter((arg) => arg.startsWith('-v=/home/user/.one-app'))).toEqual([]);
});

it('does not show a warning when the user\'s One App directory already exists', async () => {
expect.assertions(1);

const mockSpawn = makeMockSpawn();
childProcess.spawn.mockImplementation(mockSpawn);
fs.promises.mkdir.mockImplementation(() => {
throw Object.assign(
new Error('EEXIST: file already exists, mkdir \'/home/user/.one-app\''),
{
errno: -17,
code: 'EEXIST',
syscall: 'mkdir',
path: '/home/user/.one-app',
});
});
console.warn.mockClear();
await startApp({
moduleMapUrl: 'https://example.com/module-map.json',
rootModuleName: 'frank-lloyd-root',
appDockerImage: 'one-app:5.0.0',
modulesToServe: ['/path/to/module-a'],
});
expect(console.warn).not.toHaveBeenCalled();
});

it('mounts the user\'s One App directory when it already exists', async () => {
expect.assertions(1);

const mockSpawn = makeMockSpawn();
childProcess.spawn.mockImplementation(mockSpawn);
fs.promises.mkdir.mockImplementation(() => {
throw Object.assign(
new Error('EEXIST: file already exists, mkdir \'/home/user/.one-app\''),
{
errno: -17,
code: 'EEXIST',
syscall: 'mkdir',
path: '/home/user/.one-app',
});
});
await startApp({
moduleMapUrl: 'https://example.com/module-map.json',
rootModuleName: 'frank-lloyd-root',
appDockerImage: 'one-app:5.0.0',
modulesToServe: ['/path/to/module-a'],
});
expect(mockSpawn.calls[1].args.filter((arg) => arg.startsWith('-v=/home/user/.one-app'))).toEqual([
'-v=/home/user/.one-app:/home/node/.one-app',
]);
});
});
12 changes: 12 additions & 0 deletions packages/one-app-runner/src/startApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
const { spawn } = require('child_process');
const path = require('node:path');
const fs = require('node:fs');
const os = require('node:os');
const Docker = require('dockerode');

async function spawnAndPipe(command, args, logStream) {
Expand Down Expand Up @@ -206,6 +207,17 @@ module.exports = async function startApp({

const logFileStream = outputFile ? fs.createWriteStream(outputFile) : null;

const hostOneAppDirectoryPath = path.resolve(os.homedir(), '.one-app');
mounts.set(hostOneAppDirectoryPath, '/home/node/.one-app');
JAdshead marked this conversation as resolved.
Show resolved Hide resolved
try {
await fs.promises.mkdir(hostOneAppDirectoryPath);
} catch (errorCreatingOneAppDirectory) {
if (errorCreatingOneAppDirectory.code !== 'EEXIST') {
mounts.delete(hostOneAppDirectoryPath);
console.warn(`Unable to ensure ~/.one-app exists, the module cache will not be used (${errorCreatingOneAppDirectory.message})`);
}
}

try {
if (!offline) {
await dockerPull(appDockerImage, logFileStream);
Expand Down
Loading