ssh-client-wrapper 2.2.10 → 2.4.0

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/lib/fork.js CHANGED
@@ -69,7 +69,7 @@ function createMasterPty (hostInfo) {
69
69
  hostInfo.masterPty = ptyProcess;
70
70
  }
71
71
 
72
- async function fork (hostInfo, cmd, args, timeout, outputCallback) {
72
+ async function fork (hostInfo, cmd, args, timeout, outputCallback, retryableExitCodes = []) {
73
73
  return new Promise((resolve, reject) => {
74
74
  debug(`cmd=${cmd}: args=${args}`);
75
75
  const ptyProcess = spawn(cmd, args, nodePtyOpt);
@@ -119,6 +119,9 @@ async function fork (hostInfo, cmd, args, timeout, outputCallback) {
119
119
  }
120
120
  const err = new Error("exit with non-zero");
121
121
  err.rt = exitCode;
122
+ if (retryableExitCodes.includes(exitCode)) {
123
+ err.retryable = true;
124
+ }
122
125
  return reject(err);
123
126
  });
124
127
  });
package/lib/index.js CHANGED
@@ -27,8 +27,7 @@ const { isArrayOfString, sanityCheck } = require("./util.js");
27
27
  * @param {Integer} hostInfo.ControlPersist=180 - how long keep master connection after last client connection has been closed (sec).
28
28
  * @param {Integer} hostInfo.ConnectTimeout=60 - timeout used when connectiong to ths SSH server
29
29
  * @param {Integer} hostInfo.maxRetry=3 - max number of retry
30
- * @param {Integer} hostInfo.retryMinTimeout=60 - minimum duration before first retry
31
- * @param {Integer} hostInfo.retryMaxTimeout=300 - maximum duration between each retry
30
+ * @param {Integer} hostInfo.retryDuration=1000 - duration time (msec) between each retry
32
31
  * @param {string[]} hostInfo.sshOpt - additional options for ssh
33
32
  */
34
33
 
package/lib/rsyncExec.js CHANGED
@@ -79,7 +79,7 @@ async function send (hostInfo, argSrc, dst, rsyncOpt, timeout) {
79
79
  }
80
80
  debug(`send ${src} to ${hostInfo.host}:${dst}`);
81
81
  debugVerbose(`send: ${rsyncCmd} ${args.join(" ")}`);
82
- return retryWrapper(fork.bind(null, hostInfo, rsyncCmd, args, timeout), hostInfo);
82
+ return retryWrapper(fork.bind(null, hostInfo, rsyncCmd, args, timeout, null, [12]), hostInfo);
83
83
  }
84
84
 
85
85
  /**
@@ -119,7 +119,7 @@ async function recv (hostInfo, argSrc, dst, rsyncOpt, timeout) {
119
119
 
120
120
  debug(`recv ${src} to ${dst}`);
121
121
  debugVerbose(`recv: ${rsyncCmd} ${args.join(" ")}`);
122
- return retryWrapper(fork.bind(null, hostInfo, rsyncCmd, args, timeout), hostInfo);
122
+ return retryWrapper(fork.bind(null, hostInfo, rsyncCmd, args, timeout, null, [12]), hostInfo);
123
123
  }
124
124
  module.exports = {
125
125
  checkRsyncVersion,
package/lib/sshExec.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  const crypto = require("crypto");
3
- const promiseRetry = require("promise-retry");
3
+ const { setTimeout: setTimeoutPromise } = require("timers/promises");
4
4
  const debug = require("debug")("sshClientWrapper:debug:sshExec");
5
5
  const debugVerbose = require("debug")("sshClientWrapper:verbose:sshExec");
6
6
  const debugSendMasterPty = require("debug")("sshClientWrapper:insecure:sshExec_masterPty");
@@ -242,22 +242,36 @@ async function ls (hostInfo, target, lsOpt, timeout) {
242
242
 
243
243
  /**
244
244
  * retry fork() if it throw retryable error
245
- * @param {Function} func - function to be wrapped
245
+ * @param {Function} func - function to be wrapped
246
+ * @param {Object} hostInfo - hostinfo object
247
+ * @param {Integer} argMaxRetry - max retry number
246
248
  */
247
- async function retryWrapper (func, hostInfo) {
248
- const minTimeout = hostInfo.retryMinTimeout * 1000 || 1000;
249
- const maxTimeout = hostInfo.retryMaxTimeout * 1000 || 5000;
250
- const retries = hostInfo.maxRetry || 1;
251
- return promiseRetry({ retries, minTimeout, maxTimeout }, (retry, number) => {
252
- return func()
253
- .catch((e) => {
254
- if (e.retryable) {
255
- debug(`retrying: ${number} times`);
256
- disconnect(hostInfo).then(retry);
257
- }
249
+ async function retryWrapper (func, hostInfo, argMaxRetry, argDuration) {
250
+ const maxRetry = argMaxRetry || hostInfo.maxRetry || 1;
251
+ const duration = argDuration || hostInfo.retryDuration || 1000;
252
+ if (maxRetry === 1) {
253
+ return func();
254
+ }
255
+ let retryCount = 0;
256
+ while (true) {
257
+ let rt;
258
+ try {
259
+ rt = await func();
260
+ return rt;
261
+ } catch (e) {
262
+ if (!e.retryable) {
258
263
  throw e;
259
- });
260
- });
264
+ }
265
+ if (retryCount > maxRetry) {
266
+ debug(`max retry count exceeded: ${retryCount}`);
267
+ throw e;
268
+ }
269
+ retryCount++;
270
+ debug(`retryable error occurred ${e.message}`);
271
+ debug(`retrying: ${retryCount} times`);
272
+ await setTimeoutPromise(duration, true);
273
+ }
274
+ }
261
275
  }
262
276
 
263
277
  module.exports = {
package/lib/util.js CHANGED
@@ -16,6 +16,7 @@ const Ajv = require("ajv");
16
16
  const ajv = new Ajv({
17
17
  allErrors: true,
18
18
  coerceTypes: true,
19
+ useDefaults: "empty",
19
20
  logger: {
20
21
  log: debug,
21
22
  warn: debug,
@@ -33,11 +34,10 @@ const hostInfoSchema = {
33
34
  port: { type: "number", minimum: 0, maximum: 65535 },
34
35
  keyFile: { type: "string", pattern: "\\S+", transform: ["trim"] },
35
36
  noStrictHostkeyChecking: { type: "boolean" },
36
- ControlPersist: { type: "number", minimum: 0 },
37
+ ControlPersist: { type: "number", minimum: 0, default: 180 },
37
38
  ConnectTimeout: { type: "number", minimum: 0 },
38
- maxRetry: { type: "number", minimum: 0 },
39
- retryMinTimeout: { type: "number", minimum: 0 },
40
- retryMaxTimeout: { type: "number", minimum: 0 },
39
+ maxRetry: { type: "number", minimum: 0, default: 3 },
40
+ retryDuration: { type: "number", minimum: 0, default: 1000 },
41
41
  sshOpt: { type: "array", minItems: 1, items: { type: "string", pattern: "\\S+", transform: ["trim"] } }
42
42
  },
43
43
  required: ["host"]
@@ -51,8 +51,7 @@ const numberOptions = [
51
51
  "ControlPersist",
52
52
  "ConnectTimeout",
53
53
  "maxRetry",
54
- "retryMinTimeout",
55
- "retryMaxTimeout"
54
+ "retryDuration"
56
55
  ];
57
56
 
58
57
  const validate = ajv.compile(hostInfoSchema);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ssh-client-wrapper",
3
- "version": "2.2.10",
3
+ "version": "2.4.0",
4
4
  "description": "wrapper library for open ssh client and rsync",
5
5
  "keywords": [
6
6
  "ssh",
package/run_test_all.sh CHANGED
@@ -1,4 +1,12 @@
1
1
  #!/bin/bash
2
+
3
+ function cleanup(){
4
+ docker-compose rm --stop --force testbed
5
+ }
6
+
7
+ trap cleanup EXIT
8
+ set -e -o pipefail
9
+
2
10
  docker-compose up testbed -d
3
11
  export TEST_HOST=localhost
4
12
  export TEST_USER=testuser
@@ -7,27 +15,13 @@ export TEST_PORT=4000
7
15
  ssh-keygen -R '[localhost]:4000'
8
16
 
9
17
  npm run test
10
- if [ $? -ne 0 ];then
11
- echo local test failed
12
- exit 1
13
- fi
18
+ echo local test success
14
19
 
15
20
  docker-compose run --build --rm tester
16
- if [ $? -ne 0 ];then
17
- echo test on latest ubuntu failed
18
- exit 2
19
- fi
21
+ echo test on latest ubuntu success
20
22
 
21
23
  docker-compose run --build --rm tester-bullseye
22
- if [ $? -ne 0 ];then
23
- echo test on bullseye failed
24
- exit 3
25
- fi
24
+ echo test on bullseye success
26
25
 
27
26
  docker-compose run --build --rm tester-buster
28
- if [ $? -ne 0 ];then
29
- echo test on buster failed
30
- exit 4
31
- fi
32
-
33
- docker-compose rm --stop --force testbed
27
+ echo test on buster success
package/test/interface.js CHANGED
@@ -49,7 +49,12 @@ describe("test for interface", () => {
49
49
  });
50
50
  describe("test for public method", () => {
51
51
  let ssh;
52
- const hostInfoOrg = { host: "hoge" };
52
+ const hostInfoOrg = {
53
+ host: "hoge",
54
+ ControlPersist: 180,
55
+ maxRetry: 3,
56
+ retryDuration: 1000
57
+ };
53
58
  const hostInfo = { ...hostInfoOrg, masterPty: null, rsyncVersion: null };
54
59
  beforeEach(() => {
55
60
  ssh = new SshClientWrapper(hostInfoOrg);
package/test/util.js CHANGED
@@ -11,6 +11,11 @@ const { sanityCheck } = require("../lib/util.js");
11
11
 
12
12
  describe("test for sanityCheck", () => {
13
13
  const host = "testHostName";
14
+ const defaultValues = {
15
+ ControlPersist: 180,
16
+ maxRetry: 3,
17
+ retryDuration: 1000
18
+ };
14
19
  it("should throw error for empty host", () => {
15
20
  expect(sanityCheck.bind(null, { host: " ", user: " ", port: 22 })).to.throw(/empty host is not allowed/);
16
21
  });
@@ -18,10 +23,29 @@ describe("test for sanityCheck", () => {
18
23
  expect(sanityCheck.bind(null, { user: " ", port: 22 })).to.throw(/host is required/);
19
24
  });
20
25
  it("should just remove empty string props", () => {
21
- expect(sanityCheck({ host, user: " ", port: 22 })).to.deep.equal({ host, port: 22 });
26
+ expect(sanityCheck({
27
+ host,
28
+ user: " ",
29
+ port: 22
30
+ })).to.deep.equal({
31
+ host,
32
+ ...defaultValues,
33
+ port: 22
34
+ });
22
35
  });
23
36
  it("should remove empty string member in sshOpt", () => {
24
- expect(sanityCheck({ host, sshOpt: ["foo", " ", "bar"], user: "user", port: 33 })).to.deep.equal({ host, sshOpt: ["foo", "bar"], user: "user", port: 33 });
37
+ expect(sanityCheck({
38
+ host,
39
+ sshOpt: ["foo", " ", "bar"],
40
+ user: "user",
41
+ port: 33
42
+ })).to.deep.equal({
43
+ host,
44
+ ...defaultValues,
45
+ sshOpt: ["foo", "bar"],
46
+ user: "user",
47
+ port: 33
48
+ });
25
49
  });
26
50
  it("should just change type if string value specified for number", () => {
27
51
  expect(sanityCheck({
@@ -30,16 +54,14 @@ describe("test for sanityCheck", () => {
30
54
  ControlPersist: "22",
31
55
  ConnectTimeout: "33",
32
56
  maxRetry: "44",
33
- retryMinTimeout: "55",
34
- retryMaxTimeout: "66"
57
+ retryDuration: "55"
35
58
  })).to.deep.equal({
36
59
  host,
37
60
  port: 11,
38
61
  ControlPersist: 22,
39
62
  ConnectTimeout: 33,
40
63
  maxRetry: 44,
41
- retryMinTimeout: 55,
42
- retryMaxTimeout: 66
64
+ retryDuration: 55
43
65
  });
44
66
  });
45
67
  it("should remove out of range members", () => {
@@ -48,8 +70,7 @@ describe("test for sanityCheck", () => {
48
70
  ControlPersist: "-1",
49
71
  ConnectTimeout: "-1",
50
72
  maxRetry: "-1",
51
- retryMinTimeout: "-1",
52
- retryMaxTimeout: "-1"
73
+ retryDuration: "-1"
53
74
  })).to.deep.equal({
54
75
  host
55
76
  });
@@ -60,6 +81,7 @@ describe("test for sanityCheck", () => {
60
81
  noStrictHostkeyChecking: "true"
61
82
  })).to.deep.equal({
62
83
  host,
84
+ ...defaultValues,
63
85
  noStrictHostkeyChecking: true
64
86
  });
65
87
  });
@@ -69,6 +91,7 @@ describe("test for sanityCheck", () => {
69
91
  noStrictHostkeyChecking: 0
70
92
  })).to.deep.equal({
71
93
  host,
94
+ ...defaultValues,
72
95
  noStrictHostkeyChecking: false
73
96
  });
74
97
  });