@milaboratories/pl-deployments 1.2.1 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +12 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +270 -243
- package/dist/index.mjs.map +1 -1
- package/dist/ssh/pl.d.ts +3 -1
- package/dist/ssh/pl.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/common/pl_binary_download.ts +1 -1
- package/src/ssh/__tests__/ssh-docker.test.ts +1 -1
- package/src/ssh/pl.test.ts +26 -0
- package/src/ssh/pl.ts +48 -6
- package/src/ssh/ssh.ts +5 -5
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@milaboratories/pl-deployments",
|
|
3
|
-
"version": "1.2.
|
|
4
|
-
"pl-version": "1.
|
|
3
|
+
"version": "1.2.3",
|
|
4
|
+
"pl-version": "1.24.0",
|
|
5
5
|
"description": "MiLaboratories Platforma Backend code service run wrapper",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
7
|
"main": "./dist/index.js",
|
|
@@ -29,11 +29,11 @@
|
|
|
29
29
|
"@types/jest": "^29.5.14",
|
|
30
30
|
"@types/node": "~20.16.15",
|
|
31
31
|
"@types/ssh2": "^1.15.1",
|
|
32
|
-
"eslint": "^9.
|
|
32
|
+
"eslint": "^9.21.0",
|
|
33
33
|
"jest": "^29.7.0",
|
|
34
34
|
"prettier": "^3.4.1",
|
|
35
|
-
"testcontainers": "^10.
|
|
36
|
-
"ts-jest": "^29.2.
|
|
35
|
+
"testcontainers": "^10.18.0",
|
|
36
|
+
"ts-jest": "^29.2.6",
|
|
37
37
|
"tsconfig-paths": "^4.2.0",
|
|
38
38
|
"typescript": "~5.5.4",
|
|
39
39
|
"utility-types": "^3.11.0",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"upath": "^2.0.1",
|
|
48
48
|
"ssh2": "^1.16.0",
|
|
49
49
|
"tar": "^7.4.3",
|
|
50
|
-
"undici": "~7.
|
|
50
|
+
"undici": "~7.4.0",
|
|
51
51
|
"yaml": "^2.6.1",
|
|
52
52
|
"zod": "~3.23.8",
|
|
53
53
|
"@milaboratories/pl-config": "^1.4.3",
|
|
@@ -153,7 +153,7 @@ export async function downloadArchive(
|
|
|
153
153
|
|
|
154
154
|
return state;
|
|
155
155
|
} catch (e: unknown) {
|
|
156
|
-
const msg = `downloadArchive:
|
|
156
|
+
const msg = `downloadArchive: ${JSON.stringify(e)}, state: ${JSON.stringify(state)}`;
|
|
157
157
|
logger.error(msg);
|
|
158
158
|
throw new Error(msg);
|
|
159
159
|
}
|
|
@@ -220,7 +220,7 @@ describe('sshConnect', () => {
|
|
|
220
220
|
});
|
|
221
221
|
|
|
222
222
|
it('should timeout if the server is unreachable', async () => {
|
|
223
|
-
await expect(SshClient.init(new ConsoleLoggerAdapter(), { ...getConnectionForSsh(testContainer), port: 3233 })).rejects.toThrow('ssh.connect:
|
|
223
|
+
await expect(SshClient.init(new ConsoleLoggerAdapter(), { ...getConnectionForSsh(testContainer), port: 3233 })).rejects.toThrow('ssh.connect: AggregateError');
|
|
224
224
|
});
|
|
225
225
|
});
|
|
226
226
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { parseGlibcVersion } from './pl';
|
|
2
|
+
import { describe, it, expect } from 'vitest';
|
|
3
|
+
|
|
4
|
+
describe('parseGlibcVersion', () => {
|
|
5
|
+
it('correctly parses glibc version from ldd output', () => {
|
|
6
|
+
// Standard GNU libc outputs
|
|
7
|
+
expect(parseGlibcVersion('ldd (GNU libc) 2.28')).toBe(2.28);
|
|
8
|
+
expect(parseGlibcVersion('ldd (GNU libc) 2.39')).toBe(2.39);
|
|
9
|
+
|
|
10
|
+
// Ubuntu-style output
|
|
11
|
+
expect(parseGlibcVersion('ldd (Ubuntu GLIBC 2.31-0ubuntu9.9) 2.31')).toBe(2.31);
|
|
12
|
+
|
|
13
|
+
// Debian-style output
|
|
14
|
+
expect(parseGlibcVersion('ldd (Debian GLIBC 2.28-10) 2.28')).toBe(2.28);
|
|
15
|
+
|
|
16
|
+
// Different formatting with extra text
|
|
17
|
+
expect(parseGlibcVersion('ldd version 2.35, Copyright (C) 2022 Free Software Foundation, Inc.')).toBe(2.35);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('throws error when glibc version cannot be parsed', () => {
|
|
21
|
+
// Invalid outputs
|
|
22
|
+
expect(() => parseGlibcVersion('ldd: command not found')).toThrow();
|
|
23
|
+
expect(() => parseGlibcVersion('some random output')).toThrow();
|
|
24
|
+
expect(() => parseGlibcVersion('')).toThrow();
|
|
25
|
+
});
|
|
26
|
+
});
|
package/src/ssh/pl.ts
CHANGED
|
@@ -9,7 +9,7 @@ import * as plpath from './pl_paths';
|
|
|
9
9
|
import { getDefaultPlVersion } from '../common/pl_version';
|
|
10
10
|
|
|
11
11
|
import net from 'net';
|
|
12
|
-
import type { PlLicenseMode, SshPlConfigGenerationResult } from '@milaboratories/pl-config';
|
|
12
|
+
import type { PlConfig, PlLicenseMode, SshPlConfigGenerationResult } from '@milaboratories/pl-config';
|
|
13
13
|
import { generateSshPlConfigs, getFreePort } from '@milaboratories/pl-config';
|
|
14
14
|
import type { SupervisorStatus } from './supervisord';
|
|
15
15
|
import { supervisorStatus, supervisorStop as supervisorCtlShutdown, generateSupervisordConfig, supervisorCtlStart } from './supervisord';
|
|
@@ -17,6 +17,8 @@ import type { ConnectionInfo, SshPlPorts } from './connection_info';
|
|
|
17
17
|
import { newConnectionInfo, parseConnectionInfo, stringifyConnectionInfo } from './connection_info';
|
|
18
18
|
import type { PlBinarySourceDownload } from '../common/pl_binary';
|
|
19
19
|
|
|
20
|
+
const minRequiredGlibcVersion = 2.28;
|
|
21
|
+
|
|
20
22
|
export class SshPl {
|
|
21
23
|
private initState: PlatformaInitState = {};
|
|
22
24
|
constructor(
|
|
@@ -67,7 +69,7 @@ export class SshPl {
|
|
|
67
69
|
return await this.checkIsAliveWithInterval();
|
|
68
70
|
}
|
|
69
71
|
} catch (e: unknown) {
|
|
70
|
-
const msg = `SshPl.start:
|
|
72
|
+
const msg = `SshPl.start: ${e}`;
|
|
71
73
|
this.logger.error(msg);
|
|
72
74
|
throw new Error(msg);
|
|
73
75
|
}
|
|
@@ -85,7 +87,7 @@ export class SshPl {
|
|
|
85
87
|
return await this.checkIsAliveWithInterval(undefined, undefined, false);
|
|
86
88
|
}
|
|
87
89
|
} catch (e: unknown) {
|
|
88
|
-
const msg = `PlSsh.stop:
|
|
90
|
+
const msg = `PlSsh.stop: ${e}`;
|
|
89
91
|
this.logger.error(msg);
|
|
90
92
|
throw new Error(msg);
|
|
91
93
|
}
|
|
@@ -124,6 +126,7 @@ export class SshPl {
|
|
|
124
126
|
state.plBinaryOps = ops.plBinary;
|
|
125
127
|
state.arch = await this.getArch();
|
|
126
128
|
state.remoteHome = await this.getUserHomeDirectory();
|
|
129
|
+
|
|
127
130
|
state.alive = await this.isAlive();
|
|
128
131
|
|
|
129
132
|
if (state.alive.allAlive) {
|
|
@@ -142,6 +145,10 @@ export class SshPl {
|
|
|
142
145
|
await this.stop();
|
|
143
146
|
}
|
|
144
147
|
|
|
148
|
+
const glibcVersion = await getGlibcVersion(this.logger, this.sshClient);
|
|
149
|
+
if (glibcVersion < minRequiredGlibcVersion)
|
|
150
|
+
throw new Error(`glibc version ${glibcVersion} is too old. Version ${minRequiredGlibcVersion} or higher is required for Platforma.`);
|
|
151
|
+
|
|
145
152
|
const downloadRes = await this.downloadBinariesAndUploadToTheServer(
|
|
146
153
|
ops.localWorkdir, ops.plBinary!, state.remoteHome, state.arch,
|
|
147
154
|
);
|
|
@@ -172,6 +179,7 @@ export class SshPl {
|
|
|
172
179
|
},
|
|
173
180
|
licenseMode: ops.license,
|
|
174
181
|
useGlobalAccess: notEmpty(ops.useGlobalAccess),
|
|
182
|
+
plConfigPostprocessing: ops.plConfigPostprocessing,
|
|
175
183
|
});
|
|
176
184
|
state.generatedConfig = { ...config, filesToCreate: { skipped: 'it is too wordy' } };
|
|
177
185
|
|
|
@@ -218,7 +226,7 @@ export class SshPl {
|
|
|
218
226
|
|
|
219
227
|
return state.connectionInfo;
|
|
220
228
|
} catch (e: unknown) {
|
|
221
|
-
const msg = `SshPl.platformaInit:
|
|
229
|
+
const msg = `SshPl.platformaInit: ${e}, state: ${JSON.stringify(state)}`;
|
|
222
230
|
this.logger.error(msg);
|
|
223
231
|
|
|
224
232
|
throw new Error(msg);
|
|
@@ -259,7 +267,7 @@ export class SshPl {
|
|
|
259
267
|
downloadedPl: plpath.platformaBin(remoteHome, arch.arch),
|
|
260
268
|
};
|
|
261
269
|
} catch (e: unknown) {
|
|
262
|
-
const msg = `SshPl.downloadBinariesAndUploadToServer:
|
|
270
|
+
const msg = `SshPl.downloadBinariesAndUploadToServer: ${e}, state: ${JSON.stringify(state)}`;
|
|
263
271
|
this.logger.error(msg);
|
|
264
272
|
throw e;
|
|
265
273
|
}
|
|
@@ -312,13 +320,19 @@ export class SshPl {
|
|
|
312
320
|
await this.sshClient.uploadFile(state.localArchivePath, state.remoteArchivePath);
|
|
313
321
|
state.uploadDone = true;
|
|
314
322
|
|
|
323
|
+
try {
|
|
324
|
+
await this.sshClient.exec('hash tar');
|
|
325
|
+
} catch (_) {
|
|
326
|
+
throw new Error(`tar is not installed on the server. Please install it before running Platforma.`);
|
|
327
|
+
}
|
|
328
|
+
|
|
315
329
|
// TODO: Create a proper archive to avoid xattr warnings
|
|
316
330
|
const untarResult = await this.sshClient.exec(
|
|
317
331
|
`tar --warning=no-all -xvf ${state.remoteArchivePath} --directory=${state.remoteDir}`,
|
|
318
332
|
);
|
|
319
333
|
|
|
320
334
|
if (untarResult.stderr)
|
|
321
|
-
throw Error(`downloadAndUntar: untar: stderr occurred: ${untarResult.stderr}, stdout: ${untarResult.stdout}`);
|
|
335
|
+
throw new Error(`downloadAndUntar: untar: stderr occurred: ${untarResult.stderr}, stdout: ${untarResult.stdout}`);
|
|
322
336
|
|
|
323
337
|
state.untarDone = true;
|
|
324
338
|
|
|
@@ -441,6 +455,7 @@ export type SshPlConfig = {
|
|
|
441
455
|
license: PlLicenseMode;
|
|
442
456
|
useGlobalAccess?: boolean;
|
|
443
457
|
plBinary?: PlBinarySourceDownload;
|
|
458
|
+
plConfigPostprocessing?: (config: PlConfig) => PlConfig;
|
|
444
459
|
};
|
|
445
460
|
|
|
446
461
|
const defaultSshPlConfig: Pick<
|
|
@@ -489,3 +504,30 @@ type PlatformaInitState = {
|
|
|
489
504
|
connectionInfo?: ConnectionInfo;
|
|
490
505
|
started?: boolean;
|
|
491
506
|
};
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Gets the glibc version on the remote system
|
|
510
|
+
* @returns The glibc version as a number
|
|
511
|
+
* @throws Error if version cannot be determined
|
|
512
|
+
*/
|
|
513
|
+
async function getGlibcVersion(logger: MiLogger, sshClient: SshClient): Promise <number> {
|
|
514
|
+
try {
|
|
515
|
+
const { stdout, stderr } = await sshClient.exec('ldd --version | head -n 1');
|
|
516
|
+
if(stderr) {
|
|
517
|
+
throw new Error(`Failed to check glibc version: ${stderr}`);
|
|
518
|
+
}
|
|
519
|
+
return parseGlibcVersion(stdout);
|
|
520
|
+
} catch(e: unknown) {
|
|
521
|
+
logger.error(`glibc version check failed: ${e}`);
|
|
522
|
+
throw e;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
export function parseGlibcVersion(output: string): number {
|
|
527
|
+
const versionMatch = output.match(/\d+\.\d+/);
|
|
528
|
+
if (!versionMatch) {
|
|
529
|
+
throw new Error(`Could not parse glibc version from: ${output}`);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return parseFloat(versionMatch[0]);
|
|
533
|
+
}
|
package/src/ssh/ssh.ts
CHANGED
|
@@ -79,7 +79,7 @@ export class SshClient {
|
|
|
79
79
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
80
80
|
this.client.exec(command, (err: any, stream: ClientChannel) => {
|
|
81
81
|
if (err) {
|
|
82
|
-
return reject(`ssh.exec: ${command}
|
|
82
|
+
return reject(`ssh.exec: ${command}: ${err}`);
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
let stdout = '';
|
|
@@ -89,7 +89,7 @@ export class SshClient {
|
|
|
89
89
|
if (code === 0) {
|
|
90
90
|
resolve({ stdout, stderr });
|
|
91
91
|
} else {
|
|
92
|
-
reject(new Error(`Command ${command} exited with code ${code}`));
|
|
92
|
+
reject(new Error(`Command ${command} exited with code ${code}, stdout: ${stdout}, stderr: ${stderr}`));
|
|
93
93
|
}
|
|
94
94
|
}).on('data', (data: ArrayBuffer) => {
|
|
95
95
|
stdout += data.toString();
|
|
@@ -419,7 +419,7 @@ export class SshClient {
|
|
|
419
419
|
return new Promise((resolve, reject) => {
|
|
420
420
|
sftp.readFile(remotePath, (err, buffer) => {
|
|
421
421
|
if (err) {
|
|
422
|
-
return reject(new Error(`ssh.readFile:
|
|
422
|
+
return reject(new Error(`ssh.readFile: ${err}`));
|
|
423
423
|
}
|
|
424
424
|
resolve(buffer.toString());
|
|
425
425
|
});
|
|
@@ -495,7 +495,7 @@ export class SshClient {
|
|
|
495
495
|
resolve(undefined);
|
|
496
496
|
})
|
|
497
497
|
.catch((err) => {
|
|
498
|
-
const msg = `uploadFileUsingExistingSftp:
|
|
498
|
+
const msg = `uploadFileUsingExistingSftp: ${err}`;
|
|
499
499
|
this.logger.error(msg);
|
|
500
500
|
reject(new Error(msg));
|
|
501
501
|
});
|
|
@@ -666,7 +666,7 @@ async function connect(
|
|
|
666
666
|
|
|
667
667
|
client.on('error', (err: unknown) => {
|
|
668
668
|
onError(err);
|
|
669
|
-
reject(new Error(`ssh.connect:
|
|
669
|
+
reject(new Error(`ssh.connect: ${err}`));
|
|
670
670
|
});
|
|
671
671
|
|
|
672
672
|
client.on('close', () => {
|