@milaboratories/pl-deployments 1.1.4 → 1.1.5
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 +13 -13
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +301 -284
- package/dist/index.mjs.map +1 -1
- package/dist/ssh/ssh.d.ts +2 -2
- package/dist/ssh/ssh.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/ssh/__tests__/common-utils.ts +1 -1
- package/src/ssh/ssh.ts +117 -54
package/src/ssh/ssh.ts
CHANGED
|
@@ -5,7 +5,8 @@ import dns from 'dns';
|
|
|
5
5
|
import fs from 'fs';
|
|
6
6
|
import { readFile } from 'fs/promises';
|
|
7
7
|
import upath from 'upath';
|
|
8
|
-
import type
|
|
8
|
+
import { RetryablePromise, type MiLogger } from '@milaboratories/ts-helpers';
|
|
9
|
+
import { randomBytes } from 'crypto';
|
|
9
10
|
|
|
10
11
|
const defaultConfig: ConnectConfig = {
|
|
11
12
|
keepaliveInterval: 60000,
|
|
@@ -60,16 +61,7 @@ export class SshClient {
|
|
|
60
61
|
*/
|
|
61
62
|
public async connect(config: ConnectConfig) {
|
|
62
63
|
this.config = config;
|
|
63
|
-
return
|
|
64
|
-
this.client.on('ready', () => {
|
|
65
|
-
resolve(undefined);
|
|
66
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
67
|
-
}).on('error', (err: any) => {
|
|
68
|
-
reject(new Error(`ssh.connect: error occurred: ${err}`));
|
|
69
|
-
}).on('timeout', () => {
|
|
70
|
-
reject(new Error(`timeout was occurred while waiting for SSH connection.`));
|
|
71
|
-
}).connect(config);
|
|
72
|
-
});
|
|
64
|
+
return await connect(this.client, config, () => {}, () => {});
|
|
73
65
|
}
|
|
74
66
|
|
|
75
67
|
/**
|
|
@@ -154,61 +146,95 @@ export class SshClient {
|
|
|
154
146
|
* @returns { server: net.Server } A promise resolving with the created server instance.
|
|
155
147
|
*/
|
|
156
148
|
public async forwardPort(ports: { remotePort: number; localPort: number; localHost?: string }, config?: ConnectConfig): Promise<{ server: net.Server }> {
|
|
149
|
+
const log = `ssh.forward:${ports.localPort}:${ports.remotePort}.id_${randomBytes(1).toString('hex')}`;
|
|
157
150
|
config = config ?? this.config;
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
(err, stream) => {
|
|
170
|
-
if (err) {
|
|
171
|
-
console.error('Error opening SSH channel:', err.message);
|
|
172
|
-
localSocket.end();
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
localSocket.pipe(stream);
|
|
176
|
-
stream.pipe(localSocket);
|
|
177
|
-
localSocket.resume();
|
|
178
|
-
},
|
|
179
|
-
);
|
|
151
|
+
|
|
152
|
+
// we make this thing persistent so that if the connection
|
|
153
|
+
// drops (it happened in the past because of lots of errors and forwardOut opened channels),
|
|
154
|
+
// we'll recreate it here.
|
|
155
|
+
const persistentClient = new RetryablePromise((p: RetryablePromise<Client>) => {
|
|
156
|
+
return new Promise<Client>((resolve, reject) => {
|
|
157
|
+
const client = new Client();
|
|
158
|
+
|
|
159
|
+
client.on('ready', () => {
|
|
160
|
+
this.logger.info(`${log}.client.ready`);
|
|
161
|
+
resolve(client);
|
|
180
162
|
});
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
163
|
+
|
|
164
|
+
client.on('error', (err) => {
|
|
165
|
+
this.logger.info(`${log}.client.error: ${err}`);
|
|
166
|
+
p.reset();
|
|
167
|
+
reject(err);
|
|
184
168
|
});
|
|
185
169
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
reject(new Error(`ssh.forwardPort: server error: ${err}`));
|
|
170
|
+
client.on('close', () => {
|
|
171
|
+
this.logger.info(`${log}.client.closed`);
|
|
172
|
+
p.reset();
|
|
190
173
|
});
|
|
191
174
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
175
|
+
client.connect(config!);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
await persistentClient.ensure(); // warm up a connection
|
|
180
|
+
|
|
181
|
+
return new Promise((resolve, reject) => {
|
|
182
|
+
const server = net.createServer({ pauseOnConnect: true }, async (localSocket) => {
|
|
183
|
+
const sockLog = `${log}.sock_${randomBytes(1).toString('hex')}`;
|
|
184
|
+
// this.logger.info(`${sockLog}.localSocket: start connection`);
|
|
185
|
+
let conn: Client;
|
|
186
|
+
try {
|
|
187
|
+
conn = await persistentClient.ensure();
|
|
188
|
+
} catch (e: unknown) {
|
|
189
|
+
this.logger.info(`${sockLog}.persistentClient.catch: ${e}`);
|
|
190
|
+
localSocket.end();
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
let stream: ClientChannel;
|
|
195
|
+
try {
|
|
196
|
+
stream = await forwardOut(this.logger, conn, '127.0.0.1', 0, '127.0.0.1', ports.remotePort);
|
|
197
|
+
} catch (e: unknown) {
|
|
198
|
+
this.logger.error(`${sockLog}.forwardOut.err: ${e}`);
|
|
199
|
+
localSocket.end();
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
localSocket.pipe(stream);
|
|
204
|
+
stream.pipe(localSocket);
|
|
205
|
+
localSocket.resume();
|
|
206
|
+
// this.logger.info(`${sockLog}.forwardOut: connected`);
|
|
207
|
+
|
|
208
|
+
stream.on('error', (err: unknown) => {
|
|
209
|
+
this.logger.error(`${sockLog}.stream.error: ${err}`);
|
|
210
|
+
localSocket.end();
|
|
211
|
+
stream.end();
|
|
212
|
+
});
|
|
213
|
+
stream.on('close', () => {
|
|
214
|
+
// this.logger.info(`${sockLog}.stream.close: closed`);
|
|
215
|
+
localSocket.end();
|
|
216
|
+
stream.end();
|
|
217
|
+
});
|
|
218
|
+
localSocket.on('close', () => {
|
|
219
|
+
this.logger.info(`${sockLog}.localSocket: closed`);
|
|
220
|
+
localSocket.end();
|
|
221
|
+
stream.end();
|
|
198
222
|
});
|
|
199
223
|
});
|
|
200
224
|
|
|
201
|
-
|
|
202
|
-
this.logger.
|
|
203
|
-
server
|
|
204
|
-
reject(`ssh.forwardPort: conn.err: ${err}`);
|
|
225
|
+
server.listen(ports.localPort, '127.0.0.1', () => {
|
|
226
|
+
this.logger.info(`${log}.server: started listening`);
|
|
227
|
+
resolve({ server });
|
|
205
228
|
});
|
|
206
229
|
|
|
207
|
-
|
|
208
|
-
|
|
230
|
+
server.on('error', (err) => {
|
|
231
|
+
server.close();
|
|
232
|
+
reject(new Error(`${log}.server: error: ${JSON.stringify(err)}`));
|
|
209
233
|
});
|
|
210
234
|
|
|
211
|
-
|
|
235
|
+
server.on('close', () => {
|
|
236
|
+
this.logger.info(`${log}.server: closed ${JSON.stringify(ports)}`);
|
|
237
|
+
});
|
|
212
238
|
});
|
|
213
239
|
}
|
|
214
240
|
|
|
@@ -440,7 +466,7 @@ export class SshClient {
|
|
|
440
466
|
|
|
441
467
|
public uploadFileUsingExistingSftp(sftp: SFTPWrapper, localPath: string, remotePath: string, mode: number = 0o660) {
|
|
442
468
|
return new Promise((resolve, reject) => {
|
|
443
|
-
readFile(localPath).then(async (result) => {
|
|
469
|
+
readFile(localPath).then(async (result: Buffer) => {
|
|
444
470
|
this.writeFile(sftp, remotePath, result, mode)
|
|
445
471
|
.then(() => {
|
|
446
472
|
resolve(undefined);
|
|
@@ -598,3 +624,40 @@ export class SshClient {
|
|
|
598
624
|
}
|
|
599
625
|
|
|
600
626
|
export type SshExecResult = { stdout: string; stderr: string };
|
|
627
|
+
|
|
628
|
+
async function connect(
|
|
629
|
+
client: Client,
|
|
630
|
+
config: ConnectConfig,
|
|
631
|
+
onError: (e: unknown) => void,
|
|
632
|
+
onClose: () => void,
|
|
633
|
+
): Promise<Client> {
|
|
634
|
+
return new Promise((resolve, reject) => {
|
|
635
|
+
client.on('ready', () => {
|
|
636
|
+
resolve(client);
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
client.on('error', (err: unknown) => {
|
|
640
|
+
onError(err);
|
|
641
|
+
reject(new Error(`ssh.connect: error occurred: ${err}`));
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
client.on('close', () => {
|
|
645
|
+
onClose();
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
client.connect(config);
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
async function forwardOut(logger: MiLogger, conn: Client, localHost: string, localPort: number, remoteHost: string, remotePort: number): Promise<ClientChannel> {
|
|
653
|
+
return new Promise((resolve, reject) => {
|
|
654
|
+
conn.forwardOut(localHost, localPort, remoteHost, remotePort, (err, stream) => {
|
|
655
|
+
if (err) {
|
|
656
|
+
logger.error(`forwardOut.error: ${err}`);
|
|
657
|
+
return reject(err);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
return resolve(stream);
|
|
661
|
+
});
|
|
662
|
+
});
|
|
663
|
+
}
|