@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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@milaboratories/pl-deployments",
3
- "version": "1.2.1",
4
- "pl-version": "1.23.0",
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.16.0",
32
+ "eslint": "^9.21.0",
33
33
  "jest": "^29.7.0",
34
34
  "prettier": "^3.4.1",
35
- "testcontainers": "^10.16.0",
36
- "ts-jest": "^29.2.5",
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.2.3",
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: error ${JSON.stringify(e)} occurred, state: ${JSON.stringify(state)}`;
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: error occurred: AggregateError');
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: error occurred ${e}`;
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: error occurred ${e}`;
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: error occurred: ${e}, state: ${JSON.stringify(state)}`;
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: error ${e} occurred, state: ${JSON.stringify(state)}`;
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}, error occurred: ${err}`);
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: err occurred ${err}`));
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: error ${err} occurred`;
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: error occurred: ${err}`));
669
+ reject(new Error(`ssh.connect: ${err}`));
670
670
  });
671
671
 
672
672
  client.on('close', () => {