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 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(ptyProcess.write);
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(ptyProcess.write);
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
- return new Error("src must be array of string");
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
- return new Error("dst must be string");
95
+ const err = "dst must be string";
96
+ debug(err);
97
+ return new Error(err);
94
98
  }
95
- if (!isArrayOfString(opt)) {
96
- return new Error("opt must be array of string");
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
- return new Error("src must be array of string");
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
- return new Error("dst must be string");
123
+ const err = "dst must be string";
124
+ debug(err);
125
+ return new Error(err);
116
126
  }
117
- if (!isArrayOfString(opt)) {
118
- return new Error("opt must be array of string");
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 -avvm --copy-unsafe-links src rsync://[user@]host[:port]/dst
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 = ["-amvv", "--copy-unsafe-links", ...rshOpt, ...rsyncOpt, ...src, `${hostInfo.host}:${dst}`];
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 = ["-amvv", "--copy-unsafe-links", ...rshOpt, ...rsyncOpt, src, dst];
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(`-OConnectTimeout=${timeout}`);
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
- await fork(hostInfo, sshCmd, args, null, cb);
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/wheel-%r@%h:%p");
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ssh-client-wrapper",
3
- "version": "1.0.9",
3
+ "version": "1.2.1",
4
4
  "description": "wrapper library for open ssh client and rsync",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
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.rejectedWith(255);
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.rejectedWith(255);
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, 5)).to.be.rejectedWith(255);
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;