ssh-client-wrapper 2.4.1 → 2.6.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/rsyncExec.js +3 -3
- package/lib/sshExec.js +1 -1
- package/lib/util.js +21 -4
- package/package.json +1 -1
- package/test/getSshOption.js +49 -0
- /package/test/{util.js → sanityCheck.js} +0 -0
package/lib/rsyncExec.js
CHANGED
|
@@ -7,7 +7,7 @@ const { glob } = require("glob");
|
|
|
7
7
|
const { connect, retryWrapper, sshExec } = require("./sshExec.js");
|
|
8
8
|
const { fork } = require("./fork.js");
|
|
9
9
|
|
|
10
|
-
const { sshCmd, rsyncCmd, getSshOption } = require("./util.js");
|
|
10
|
+
const { sshCmd, rsyncCmd, acceptableRsyncRetrunCodes, getSshOption } = require("./util.js");
|
|
11
11
|
|
|
12
12
|
async function checkRsyncVersion (timeout) {
|
|
13
13
|
const rt = {};
|
|
@@ -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, null,
|
|
82
|
+
return retryWrapper(fork.bind(null, hostInfo, rsyncCmd, args, timeout, null, acceptableRsyncRetrunCodes), 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, null,
|
|
122
|
+
return retryWrapper(fork.bind(null, hostInfo, rsyncCmd, args, timeout, null, acceptableRsyncRetrunCodes), hostInfo);
|
|
123
123
|
}
|
|
124
124
|
module.exports = {
|
|
125
125
|
checkRsyncVersion,
|
package/lib/sshExec.js
CHANGED
|
@@ -247,7 +247,7 @@ async function ls (hostInfo, target, lsOpt, timeout) {
|
|
|
247
247
|
* @param {Integer} argMaxRetry - max retry number
|
|
248
248
|
*/
|
|
249
249
|
async function retryWrapper (func, hostInfo, argMaxRetry, argDuration) {
|
|
250
|
-
const maxRetry = argMaxRetry || hostInfo.maxRetry ||
|
|
250
|
+
const maxRetry = argMaxRetry || hostInfo.maxRetry || 3;
|
|
251
251
|
const duration = argDuration || hostInfo.retryDuration || 1000;
|
|
252
252
|
if (maxRetry === 1) {
|
|
253
253
|
return func();
|
package/lib/util.js
CHANGED
|
@@ -6,6 +6,7 @@ const { setTimeout: setTimeoutPromise } = require("timers/promises");
|
|
|
6
6
|
|
|
7
7
|
const sshCmd = "ssh";
|
|
8
8
|
const rsyncCmd = "rsync";
|
|
9
|
+
const acceptableRsyncRetrunCodes = [10, 11, 12, 13, 14];
|
|
9
10
|
|
|
10
11
|
const rePwPrompt = /password:/;
|
|
11
12
|
const rePhPrompt = /Enter passphrase for key/;
|
|
@@ -36,6 +37,7 @@ const hostInfoSchema = {
|
|
|
36
37
|
noStrictHostkeyChecking: { type: "boolean" },
|
|
37
38
|
ControlPersist: { type: "number", minimum: 0, default: 180 },
|
|
38
39
|
ConnectTimeout: { type: "number", minimum: 0 },
|
|
40
|
+
ControlPersistDir: { type: "string", pattern: "\\S+", transform: ["trim"] },
|
|
39
41
|
maxRetry: { type: "number", minimum: 0, default: 3 },
|
|
40
42
|
retryDuration: { type: "number", minimum: 0, default: 1000 },
|
|
41
43
|
sshOpt: { type: "array", minItems: 1, items: { type: "string", pattern: "\\S+", transform: ["trim"] } }
|
|
@@ -45,7 +47,8 @@ const hostInfoSchema = {
|
|
|
45
47
|
|
|
46
48
|
const stringOptions = [
|
|
47
49
|
"user",
|
|
48
|
-
"keyFile"
|
|
50
|
+
"keyFile",
|
|
51
|
+
"ControlPersistDir"
|
|
49
52
|
];
|
|
50
53
|
const numberOptions = [
|
|
51
54
|
"ControlPersist",
|
|
@@ -129,6 +132,18 @@ const sanityCheck = (hostInfo) => {
|
|
|
129
132
|
return hostInfo;
|
|
130
133
|
};
|
|
131
134
|
|
|
135
|
+
function getControlPersistDir (hostInfo) {
|
|
136
|
+
for (const candidate of [hostInfo.ControlPersistDir, process.env.SSH_CONTROL_PERSIST_DIR]) {
|
|
137
|
+
if (typeof candidate === "string") {
|
|
138
|
+
const stat = fs.statSync(candidate, { throwIfNoEntry: false });
|
|
139
|
+
if (stat.isDirectory()) {
|
|
140
|
+
return candidate;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return "~/.ssh";
|
|
145
|
+
}
|
|
146
|
+
|
|
132
147
|
/**
|
|
133
148
|
* return ssh option with some hard corded options
|
|
134
149
|
* @param { Object} hostInfo - host information object defined in index.js
|
|
@@ -178,10 +193,11 @@ const getSshOption = (hostInfo, withoutDestination) => {
|
|
|
178
193
|
if (hostInfo.noStrictHostkeyChecking) {
|
|
179
194
|
args.push("-oStrictHostKeyChecking=no");
|
|
180
195
|
}
|
|
181
|
-
const
|
|
196
|
+
const controlPersist = Number.isInteger(hostInfo.ControlPersist) && hostInfo.ControlPersist >= 0 ? hostInfo.ControlPersist : "180";
|
|
197
|
+
const controlPersistDir = getControlPersistDir(hostInfo);
|
|
182
198
|
args.push("-oControlMaster=auto");
|
|
183
|
-
args.push(
|
|
184
|
-
args.push(`-oControlPersist=${
|
|
199
|
+
args.push(`-oControlPath=${controlPersistDir}/ssh-client-wrapper-%r@%h:%p`);
|
|
200
|
+
args.push(`-oControlPersist=${controlPersist}`);
|
|
185
201
|
|
|
186
202
|
if (Number.isInteger(hostInfo.ConnectTimeout) && hostInfo.ConnectTimeout > 0) {
|
|
187
203
|
args.push(`-oConnectTimeout=${hostInfo.ConnectTimeout}`);
|
|
@@ -244,6 +260,7 @@ const watchDogTimer = (timeout, label, cb) => {
|
|
|
244
260
|
module.exports = {
|
|
245
261
|
sshCmd,
|
|
246
262
|
rsyncCmd,
|
|
263
|
+
acceptableRsyncRetrunCodes,
|
|
247
264
|
rePwPrompt,
|
|
248
265
|
rePhPrompt,
|
|
249
266
|
reNewHostPrompt,
|
package/package.json
CHANGED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
process.on("unhandledRejection", console.dir); // eslint-disable-line no-console
|
|
3
|
+
|
|
4
|
+
// setup test framework
|
|
5
|
+
const chai = require("chai");
|
|
6
|
+
const { expect } = require("chai");
|
|
7
|
+
chai.use(require("chai-as-promised"));
|
|
8
|
+
|
|
9
|
+
// testee
|
|
10
|
+
const { getSshOption } = require("../lib/util.js");
|
|
11
|
+
|
|
12
|
+
describe("test for getSshOption", () => {
|
|
13
|
+
const defaultValues = {
|
|
14
|
+
host: "testHostName",
|
|
15
|
+
ControlPersist: 180,
|
|
16
|
+
maxRetry: 3,
|
|
17
|
+
retryDuration: 1000
|
|
18
|
+
};
|
|
19
|
+
it("should return minimum ssh option array", () => {
|
|
20
|
+
const sshOpts = getSshOption(defaultValues);
|
|
21
|
+
expect(sshOpts).to.be.an("array");
|
|
22
|
+
expect(sshOpts).to.have.lengthOf(4);
|
|
23
|
+
expect(sshOpts[0]).to.equal(defaultValues.host);
|
|
24
|
+
expect(sshOpts[1]).to.equal("-oControlMaster=auto");
|
|
25
|
+
expect(sshOpts[2]).to.equal("-oControlPath=~/.ssh/ssh-client-wrapper-%r@%h:%p");
|
|
26
|
+
expect(sshOpts[3]).to.equal("-oControlPersist=180");
|
|
27
|
+
});
|
|
28
|
+
it("should change ControlPath by ControlPersistDir", () => {
|
|
29
|
+
const hostInfo = { ...defaultValues };
|
|
30
|
+
hostInfo.ControlPersistDir = "/tmp";
|
|
31
|
+
const sshOpts = getSshOption(hostInfo);
|
|
32
|
+
expect(sshOpts).to.be.an("array");
|
|
33
|
+
expect(sshOpts).to.have.lengthOf(4);
|
|
34
|
+
expect(sshOpts[0]).to.equal(defaultValues.host);
|
|
35
|
+
expect(sshOpts[1]).to.equal("-oControlMaster=auto");
|
|
36
|
+
expect(sshOpts[2]).to.equal("-oControlPath=/tmp/ssh-client-wrapper-%r@%h:%p");
|
|
37
|
+
expect(sshOpts[3]).to.equal("-oControlPersist=180");
|
|
38
|
+
});
|
|
39
|
+
it("should change ControlPath by Environment variable", () => {
|
|
40
|
+
process.env.SSH_CONTROL_PERSIST_DIR = "/tmp";
|
|
41
|
+
const sshOpts = getSshOption(defaultValues);
|
|
42
|
+
expect(sshOpts).to.be.an("array");
|
|
43
|
+
expect(sshOpts).to.have.lengthOf(4);
|
|
44
|
+
expect(sshOpts[0]).to.equal(defaultValues.host);
|
|
45
|
+
expect(sshOpts[1]).to.equal("-oControlMaster=auto");
|
|
46
|
+
expect(sshOpts[2]).to.equal("-oControlPath=/tmp/ssh-client-wrapper-%r@%h:%p");
|
|
47
|
+
expect(sshOpts[3]).to.equal("-oControlPersist=180");
|
|
48
|
+
});
|
|
49
|
+
});
|
|
File without changes
|