ssh-client-wrapper 1.8.2 → 2.0.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/util.js CHANGED
@@ -1,5 +1,9 @@
1
1
  "use strict";
2
2
  const { EOL } = require("os");
3
+ const fs = require("fs");
4
+ const debug = require("debug")("sshClientWrapper:debug:util");
5
+ const { setTimeout: setTimeoutPromise } = require("timers/promises");
6
+
3
7
  const sshCmd = "ssh";
4
8
  const rsyncCmd = "rsync";
5
9
 
@@ -62,8 +66,20 @@ const getSshOption = (hostInfo, withoutDestination)=>{
62
66
  args.push(hostInfo.port);
63
67
  }
64
68
  if (typeof hostInfo.keyFile === "string") {
65
- args.push("-i");
66
- args.push(hostInfo.keyFile);
69
+ try {
70
+ const stats = fs.statSync(hostInfo.keyFile);
71
+ if (stats.isFile()) {
72
+ args.push("-i");
73
+ args.push(hostInfo.keyFile);
74
+ } else {
75
+ debug(`specified keyFile(${hostInfo.keyFile}) is not file. so it is ignored`);
76
+ }
77
+ } catch (e) {
78
+ if (e.code !== "ENOENT") {
79
+ throw e;
80
+ }
81
+ debug(`specified keyFile(${hostInfo.keyFile}) not found and ignored`);
82
+ }
67
83
  }
68
84
 
69
85
  if (hostInfo.noStrictHostkeyChecking) {
@@ -95,6 +111,42 @@ const isArrayOfString = (target)=>{
95
111
  });
96
112
  };
97
113
 
114
+ /**
115
+ * log and send message to pty
116
+ * @param {Object} pty - pty object which message will be send to
117
+ * @param {string} message - message
118
+ * @param {Function} logger - logging function
119
+ * @param {string} label - where this function is called
120
+ */
121
+ const sendPty = async (pty, message, logger, label)=>{
122
+ logger(`${label}: ${message}`);
123
+ pty.write(message);
124
+ };
125
+
126
+ /**
127
+ * set watch dog timer
128
+ * @param {number} timeout - timeout (in sec.)
129
+ * @param {string} label - used in both timeout and aborted message
130
+ * @param {Function} cb - call back function which will be called after timeout
131
+ * @returns {AbortController} - ac to stop watch dog timer
132
+ */
133
+ const watchDogTimer = (timeout, label, cb)=>{
134
+ const ac = new AbortController();
135
+ const signal = ac.signal;
136
+ setTimeoutPromise(timeout * 1000, "neverUsedValue", { signal })
137
+ .then(()=>{
138
+ debug(`can not finish ${label} within ${timeout} sec`);
139
+ return typeof cb === "function" ? cb() : true;
140
+ })
141
+ .catch((err)=>{
142
+ if (err.name === "AbortError") {
143
+ debug(`timeout was aborted because ${label} has done`);
144
+ } else {
145
+ throw err;
146
+ }
147
+ });
148
+ return ac;
149
+ };
98
150
 
99
151
  module.exports = {
100
152
  sshCmd,
@@ -105,5 +157,7 @@ module.exports = {
105
157
  buff2String,
106
158
  getSshOption,
107
159
  sanityCheck,
108
- isArrayOfString
160
+ isArrayOfString,
161
+ sendPty,
162
+ watchDogTimer
109
163
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ssh-client-wrapper",
3
- "version": "1.8.2",
3
+ "version": "2.0.1",
4
4
  "description": "wrapper library for open ssh client and rsync",
5
5
  "keywords": [
6
6
  "ssh",
@@ -38,7 +38,8 @@
38
38
  "glob": "^10.3.3",
39
39
  "node-pty": "^0.10.1",
40
40
  "nyc": "^15.1.0",
41
- "promise-retry": "^2.0.1"
41
+ "promise-retry": "^2.0.1",
42
+ "rewire": "^7.0.0"
42
43
  },
43
44
  "devDependencies": {
44
45
  "chai": "^4.3.7",
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+ process.on("unhandledRejection", console.dir); //eslint-disable-line no-console
3
+ Error.traceLimit = 100000;
4
+
5
+ //setup test framework
6
+ const chai = require("chai");
7
+ const { expect } = require("chai");
8
+ const sinon = require("sinon");
9
+ chai.use(require("sinon-chai"));
10
+ chai.use(require("chai-as-promised"));
11
+
12
+ const rewire = require("rewire");
13
+
14
+ //testee
15
+ const SshClientWrapper = rewire("../lib/index.js");
16
+
17
+ const sshExec = sinon.stub();
18
+ const canConnect = sinon.stub();
19
+ const disconnect = sinon.stub();
20
+ const ls = sinon.stub();
21
+ const send = sinon.stub();
22
+ const recv = sinon.stub();
23
+
24
+ //eslint-disable-next-line no-underscore-dangle
25
+ SshClientWrapper.__set__({
26
+ sshExec, canConnect, disconnect, ls, send, recv
27
+ });
28
+
29
+
30
+ describe("test for interface", ()=>{
31
+ describe("test for constructor", ()=>{
32
+ it("should throw error when instanciate with non-empty string host", ()=>{
33
+ expect(()=>{
34
+ //eslint-disable-next-line no-new
35
+ new SshClientWrapper({});
36
+ }).to.throw("host must be non-empty string");
37
+ expect(()=>{
38
+ //eslint-disable-next-line no-new
39
+ new SshClientWrapper({ host: 1 });
40
+ }).to.throw("host must be non-empty string");
41
+ expect(()=>{
42
+ //eslint-disable-next-line no-new
43
+ new SshClientWrapper({ host: "" });
44
+ }).to.throw("host must be non-empty string");
45
+ });
46
+ it("should successfully instanciate only with host prop", ()=>{
47
+ expect(new SshClientWrapper({ host: "hoge" })).to.have.property("exec");
48
+ expect(new SshClientWrapper({ host: "hoge" })).to.have.property("ls");
49
+ expect(new SshClientWrapper({ host: "hoge" })).to.have.property("watch");
50
+ expect(new SshClientWrapper({ host: "hoge" })).to.have.property("send");
51
+ expect(new SshClientWrapper({ host: "hoge" })).to.have.property("recv");
52
+ expect(new SshClientWrapper({ host: "hoge" })).to.have.property("canConnect");
53
+ expect(new SshClientWrapper({ host: "hoge" })).to.have.property("disconnect");
54
+ });
55
+ });
56
+ describe("test for public method", ()=>{
57
+ let ssh;
58
+ const hostInfoOrg = { host: "hoge" };
59
+ const hostInfo = { ...hostInfoOrg, masterPty: null };
60
+ beforeEach(()=>{
61
+ ssh = new SshClientWrapper(hostInfoOrg);
62
+ sshExec.reset();
63
+ canConnect.reset();
64
+ disconnect.reset();
65
+ ls.reset();
66
+ send.reset();
67
+ recv.reset();
68
+ });
69
+ describe("test for exec", ()=>{
70
+ it("should call sshExec with cmd", ()=>{
71
+ ssh.exec("ls");
72
+ expect(sshExec).to.be.calledWith(hostInfo, "ls", 0, null);
73
+ });
74
+ it("should call sshExec with cmd and timeout", ()=>{
75
+ ssh.exec("ls", 10);
76
+ expect(sshExec).to.be.calledWith(hostInfo, "ls", 10, null);
77
+ });
78
+ it("should call sshExec with cmd, timeout and call back function", ()=>{
79
+ const cb = ()=>{};
80
+ ssh.exec("ls", 10, cb);
81
+ expect(sshExec).to.be.calledWith(hostInfo, "ls", 10, cb);
82
+ });
83
+ it("should reject if cmd is not string (number)", ()=>{
84
+ return expect(ssh.exec(0)).to.be.rejectedWith("cmd must be string");
85
+ });
86
+ it("should reject if cmd is not string (null)", ()=>{
87
+ return expect(ssh.exec(null)).to.be.rejectedWith("cmd must be string");
88
+ });
89
+ it("should reject if cmd is empty string", ()=>{
90
+ return expect(ssh.exec("")).to.be.rejectedWith("cmd must be string");
91
+ });
92
+ });
93
+ describe("test for ls", ()=>{
94
+ it("should call ls without timeout", ()=>{
95
+ ssh.ls("hoge");
96
+ expect(ls).to.be.calledWith(hostInfo, "hoge", [], 0);
97
+ });
98
+ it("should call ls with option", ()=>{
99
+ ssh.ls("hoge", ["-l", "-r", "-t"]);
100
+ expect(ls).to.be.calledWith(hostInfo, "hoge", ["-l", "-r", "-t"], 0);
101
+ });
102
+ it("should call ls with option and timeout", ()=>{
103
+ ssh.ls("hoge", ["-l", "-r", "-t"], 10);
104
+ expect(ls).to.be.calledWith(hostInfo, "hoge", ["-l", "-r", "-t"], 10);
105
+ });
106
+ });
107
+ describe("test for send", ()=>{
108
+ it("should call send with src and dst", ()=>{
109
+ ssh.send(["src"], "dst");
110
+ expect(send).to.be.calledWith(hostInfo, ["src"], "dst", [], 0);
111
+ });
112
+ it("should call send with src, dst, and opt", ()=>{
113
+ ssh.send(["src"], "dst", ["--dry-run", "--delete"]);
114
+ expect(send).to.be.calledWith(hostInfo, ["src"], "dst", ["--dry-run", "--delete"], 0);
115
+ });
116
+ it("should call send with src, dst, opt, and timeout", ()=>{
117
+ ssh.send(["src"], "dst", ["--dry-run", "--delete"], 10);
118
+ expect(send).to.be.calledWith(hostInfo, ["src"], "dst", ["--dry-run", "--delete"], 10);
119
+ });
120
+ it("should be rejected if src is not array of string", ()=>{
121
+ return expect(ssh.send(1)).to.be.rejectedWith("src must be array of string");
122
+ });
123
+ it("should be rejected if src has only empty string element", ()=>{
124
+ return expect(ssh.send(["", ""])).to.be.rejectedWith("src must contain non-empty string");
125
+ });
126
+ it("should be rejected if src has only empty string element", ()=>{
127
+ return expect(ssh.send([""])).to.be.rejectedWith("src must contain non-empty string");
128
+ });
129
+ it("should be called if src includes empty string ", ()=>{
130
+ ssh.send(["src", ""], "dst");
131
+ expect(send).to.be.calledWith(hostInfo, ["src", ""], "dst", [], 0);
132
+ });
133
+ it("should be rejected if dst is not string", ()=>{
134
+ return expect(ssh.send(["src"], 0)).to.be.rejectedWith("dst must be string");
135
+ });
136
+ it("should be rejected if dst is empty string", ()=>{
137
+ return expect(ssh.send(["src"], "")).to.be.rejectedWith("dst must be non-empty string");
138
+ });
139
+ it("should be rejected if opt is not array of string", ()=>{
140
+ return expect(ssh.send(["src"], "dst", [null])).to.be.rejectedWith("opt must be array of string");
141
+ });
142
+ });
143
+ describe("test for recv", ()=>{
144
+ it("should call recv with src and dst", ()=>{
145
+ ssh.recv(["src"], "dst");
146
+ expect(recv).to.be.calledWith(hostInfo, ["src"], "dst", [], 0);
147
+ });
148
+ it("should call recv with src, dst, and opt", ()=>{
149
+ ssh.recv(["src"], "dst", ["--dry-run", "--delete"]);
150
+ expect(recv).to.be.calledWith(hostInfo, ["src"], "dst", ["--dry-run", "--delete"], 0);
151
+ });
152
+ it("should call recv with src, dst, opt, and timeout", ()=>{
153
+ ssh.recv(["src"], "dst", ["--dry-run", "--delete"], 10);
154
+ expect(recv).to.be.calledWith(hostInfo, ["src"], "dst", ["--dry-run", "--delete"], 10);
155
+ });
156
+ it("should be rejected if src is not array of string", ()=>{
157
+ return expect(ssh.recv(1)).to.be.rejectedWith("src must be array of string");
158
+ });
159
+ it("should be rejected if src has only empty string element", ()=>{
160
+ return expect(ssh.recv(["", ""])).to.be.rejectedWith("src must contain non-empty string");
161
+ });
162
+ it("should be rejected if src has only empty string element", ()=>{
163
+ return expect(ssh.recv([""])).to.be.rejectedWith("src must contain non-empty string");
164
+ });
165
+ it("should be called if src includes empty string ", ()=>{
166
+ ssh.recv(["src", ""], "dst");
167
+ expect(recv).to.be.calledWith(hostInfo, ["src", ""], "dst", [], 0);
168
+ });
169
+ it("should be rejected if dst is not string", ()=>{
170
+ return expect(ssh.recv(["src"], 0)).to.be.rejectedWith("dst must be string");
171
+ });
172
+ it("should be rejected if dst is empty string", ()=>{
173
+ return expect(ssh.recv(["src"], "")).to.be.rejectedWith("dst must be non-empty string");
174
+ });
175
+ it("should be rejected if opt is not array of string", ()=>{
176
+ return expect(ssh.recv(["src"], "dst", [null])).to.be.rejectedWith("opt must be array of string");
177
+ });
178
+ });
179
+ });
180
+ });