ssh-client-wrapper 1.0.9 → 1.2.1
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 +9 -3
- package/lib/index.js +24 -13
- package/lib/rsyncExec.js +5 -3
- package/lib/sshExec.js +32 -3
- package/lib/util.js +3 -2
- package/package.json +1 -1
- package/test/rsyncExec.js +12 -12
- package/test/sshExec.js +3 -3
package/lib/fork.js
CHANGED
|
@@ -15,8 +15,8 @@ async function fork(hostInfo, cmd, args, argOpt, outputCallback) {
|
|
|
15
15
|
return new Promise((resolve, reject)=>{
|
|
16
16
|
const opt = argOpt || defaultOpt;
|
|
17
17
|
|
|
18
|
-
const ptyProcess = spawn(cmd, args, opt);
|
|
19
18
|
debug(`cmd=${cmd}: args=${args}`);
|
|
19
|
+
const ptyProcess = spawn(cmd, args, opt);
|
|
20
20
|
ptyProcess.onData(buff2String.bind(null, debugVerbose));
|
|
21
21
|
|
|
22
22
|
ptyProcess.onData((data)=>{
|
|
@@ -31,7 +31,10 @@ async function fork(hostInfo, cmd, args, argOpt, outputCallback) {
|
|
|
31
31
|
debugSendPty(hostInfo.password);
|
|
32
32
|
ptyProcess.write(`${hostInfo.password}\n`);
|
|
33
33
|
} else if (typeof hostInfo.password === "function") {
|
|
34
|
-
hostInfo.password(
|
|
34
|
+
hostInfo.password()
|
|
35
|
+
.then((pw)=>{
|
|
36
|
+
ptyProcess.write(`${pw}\n`);
|
|
37
|
+
});
|
|
35
38
|
}
|
|
36
39
|
}
|
|
37
40
|
if (rePhPrompt.test(output)) {
|
|
@@ -39,7 +42,10 @@ async function fork(hostInfo, cmd, args, argOpt, outputCallback) {
|
|
|
39
42
|
debugSendPty(hostInfo.passphrase);
|
|
40
43
|
ptyProcess.write(`${hostInfo.passphrase}\n`);
|
|
41
44
|
} else if (typeof hostInfo.passphrase === "function") {
|
|
42
|
-
hostInfo.passphrase(
|
|
45
|
+
hostInfo.passphrase()
|
|
46
|
+
.then((pw)=>{
|
|
47
|
+
ptyProcess.write(`${pw}\n`);
|
|
48
|
+
});
|
|
43
49
|
}
|
|
44
50
|
}
|
|
45
51
|
});
|
package/lib/index.js
CHANGED
|
@@ -83,17 +83,23 @@ class SshClientWrapper {
|
|
|
83
83
|
* @param {[string]} opt - option for rsync
|
|
84
84
|
* @returns {Promise} - Resolved with undefined when file transfer is done.
|
|
85
85
|
*/
|
|
86
|
-
async send(src, dst, opt
|
|
87
|
-
debug("send called", src, dst);
|
|
86
|
+
async send(src, dst, opt) {
|
|
87
|
+
debug("send called", src, dst, opt);
|
|
88
88
|
|
|
89
89
|
if (!isArrayOfString(src)) {
|
|
90
|
-
|
|
90
|
+
const err = "src must be array of string";
|
|
91
|
+
debug(err);
|
|
92
|
+
return new Error(err);
|
|
91
93
|
}
|
|
92
94
|
if (typeof dst !== "string") {
|
|
93
|
-
|
|
95
|
+
const err = "dst must be string";
|
|
96
|
+
debug(err);
|
|
97
|
+
return new Error(err);
|
|
94
98
|
}
|
|
95
|
-
if (!isArrayOfString(opt)) {
|
|
96
|
-
|
|
99
|
+
if (typeof opt !== "undefined" && !isArrayOfString(opt)) {
|
|
100
|
+
const err = "opt must be array of string";
|
|
101
|
+
debug(err);
|
|
102
|
+
return new Error(err);
|
|
97
103
|
}
|
|
98
104
|
return send(this.hostInfo, src, dst, opt);
|
|
99
105
|
}
|
|
@@ -105,19 +111,24 @@ class SshClientWrapper {
|
|
|
105
111
|
* @param {[string]} opt - option for rsync
|
|
106
112
|
* @returns {Promise} - Resolved with undefined when file transfer is done.
|
|
107
113
|
*/
|
|
108
|
-
async recv(src, dst, opt
|
|
109
|
-
debug("recv called", src, dst);
|
|
114
|
+
async recv(src, dst, opt) {
|
|
115
|
+
debug("recv called", src, dst, opt);
|
|
110
116
|
|
|
111
117
|
if (!isArrayOfString(src)) {
|
|
112
|
-
|
|
118
|
+
const err = "src must be array of string";
|
|
119
|
+
debug(err);
|
|
120
|
+
return new Error(err);
|
|
113
121
|
}
|
|
114
122
|
if (typeof dst !== "string") {
|
|
115
|
-
|
|
123
|
+
const err = "dst must be string";
|
|
124
|
+
debug(err);
|
|
125
|
+
return new Error(err);
|
|
116
126
|
}
|
|
117
|
-
if (!isArrayOfString(opt)) {
|
|
118
|
-
|
|
127
|
+
if (typeof opt !== "undefined" && !isArrayOfString(opt)) {
|
|
128
|
+
const err = "opt must be array of string";
|
|
129
|
+
debug(err);
|
|
130
|
+
return new Error(err);
|
|
119
131
|
}
|
|
120
|
-
|
|
121
132
|
return recv(this.hostInfo, src, dst, opt);
|
|
122
133
|
}
|
|
123
134
|
|
package/lib/rsyncExec.js
CHANGED
|
@@ -16,7 +16,7 @@ const { sshCmd, rsyncCmd, getSshOption } = require("./util.js");
|
|
|
16
16
|
* @param {string} dst - Destination path.
|
|
17
17
|
* @param {[string]} rsyncOpt - optional arguments for rsync
|
|
18
18
|
*
|
|
19
|
-
* this function execute rsync -
|
|
19
|
+
* this function execute rsync -avv --copy-unsafe-links src rsync://[user@]host[:port]/dst
|
|
20
20
|
*/
|
|
21
21
|
async function send(hostInfo, argSrc, dst, rsyncOpt = []) {
|
|
22
22
|
const promises = await Promise.allSettled(argSrc.map((e)=>{
|
|
@@ -32,8 +32,10 @@ async function send(hostInfo, argSrc, dst, rsyncOpt = []) {
|
|
|
32
32
|
const sshArgs = getSshOption(hostInfo, true);
|
|
33
33
|
const rshOpt = ["-e", `${sshCmd} ${sshArgs.join(" ")}`];
|
|
34
34
|
|
|
35
|
-
const args = ["-
|
|
35
|
+
const args = ["-avv", "--copy-unsafe-links", ...rshOpt, ...rsyncOpt, ...src, `${hostInfo.host}:${dst}`];
|
|
36
36
|
const { dir: remoteDir } = path.parse(dst);
|
|
37
|
+
debug("remoteDir=", remoteDir);
|
|
38
|
+
|
|
37
39
|
if (remoteDir !== "" && remoteDir !== "/") {
|
|
38
40
|
debug(`mkdir ${remoteDir}`);
|
|
39
41
|
sshExec(hostInfo, `mkdir -p ${remoteDir}`, debugVerbose);
|
|
@@ -57,7 +59,7 @@ async function recv(hostInfo, argSrc, dst, rsyncOpt = []) {
|
|
|
57
59
|
const sshArgs = getSshOption(hostInfo, true);
|
|
58
60
|
const rshOpt = ["-e", `${sshCmd} ${sshArgs.join(" ")}`];
|
|
59
61
|
|
|
60
|
-
const args = ["-
|
|
62
|
+
const args = ["-avv", "--copy-unsafe-links", ...rshOpt, ...rsyncOpt, src, dst];
|
|
61
63
|
|
|
62
64
|
debug(`recv ${src} to ${dst}`);
|
|
63
65
|
debugVerbose(`rsyncCmd=${rsyncCmd}: args=${args}`);
|
package/lib/sshExec.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
const { setTimeout: setTimeoutPromise } = require("timers/promises");
|
|
2
3
|
const crypto = require("crypto");
|
|
3
4
|
const debug = require("debug")("sshClientWrapper:sshExec");
|
|
4
5
|
const { sshCmd, getSshOption } = require("./util.js");
|
|
@@ -23,12 +24,12 @@ async function sshExec(hostInfo, cmd, outputCallback, opt = null) {
|
|
|
23
24
|
* @param {Object} hostInfo - hostinfo object
|
|
24
25
|
* @param {Integer} timeout - timeout in secconds
|
|
25
26
|
*/
|
|
26
|
-
async function canConnect(hostInfo, timeout) {
|
|
27
|
+
async function canConnect(hostInfo, timeout = 5) {
|
|
27
28
|
debug("check connectibity");
|
|
28
29
|
const words = crypto.randomUUID();
|
|
29
30
|
const args = getSshOption(hostInfo);
|
|
30
31
|
if (Number.isInteger(timeout) && timeout > 0) {
|
|
31
|
-
args.push(`-
|
|
32
|
+
args.push(`-oConnectTimeout=${timeout}`);
|
|
32
33
|
}
|
|
33
34
|
args.push(`echo ${words}`);
|
|
34
35
|
|
|
@@ -36,7 +37,35 @@ async function canConnect(hostInfo, timeout) {
|
|
|
36
37
|
const cb = (e)=>{
|
|
37
38
|
output += e;
|
|
38
39
|
};
|
|
39
|
-
|
|
40
|
+
|
|
41
|
+
const ac = new AbortController();
|
|
42
|
+
const signal = ac.signal;
|
|
43
|
+
|
|
44
|
+
setTimeoutPromise(timeout * 1000, "foobar", { signal })
|
|
45
|
+
.then(()=>{
|
|
46
|
+
const err = new Error(`can not connect within ${timeout} sec`);
|
|
47
|
+
err.host = hostInfo.host;
|
|
48
|
+
err.user = hostInfo.user;
|
|
49
|
+
err.port = hostInfo.port;
|
|
50
|
+
throw err;
|
|
51
|
+
})
|
|
52
|
+
.catch((err)=>{
|
|
53
|
+
if (err.name === "AbortError") {
|
|
54
|
+
debug("timeout was aborted because connection check has done");
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const rt = await fork(hostInfo, sshCmd, args, null, cb);
|
|
59
|
+
ac.abort();
|
|
60
|
+
|
|
61
|
+
if (rt !== 0) {
|
|
62
|
+
const err = new Error(`can not connect to ${hostInfo.host}. return code=${rt}`);
|
|
63
|
+
err.rt = rt;
|
|
64
|
+
err.host = hostInfo.host;
|
|
65
|
+
err.user = hostInfo.user;
|
|
66
|
+
err.port = hostInfo.port;
|
|
67
|
+
return err;
|
|
68
|
+
}
|
|
40
69
|
const re = new RegExp(words);
|
|
41
70
|
return re.test(output);
|
|
42
71
|
}
|
package/lib/util.js
CHANGED
|
@@ -50,7 +50,7 @@ const getSshOption = (hostInfo, withoutDestination)=>{
|
|
|
50
50
|
}
|
|
51
51
|
const renewInterval = Number.isInteger(hostInfo.renewInterval) && hostInfo.renewInterval > 0 ? hostInfo.renewInterval : 180;
|
|
52
52
|
args.push("-oControlMaster=auto");
|
|
53
|
-
args.push("-oControlPath=~/.ssh/
|
|
53
|
+
args.push("-oControlPath=~/.ssh/ssh-client-wrapper-%r@%h:%p");
|
|
54
54
|
args.push(`-oControlPersist=${renewInterval}`);
|
|
55
55
|
return args;
|
|
56
56
|
};
|
|
@@ -72,7 +72,8 @@ const isArrayOfString = (target)=>{
|
|
|
72
72
|
if (!Array.isArray(target)) {
|
|
73
73
|
return false;
|
|
74
74
|
}
|
|
75
|
-
return target.some((e)=>{
|
|
75
|
+
return !target.some((e)=>{
|
|
76
|
+
//target has non string member
|
|
76
77
|
return typeof e !== "string";
|
|
77
78
|
});
|
|
78
79
|
};
|
package/package.json
CHANGED
package/test/rsyncExec.js
CHANGED
|
@@ -154,7 +154,7 @@ describe("test rsync exec", function() {
|
|
|
154
154
|
await send(hostInfo, [localRoot], remoteEmptyDir);
|
|
155
155
|
|
|
156
156
|
await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot)}`, output);
|
|
157
|
-
expect(formatLsOutput(rt)).to.have.members(["foo", "bar", "baz", "hoge"]);
|
|
157
|
+
expect(formatLsOutput(rt)).to.have.members(["foo", "bar", "baz", "hoge", "huga"]);
|
|
158
158
|
|
|
159
159
|
rt.splice(0, rt.length);
|
|
160
160
|
await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot, "hoge")}`, output);
|
|
@@ -164,7 +164,7 @@ describe("test rsync exec", function() {
|
|
|
164
164
|
await send(hostInfo, [path.resolve(localRoot)], remoteEmptyDir);
|
|
165
165
|
|
|
166
166
|
await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot)}`, output);
|
|
167
|
-
expect(formatLsOutput(rt)).to.have.members(["foo", "bar", "baz", "hoge"]);
|
|
167
|
+
expect(formatLsOutput(rt)).to.have.members(["foo", "bar", "baz", "hoge", "huga"]);
|
|
168
168
|
|
|
169
169
|
rt.splice(0, rt.length);
|
|
170
170
|
await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot, "hoge")}`, output);
|
|
@@ -174,7 +174,7 @@ describe("test rsync exec", function() {
|
|
|
174
174
|
await send(hostInfo, [localRoot], path.posix.join(remoteHome, remoteEmptyDir));
|
|
175
175
|
|
|
176
176
|
await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot)}`, output);
|
|
177
|
-
expect(formatLsOutput(rt)).to.have.members(["foo", "bar", "baz", "hoge"]);
|
|
177
|
+
expect(formatLsOutput(rt)).to.have.members(["foo", "bar", "baz", "hoge", "huga"]);
|
|
178
178
|
|
|
179
179
|
rt.splice(0, rt.length);
|
|
180
180
|
await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot, "hoge")}`, output);
|
|
@@ -184,7 +184,7 @@ describe("test rsync exec", function() {
|
|
|
184
184
|
await send(hostInfo, [path.resolve(localRoot)], path.posix.join(remoteHome, remoteEmptyDir));
|
|
185
185
|
|
|
186
186
|
await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot)}`, output);
|
|
187
|
-
expect(formatLsOutput(rt)).to.have.members(["foo", "bar", "baz", "hoge"]);
|
|
187
|
+
expect(formatLsOutput(rt)).to.have.members(["foo", "bar", "baz", "hoge", "huga"]);
|
|
188
188
|
|
|
189
189
|
rt.splice(0, rt.length);
|
|
190
190
|
await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot, "hoge")}`, output);
|
|
@@ -205,7 +205,7 @@ describe("test rsync exec", function() {
|
|
|
205
205
|
await send(hostInfo, [localRoot], remoteEmptyDir, ["--exclude=ba*", "--exclude=hoge*}"]);
|
|
206
206
|
|
|
207
207
|
await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot)}`, output);
|
|
208
|
-
expect(formatLsOutput(rt)).to.have.members(["foo", "hoge"]);
|
|
208
|
+
expect(formatLsOutput(rt)).to.have.members(["foo", "hoge", "huga"]);
|
|
209
209
|
});
|
|
210
210
|
it.skip("[not implemented] should send directory tree if only filter matched but exclude filter not matched", async ()=>{
|
|
211
211
|
await send(hostInfo, [localRoot], remoteEmptyDir, "*/{ba*,hoge/*}", "**/poyo");
|
|
@@ -217,8 +217,8 @@ describe("test rsync exec", function() {
|
|
|
217
217
|
await sshExec(hostInfo, `ls ${path.posix.join(remoteEmptyDir, localRoot, "hoge")}`, output);
|
|
218
218
|
expect(formatLsOutput(rt)).to.have.members(["piyo", "puyo"]);
|
|
219
219
|
});
|
|
220
|
-
it("shoud not send empty directory", async ()=>{
|
|
221
|
-
await send(hostInfo, [localEmptyDir], remoteEmptyDir);
|
|
220
|
+
it("shoud not send empty directory with -m option", async ()=>{
|
|
221
|
+
await send(hostInfo, [localEmptyDir], remoteEmptyDir, ["-m"]);
|
|
222
222
|
|
|
223
223
|
await sshExec(hostInfo, `ls ${remoteEmptyDir}`, output);
|
|
224
224
|
expect(rt).to.have.lengthOf(0);
|
|
@@ -318,7 +318,7 @@ describe("test rsync exec", function() {
|
|
|
318
318
|
await recv(hostInfo, [remoteRoot], localEmptyDir);
|
|
319
319
|
|
|
320
320
|
let rt2 = await fs.readdir(path.posix.join(localEmptyDir, remoteRoot));
|
|
321
|
-
expect(rt2).to.have.members(["foo", "bar", "baz", "hoge"]);
|
|
321
|
+
expect(rt2).to.have.members(["foo", "bar", "baz", "hoge", "huga"]);
|
|
322
322
|
rt2 = await fs.readdir(path.join(localEmptyDir, remoteRoot, "hoge"));
|
|
323
323
|
expect(rt2).to.have.members(["piyo", "puyo", "poyo"]);
|
|
324
324
|
});
|
|
@@ -326,7 +326,7 @@ describe("test rsync exec", function() {
|
|
|
326
326
|
await recv(hostInfo, [path.posix.join(remoteHome, remoteRoot)], localEmptyDir);
|
|
327
327
|
|
|
328
328
|
let rt2 = await fs.readdir(path.posix.join(localEmptyDir, remoteRoot));
|
|
329
|
-
expect(rt2).to.have.members(["foo", "bar", "baz", "hoge"]);
|
|
329
|
+
expect(rt2).to.have.members(["foo", "bar", "baz", "hoge", "huga"]);
|
|
330
330
|
rt2 = await fs.readdir(path.join(localEmptyDir, remoteRoot, "hoge"));
|
|
331
331
|
expect(rt2).to.have.members(["piyo", "puyo", "poyo"]);
|
|
332
332
|
});
|
|
@@ -334,7 +334,7 @@ describe("test rsync exec", function() {
|
|
|
334
334
|
await recv(hostInfo, [remoteRoot], path.resolve(localEmptyDir));
|
|
335
335
|
|
|
336
336
|
let rt2 = await fs.readdir(path.posix.join(localEmptyDir, remoteRoot));
|
|
337
|
-
expect(rt2).to.have.members(["foo", "bar", "baz", "hoge"]);
|
|
337
|
+
expect(rt2).to.have.members(["foo", "bar", "baz", "hoge", "huga"]);
|
|
338
338
|
rt2 = await fs.readdir(path.join(localEmptyDir, remoteRoot, "hoge"));
|
|
339
339
|
expect(rt2).to.have.members(["piyo", "puyo", "poyo"]);
|
|
340
340
|
});
|
|
@@ -342,7 +342,7 @@ describe("test rsync exec", function() {
|
|
|
342
342
|
await recv(hostInfo, [path.posix.join(remoteHome, remoteRoot)], path.resolve(localEmptyDir));
|
|
343
343
|
|
|
344
344
|
let rt2 = await fs.readdir(path.posix.join(localEmptyDir, remoteRoot));
|
|
345
|
-
expect(rt2).to.have.members(["foo", "bar", "baz", "hoge"]);
|
|
345
|
+
expect(rt2).to.have.members(["foo", "bar", "baz", "hoge", "huga"]);
|
|
346
346
|
rt2 = await fs.readdir(path.join(localEmptyDir, remoteRoot, "hoge"));
|
|
347
347
|
expect(rt2).to.have.members(["piyo", "puyo", "poyo"]);
|
|
348
348
|
});
|
|
@@ -358,7 +358,7 @@ describe("test rsync exec", function() {
|
|
|
358
358
|
await recv(hostInfo, [remoteRoot], localEmptyDir, ["--exclude=*/ba*", "--exclude=hoge"]);
|
|
359
359
|
|
|
360
360
|
const rt2 = await fs.readdir(path.posix.join(localEmptyDir, remoteRoot));
|
|
361
|
-
expect(rt2).to.have.members(["foo"]);
|
|
361
|
+
expect(rt2).to.have.members(["foo", "huga"]);
|
|
362
362
|
});
|
|
363
363
|
it.skip("[not implemented] should recv files which matches only filter but should not recv which matches exclude filter", async ()=>{
|
|
364
364
|
await recv(hostInfo, [remoteRoot], localEmptyDir, "*/{ba*,hoge/*}", "**/piyo");
|
package/test/sshExec.js
CHANGED
|
@@ -68,11 +68,11 @@ describe("test for ssh execution", function() {
|
|
|
68
68
|
});
|
|
69
69
|
it("should be rejected if user does not exist", async ()=>{
|
|
70
70
|
hostInfo2.user = "xxxx";
|
|
71
|
-
return expect(canConnect(hostInfo2)).to.be.
|
|
71
|
+
return expect(canConnect(hostInfo2)).to.be.rejected;
|
|
72
72
|
});
|
|
73
73
|
it("should be rejected if password is wrong", async ()=>{
|
|
74
74
|
hostInfo2.password = "xxxx";
|
|
75
|
-
return expect(canConnect(hostInfo2)).to.be.
|
|
75
|
+
return expect(canConnect(hostInfo2)).to.be.rejected;
|
|
76
76
|
});
|
|
77
77
|
it("should be rejected if host does not exist", async ()=>{
|
|
78
78
|
hostInfo2.host = "foo.bar.example.com";
|
|
@@ -80,7 +80,7 @@ describe("test for ssh execution", function() {
|
|
|
80
80
|
});
|
|
81
81
|
it("should be rejected if host(ip address) does not exist", async ()=>{
|
|
82
82
|
hostInfo2.host = "192.0.2.1";
|
|
83
|
-
return expect(canConnect(hostInfo2
|
|
83
|
+
return expect(canConnect(hostInfo2)).to.be.rejectedWith(255);
|
|
84
84
|
});
|
|
85
85
|
it("should be rejected if port number is out of range(-1)", async ()=>{
|
|
86
86
|
hostInfo2.port = -1;
|