@milaboratories/pl-deployments 1.1.2 → 1.1.4

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.
@@ -12,7 +12,7 @@ let testContainer: StartedTestContainer;
12
12
  beforeAll(async () => {
13
13
  testContainer = await initContainer('ssh');
14
14
  client = await SshClient.init(new ConsoleLoggerAdapter(), getConnectionForSsh(testContainer));
15
- });
15
+ }, 200000);
16
16
 
17
17
  describe('SSH Tests', () => {
18
18
  it('isPassphraseRequiredForKey', async () => {
@@ -131,6 +131,25 @@ describe('SSH Tests', () => {
131
131
  server.close();
132
132
  });
133
133
 
134
+ it('Remove directory', async () => {
135
+ const rootFolder = '/home/pl-doctor/upload';
136
+ await client.createRemoteDirectory(`${rootFolder}/upload/nested`);
137
+ await client.createRemoteDirectory(`${rootFolder}/2-nested`);
138
+ await client.createRemoteDirectory(`${rootFolder}/2-nested/3-sub`);
139
+ await client.writeFileOnTheServer(`${rootFolder}/2-nested/3-sub/qwerty.txt`, 'HELLO FROM SSH');
140
+
141
+ const text = await client.readFile(`${rootFolder}/2-nested/3-sub/qwerty.txt`);
142
+ expect(text).toBe('HELLO FROM SSH');
143
+
144
+ let data = await client.checkPathExists(rootFolder);
145
+ expect(data.exists).toBe(true);
146
+
147
+ await client.deleteFolder(rootFolder);
148
+
149
+ data = await client.checkPathExists(rootFolder);
150
+ expect(data.exists).toBe(false);
151
+ });
152
+
134
153
  it('Auth types', async () => {
135
154
  const hostData = getContainerHostAndPort(testContainer);
136
155
  const types = await SshClient.getAuthTypes(hostData.host, hostData.port);
@@ -148,7 +167,8 @@ describe('sshConnect', () => {
148
167
  await expect(SshClient.init(new ConsoleLoggerAdapter(), { ...getConnectionForSsh(testContainer), username: 'dfasdfa' })).rejects.toThrow();
149
168
  });
150
169
 
151
- it('should fail with invalid passphrase', async () => {
170
+ // FIXME
171
+ it.skip('should fail with invalid passphrase', async () => {
152
172
  let catched = false;
153
173
  try {
154
174
  await SshClient.init(new ConsoleLoggerAdapter(), { ...getConnectionForSsh(testContainer), passphrase: 'dfasdfa' });
@@ -225,4 +245,4 @@ describe('sshExec', () => {
225
245
 
226
246
  afterAll(async () => {
227
247
  await cleanUp(testContainer);
228
- });
248
+ }, 200000);
package/src/ssh/pl.ts CHANGED
@@ -60,7 +60,7 @@ export class SshPl {
60
60
  // We are waiting for Platforma to run to ensure that it has started.
61
61
  return await this.checkIsAliveWithInterval();
62
62
  } catch (e: unknown) {
63
- const msg = `ssh.start: error occurred ${e}`
63
+ const msg = `ssh.start: error occurred ${e}`;
64
64
  this.logger.error(msg);
65
65
  throw new Error(msg);
66
66
  }
@@ -74,12 +74,24 @@ export class SshPl {
74
74
  await supervisorCtlShutdown(this.sshClient, remoteHome, arch.arch);
75
75
  return await this.checkIsAliveWithInterval(undefined, undefined, false);
76
76
  } catch (e: unknown) {
77
- const msg = `ssh.stop: error occurred ${e}`
77
+ const msg = `ssh.stop: error occurred ${e}`;
78
78
  this.logger.error(msg);
79
- throw new Error(msg)
79
+ throw new Error(msg);
80
80
  }
81
81
  }
82
82
 
83
+ public async reset(): Promise<boolean> {
84
+ const workDir = await this.getUserHomeDirectory();
85
+
86
+ this.logger.info(`pl.reset: Stop Platforma on the server`);
87
+ await this.stop();
88
+
89
+ this.logger.info(`pl.reset: Deleting Platforma workDir ${workDir} on the server`);
90
+ await this.sshClient.deleteFolder(plpath.workDir(workDir));
91
+
92
+ return true;
93
+ }
94
+
83
95
  public async platformaInit(localWorkdir: string): Promise<SshInitReturnTypes> {
84
96
  const state: PlatformaInitState = { localWorkdir };
85
97
 
@@ -232,7 +244,6 @@ export class SshPl {
232
244
  softwareName: string,
233
245
  tgzName: string,
234
246
  ): Promise<DownloadAndUntarState> {
235
-
236
247
  const state: DownloadAndUntarState = {};
237
248
  state.binBasePath = plpath.binariesDir(remoteHome);
238
249
  await this.sshClient.createRemoteDirectory(state.binBasePath);
@@ -242,7 +253,7 @@ export class SshPl {
242
253
  const attempts = 5;
243
254
  for (let i = 1; i <= attempts; i++) {
244
255
  try {
245
- downloadBinaryResult = await downloadBinaryNoExtract(
256
+ downloadBinaryResult = await downloadBinaryNoExtract(
246
257
  this.logger,
247
258
  localWorkdir,
248
259
  softwareName,
@@ -250,7 +261,7 @@ export class SshPl {
250
261
  arch.arch, arch.platform,
251
262
  );
252
263
  break;
253
- } catch(e: unknown) {
264
+ } catch (e: unknown) {
254
265
  await sleep(300);
255
266
  if (i == attempts) {
256
267
  throw new Error(`downloadAndUntar: ${attempts} attempts, last error: ${e}`);
package/src/ssh/ssh.ts CHANGED
@@ -10,10 +10,14 @@ import type { MiLogger } from '@milaboratories/ts-helpers';
10
10
  const defaultConfig: ConnectConfig = {
11
11
  keepaliveInterval: 60000,
12
12
  keepaliveCountMax: 10,
13
- }
13
+ };
14
14
 
15
15
  export type SshAuthMethods = 'publickey' | 'password';
16
16
  export type SshAuthMethodsResult = SshAuthMethods[];
17
+ export type SshDirContent = {
18
+ files: string[];
19
+ directories: string[];
20
+ };
17
21
 
18
22
  export class SshClient {
19
23
  private config?: ConnectConfig;
@@ -32,7 +36,7 @@ export class SshClient {
32
36
  public static async init(logger: MiLogger, config: ConnectConfig): Promise<SshClient> {
33
37
  const withDefaults = {
34
38
  ...defaultConfig,
35
- ...config
39
+ ...config,
36
40
  };
37
41
 
38
42
  const client = new SshClient(logger, new Client());
@@ -41,6 +45,14 @@ export class SshClient {
41
45
  return client;
42
46
  }
43
47
 
48
+ public getFullHostName() {
49
+ return `${this.config?.host}:${this.config?.port}`;
50
+ }
51
+
52
+ public getUserName() {
53
+ return this.config?.username;
54
+ }
55
+
44
56
  /**
45
57
  * Connects to the SSH server using the specified configuration.
46
58
  * @param config - The connection configuration object for the SSH client.
@@ -286,6 +298,73 @@ export class SshClient {
286
298
  });
287
299
  }
288
300
 
301
+ public async getForderStructure(sftp: SFTPWrapper, remotePath: string, data: SshDirContent = { files: [], directories: [] }): Promise<SshDirContent> {
302
+ return new Promise((resolve, reject) => {
303
+ sftp.readdir(remotePath, async (err, items) => {
304
+ if (err) {
305
+ return reject(err);
306
+ }
307
+
308
+ for (const item of items) {
309
+ const itemPath = `${remotePath}/${item.filename}`;
310
+ if (item.attrs.isDirectory()) {
311
+ data.directories.push(itemPath);
312
+ try {
313
+ await this.getForderStructure(sftp, itemPath, data);
314
+ } catch (error) {
315
+ return reject(error);
316
+ }
317
+ } else {
318
+ data.files.push(itemPath);
319
+ }
320
+ }
321
+ resolve(data);
322
+ });
323
+ });
324
+ }
325
+
326
+ public rmdir(sftp: SFTPWrapper, path: string) {
327
+ return new Promise((resolve, reject) => {
328
+ sftp.rmdir(path, (err) => err ? reject(err) : resolve(true));
329
+ });
330
+ }
331
+
332
+ public unlink(sftp: SFTPWrapper, path: string) {
333
+ return new Promise((resolve, reject) => {
334
+ sftp.unlink(path, (err) => err ? reject(err) : resolve(true));
335
+ });
336
+ }
337
+
338
+ public async deleteFolder(path: string) {
339
+ return this.withSftp(async (sftp) => {
340
+ try {
341
+ const list = await this.getForderStructure(sftp, path);
342
+ this.logger.info(`ssh.deleteFolder list of files and directories`);
343
+ this.logger.info(`ssh.deleteFolder list of files: ${list.files}`);
344
+ this.logger.info(`ssh.deleteFolder list of directories: ${list.directories}`);
345
+
346
+ for (const filePath of list.files) {
347
+ this.logger.info(`ssh.deleteFolder unlink file ${filePath}`);
348
+ await this.unlink(sftp, filePath);
349
+ }
350
+
351
+ list.directories.sort((a, b) => b.length - a.length);
352
+
353
+ for (const directoryPath of list.directories) {
354
+ this.logger.info(`ssh.deleteFolder rmdir ${directoryPath}`);
355
+ await this.rmdir(sftp, directoryPath);
356
+ }
357
+
358
+ await this.rmdir(sftp, path);
359
+ return true;
360
+ } catch (e: unknown) {
361
+ this.logger.error(e);
362
+ const message = e instanceof Error ? e.message : '';
363
+ throw new Error(`ssh.deleteFolder: path: ${path}, message: ${message}`);
364
+ }
365
+ });
366
+ }
367
+
289
368
  public async readFile(remotePath: string): Promise<string> {
290
369
  return this.withSftp(async (sftp) => {
291
370
  return new Promise((resolve, reject) => {