ssh-client-wrapper 2.10.2 → 2.12.4
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/.github/workflows/coverage_report.yml +1 -1
- package/.github/workflows/run_test.yml +3 -2
- package/{docker-compose.yml → compose.yml} +2 -2
- package/eslint.config.mjs +206 -0
- package/lib/fork.js +25 -13
- package/lib/index.js +70 -17
- package/lib/rsyncExec.js +17 -17
- package/lib/sshExec.js +81 -58
- package/lib/util.js +28 -21
- package/package.json +10 -12
- package/run_test_all.sh +3 -12
- package/run_test_local.sh +13 -0
- package/test/getSshOption.js +7 -7
- package/test/interface.js +54 -54
- package/test/rsyncExec.js +72 -72
- package/test/sanityCheck.js +14 -14
- package/test/sshExec.js +63 -61
- package/test/testUtil/testFiles.js +21 -7
- package/test/testUtil/testrc +1 -0
package/lib/sshExec.js
CHANGED
|
@@ -3,22 +3,40 @@ const crypto = require("crypto");
|
|
|
3
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
|
-
const debugSendMasterPty = require("debug")("
|
|
6
|
+
const debugSendMasterPty = require("debug")("sshClientWrapper:insecure:sshExec:masterPty");
|
|
7
7
|
const { sendPty, sshCmd, getSshOption, sanityCheck, watchDogTimer } = require("./util.js");
|
|
8
8
|
const { fork, createMasterPty, sshLoginCallback } = require("./fork.js");
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* @typedef Integer {number} - integer number
|
|
12
|
+
*/
|
|
13
|
+
|
|
10
14
|
/**
|
|
11
15
|
* Execute command on remote host.
|
|
12
|
-
* @param {
|
|
16
|
+
* @param {object} hostInfo - hostinfo object
|
|
13
17
|
* @param {string} cmd - Cmdline which will be executed on remote host.
|
|
14
18
|
* @param {number} timeout - timeout (in sec.)
|
|
15
19
|
* @param {Function} outputCallback - Callback routine for stdout and stderr.
|
|
20
|
+
* @param {string} rcfile - rcfile path which will be sourced before executing actual command
|
|
21
|
+
* @param {string} prependCmd - command string which will be executed before executing actual command
|
|
22
|
+
* @returns {Integer} - return value of cmd
|
|
16
23
|
*/
|
|
17
|
-
async function sshExec
|
|
24
|
+
async function sshExec(hostInfo, cmd, timeout, outputCallback, rcfile, prependCmd) {
|
|
18
25
|
debug("sshExec called");
|
|
19
26
|
await connect(hostInfo);
|
|
20
27
|
debug("exec", cmd, "on remote server");
|
|
21
28
|
const args = getSshOption(hostInfo, false);
|
|
29
|
+
if (rcfile) {
|
|
30
|
+
args.push(`. ${rcfile};`);
|
|
31
|
+
} else if (hostInfo.rcfile) {
|
|
32
|
+
args.push(`. ${hostInfo.rcfile};`);
|
|
33
|
+
}
|
|
34
|
+
if (prependCmd) {
|
|
35
|
+
args.push(`${prependCmd};`);
|
|
36
|
+
} else if (hostInfo.prependCmd) {
|
|
37
|
+
args.push(`${hostInfo.prependCmd};`);
|
|
38
|
+
}
|
|
39
|
+
|
|
22
40
|
args.push(cmd);
|
|
23
41
|
debugVerbose(`exec: ${sshCmd} ${args.join(" ")}`);
|
|
24
42
|
|
|
@@ -31,11 +49,42 @@ async function sshExec (hostInfo, cmd, timeout, outputCallback) {
|
|
|
31
49
|
}
|
|
32
50
|
if (e.rt === 126) {
|
|
33
51
|
debug(`${cmd} got Permission deny error but ignored`);
|
|
34
|
-
return e.rt;
|
|
35
52
|
}
|
|
36
53
|
if (e.rt === 127) {
|
|
37
54
|
debug(`${cmd} not found but ignored`);
|
|
38
|
-
|
|
55
|
+
}
|
|
56
|
+
return e.rt;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function expect(hostInfo, cmd, expects, timeout) {
|
|
61
|
+
debug("expect called");
|
|
62
|
+
await connect(hostInfo);
|
|
63
|
+
debugVerbose("exec", cmd, "on remote server with", expects);
|
|
64
|
+
const args = getSshOption(hostInfo, false);
|
|
65
|
+
const compiledExpects = expects.map((e)=>{
|
|
66
|
+
return {
|
|
67
|
+
reExpect: new RegExp(e.expect),
|
|
68
|
+
send: e.send,
|
|
69
|
+
keep: e.keep
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
const reExpect = hostInfo.prompt ? new RegExp(hostInfo.prompt) : /[$#>] /;
|
|
73
|
+
compiledExpects.unshift({ reExpect, send: cmd });
|
|
74
|
+
compiledExpects.push({ reExpect, send: "exit" });
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const rt = await fork(hostInfo, sshCmd, args, timeout, null, [], compiledExpects);
|
|
78
|
+
return rt;
|
|
79
|
+
} catch (e) {
|
|
80
|
+
if (typeof e.rt === "undefined") {
|
|
81
|
+
throw e;
|
|
82
|
+
}
|
|
83
|
+
if (e.rt === 126) {
|
|
84
|
+
debug(`${cmd} got Permission deny error but ignored`);
|
|
85
|
+
}
|
|
86
|
+
if (e.rt === 127) {
|
|
87
|
+
debug(`${cmd} not found but ignored`);
|
|
39
88
|
}
|
|
40
89
|
return e.rt;
|
|
41
90
|
}
|
|
@@ -43,32 +92,32 @@ async function sshExec (hostInfo, cmd, timeout, outputCallback) {
|
|
|
43
92
|
|
|
44
93
|
/**
|
|
45
94
|
* Check if you can connect to sepecified server
|
|
46
|
-
* @param {
|
|
95
|
+
* @param {object} hostInfo - hostinfo object
|
|
47
96
|
* @param {Integer} timeout - timeout in secconds
|
|
48
97
|
*/
|
|
49
|
-
async function canConnect
|
|
98
|
+
async function canConnect(hostInfo, timeout) {
|
|
50
99
|
debug("canConnect called");
|
|
51
100
|
sanityCheck(hostInfo);
|
|
52
101
|
|
|
53
102
|
await connect(hostInfo, timeout);
|
|
54
103
|
await disconnect(hostInfo);
|
|
55
|
-
//
|
|
56
|
-
//
|
|
104
|
+
//if connection fail, Error is throwed.
|
|
105
|
+
//so, if we reach here, connection must be succeeded
|
|
57
106
|
return true;
|
|
58
107
|
}
|
|
59
108
|
|
|
60
109
|
/**
|
|
61
110
|
* check if master session exists or not
|
|
62
|
-
* @param {
|
|
111
|
+
* @param {object} hostInfo - hostinfo object
|
|
63
112
|
* @returns {boolean} - true if master session exists
|
|
64
113
|
*/
|
|
65
|
-
async function existsMaster
|
|
114
|
+
async function existsMaster(hostInfo, argTimeout) {
|
|
66
115
|
debugVerbose("existsMaster called");
|
|
67
116
|
const args = getSshOption(hostInfo);
|
|
68
117
|
args.push("-Ocheck");
|
|
69
118
|
|
|
70
119
|
let output = "";
|
|
71
|
-
const cb = (e)
|
|
120
|
+
const cb = (e)=>{
|
|
72
121
|
output += e;
|
|
73
122
|
};
|
|
74
123
|
|
|
@@ -92,9 +141,9 @@ async function existsMaster (hostInfo, argTimeout) {
|
|
|
92
141
|
|
|
93
142
|
/**
|
|
94
143
|
* create master ssh connection
|
|
95
|
-
* @param {
|
|
144
|
+
* @param {object} hostInfo - hostinfo object
|
|
96
145
|
*/
|
|
97
|
-
async function connect
|
|
146
|
+
async function connect(hostInfo, timeout = 60) {
|
|
98
147
|
debug("connect called");
|
|
99
148
|
|
|
100
149
|
if (await existsMaster(hostInfo, timeout)) {
|
|
@@ -110,8 +159,9 @@ async function connect (hostInfo, timeout = 60) {
|
|
|
110
159
|
const re = new RegExp(`^${words}`);
|
|
111
160
|
|
|
112
161
|
let ac = null;
|
|
113
|
-
|
|
114
|
-
|
|
162
|
+
let output = null;
|
|
163
|
+
const p = new Promise((resolve, reject)=>{
|
|
164
|
+
ac = watchDogTimer(timeout, "make connection", ()=>{
|
|
115
165
|
sendPty(hostInfo.masterPty, "\n", debugSendMasterPty, 137);
|
|
116
166
|
sendPty(hostInfo.masterPty, "exit\n", debugSendMasterPty, 138);
|
|
117
167
|
|
|
@@ -119,16 +169,17 @@ async function connect (hostInfo, timeout = 60) {
|
|
|
119
169
|
err.host = hostInfo.host;
|
|
120
170
|
err.user = hostInfo.user;
|
|
121
171
|
err.port = hostInfo.port;
|
|
172
|
+
err.output = output;
|
|
122
173
|
reject(err);
|
|
123
174
|
});
|
|
124
175
|
|
|
125
176
|
let done = false;
|
|
126
|
-
hostInfo.masterPty.onData((data)
|
|
177
|
+
hostInfo.masterPty.onData((data)=>{
|
|
127
178
|
if (done) {
|
|
128
|
-
//
|
|
179
|
+
//safeguard
|
|
129
180
|
return;
|
|
130
181
|
}
|
|
131
|
-
|
|
182
|
+
output = data.toString();
|
|
132
183
|
debugVerbose(output);
|
|
133
184
|
|
|
134
185
|
if (re.test(output)) {
|
|
@@ -147,12 +198,12 @@ async function connect (hostInfo, timeout = 60) {
|
|
|
147
198
|
reject(new Error("Control socket creation failed"));
|
|
148
199
|
}
|
|
149
200
|
});
|
|
150
|
-
hostInfo.masterPty.onData((data)
|
|
201
|
+
hostInfo.masterPty.onData((data)=>{
|
|
151
202
|
const output = data.toString();
|
|
152
203
|
sshLoginCallback(output, hostInfo.masterPty, hostInfo.password, hostInfo.passphrase, debugSendMasterPty)
|
|
153
204
|
.catch(reject);
|
|
154
205
|
});
|
|
155
|
-
hostInfo.masterPty.onExit((e)
|
|
206
|
+
hostInfo.masterPty.onExit((e)=>{
|
|
156
207
|
debug("master pty exit", e);
|
|
157
208
|
resolve(e);
|
|
158
209
|
});
|
|
@@ -175,9 +226,9 @@ async function connect (hostInfo, timeout = 60) {
|
|
|
175
226
|
|
|
176
227
|
/**
|
|
177
228
|
* remove master session
|
|
178
|
-
* @param {
|
|
229
|
+
* @param {object} hostInfo - hostinfo object
|
|
179
230
|
*/
|
|
180
|
-
async function disconnect
|
|
231
|
+
async function disconnect(hostInfo) {
|
|
181
232
|
debug("disconnect called");
|
|
182
233
|
|
|
183
234
|
if (await existsMaster(hostInfo)) {
|
|
@@ -185,7 +236,7 @@ async function disconnect (hostInfo) {
|
|
|
185
236
|
args.push("-Oexit");
|
|
186
237
|
|
|
187
238
|
let output = "";
|
|
188
|
-
const cb = (e)
|
|
239
|
+
const cb = (e)=>{
|
|
189
240
|
output += e;
|
|
190
241
|
};
|
|
191
242
|
try {
|
|
@@ -212,50 +263,22 @@ async function disconnect (hostInfo) {
|
|
|
212
263
|
}
|
|
213
264
|
}
|
|
214
265
|
|
|
215
|
-
/**
|
|
216
|
-
* execute ls command and return output
|
|
217
|
-
* @param {Object} hostInfo - hostinfo object
|
|
218
|
-
* @param {string} target - file or directory path to watch
|
|
219
|
-
* @param {string[]} lsOpt - optional arguments for ls
|
|
220
|
-
* @param {number} timeout - timeout (in sec.)
|
|
221
|
-
* @returns {string[]} - output from ls
|
|
222
|
-
*/
|
|
223
|
-
async function ls (hostInfo, target, lsOpt, timeout) {
|
|
224
|
-
debug("ls called");
|
|
225
|
-
const outputArray = [];
|
|
226
|
-
try {
|
|
227
|
-
await sshExec(hostInfo, `ls ${lsOpt.join(" ")} ${target}`, timeout, (data) => {
|
|
228
|
-
outputArray.push(data);
|
|
229
|
-
});
|
|
230
|
-
} catch (e) {
|
|
231
|
-
// non-zero retrun should be allowed
|
|
232
|
-
if (typeof e.rt === "undefined" || e.rt < 0) {
|
|
233
|
-
throw e;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
return outputArray.length === 0
|
|
237
|
-
? []
|
|
238
|
-
: outputArray
|
|
239
|
-
.join("\n")
|
|
240
|
-
.replace(/\n+/g, "\n")
|
|
241
|
-
.replace(/\n$/, "")
|
|
242
|
-
.split("\n");
|
|
243
|
-
}
|
|
244
|
-
|
|
245
266
|
/**
|
|
246
267
|
* retry fork() if it throw retryable error
|
|
247
268
|
* @param {Function} func - function to be wrapped
|
|
248
|
-
* @param {
|
|
269
|
+
* @param {object} hostInfo - hostinfo object
|
|
249
270
|
* @param {Integer} argMaxRetry - max retry number
|
|
271
|
+
* @param {Integer} argDuration - time to wait before retring
|
|
272
|
+
* @returns {Integer} - return value of func
|
|
250
273
|
*/
|
|
251
|
-
async function retryWrapper
|
|
274
|
+
async function retryWrapper(func, hostInfo, argMaxRetry, argDuration) {
|
|
252
275
|
const maxRetry = argMaxRetry || hostInfo.maxRetry || 3;
|
|
253
276
|
const duration = argDuration || hostInfo.retryDuration || 1000;
|
|
254
277
|
if (maxRetry === 1) {
|
|
255
278
|
return func();
|
|
256
279
|
}
|
|
257
280
|
let retryCount = 0;
|
|
258
|
-
|
|
281
|
+
for (;;) {
|
|
259
282
|
let rt;
|
|
260
283
|
try {
|
|
261
284
|
rt = await func();
|
|
@@ -284,6 +307,6 @@ module.exports = {
|
|
|
284
307
|
canConnect,
|
|
285
308
|
connect,
|
|
286
309
|
disconnect,
|
|
287
|
-
|
|
310
|
+
expect,
|
|
288
311
|
retryWrapper
|
|
289
312
|
};
|
package/lib/util.js
CHANGED
|
@@ -26,7 +26,7 @@ const ajv = new Ajv({
|
|
|
26
26
|
});
|
|
27
27
|
require("ajv-keywords")(ajv, "transform");
|
|
28
28
|
|
|
29
|
-
//
|
|
29
|
+
//never validate password and passphrase value to avoid security insident
|
|
30
30
|
const hostInfoSchema = {
|
|
31
31
|
type: "object",
|
|
32
32
|
properties: {
|
|
@@ -40,7 +40,13 @@ const hostInfoSchema = {
|
|
|
40
40
|
ControlPersistDir: { type: "string", pattern: "\\S+", transform: ["trim"] },
|
|
41
41
|
maxRetry: { type: "number", minimum: 0, default: 3 },
|
|
42
42
|
retryDuration: { type: "number", minimum: 0, default: 1000 },
|
|
43
|
-
|
|
43
|
+
rcfile: { type: "string", pattern: "\\S+", transform: ["trim"] },
|
|
44
|
+
prependCmd: { type: "string", pattern: "\\S+", transform: ["trim"] },
|
|
45
|
+
sshOpt: {
|
|
46
|
+
type: "array",
|
|
47
|
+
minItems: 1,
|
|
48
|
+
items: { type: "string", pattern: "\\S+", transform: ["trim"] }
|
|
49
|
+
}
|
|
44
50
|
},
|
|
45
51
|
required: ["host"]
|
|
46
52
|
};
|
|
@@ -64,20 +70,20 @@ const validate = ajv.compile(hostInfoSchema);
|
|
|
64
70
|
* @param {Function} func - function
|
|
65
71
|
* @param {data} data - binary data which can be converted to string
|
|
66
72
|
*/
|
|
67
|
-
const buff2String = (func, data)
|
|
73
|
+
const buff2String = (func, data)=>{
|
|
68
74
|
func(data.toString().replace(/\r\n/g, EOL));
|
|
69
75
|
};
|
|
70
76
|
|
|
71
77
|
/**
|
|
72
78
|
* check if given hostInfo object is OK
|
|
73
|
-
* @param {
|
|
79
|
+
* @param {object} hostInfo - host information object defined in index.js
|
|
74
80
|
*/
|
|
75
|
-
const sanityCheck = (hostInfo)
|
|
81
|
+
const sanityCheck = (hostInfo)=>{
|
|
76
82
|
debug("sanityCheck called", hostInfo);
|
|
77
83
|
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
84
|
+
//keep password, passphrase, and masterPty before validate.
|
|
85
|
+
//these properties can have a iellegal value as JSON data
|
|
86
|
+
//so we keep them before ajv validation and put them back
|
|
81
87
|
|
|
82
88
|
const { password, passphrase, masterPty } = hostInfo;
|
|
83
89
|
validate(hostInfo);
|
|
@@ -100,7 +106,7 @@ const sanityCheck = (hostInfo) => {
|
|
|
100
106
|
}
|
|
101
107
|
if (prop.startsWith("sshOpt")) {
|
|
102
108
|
debug("remove empty member of sshOpt");
|
|
103
|
-
hostInfo.sshOpt = hostInfo.sshOpt.filter((opt)
|
|
109
|
+
hostInfo.sshOpt = hostInfo.sshOpt.filter((opt)=>{
|
|
104
110
|
return opt !== "";
|
|
105
111
|
});
|
|
106
112
|
continue;
|
|
@@ -132,7 +138,7 @@ const sanityCheck = (hostInfo) => {
|
|
|
132
138
|
return hostInfo;
|
|
133
139
|
};
|
|
134
140
|
|
|
135
|
-
function getControlPersistDir
|
|
141
|
+
function getControlPersistDir(hostInfo) {
|
|
136
142
|
for (const candidate of [hostInfo.ControlPersistDir, process.env.SSH_CONTROL_PERSIST_DIR]) {
|
|
137
143
|
if (typeof candidate === "string") {
|
|
138
144
|
const stat = fs.statSync(candidate, { throwIfNoEntry: false });
|
|
@@ -146,10 +152,10 @@ function getControlPersistDir (hostInfo) {
|
|
|
146
152
|
|
|
147
153
|
/**
|
|
148
154
|
* return ssh option with some hard corded options
|
|
149
|
-
* @param {
|
|
155
|
+
* @param {object} hostInfo - host information object defined in index.js
|
|
150
156
|
* @param {boolean} withoutDestination - return except for hostname
|
|
151
157
|
*/
|
|
152
|
-
const getSshOption = (hostInfo, withoutDestination)
|
|
158
|
+
const getSshOption = (hostInfo, withoutDestination)=>{
|
|
153
159
|
debug("getSshOption called");
|
|
154
160
|
const args = [];
|
|
155
161
|
|
|
@@ -203,31 +209,32 @@ const getSshOption = (hostInfo, withoutDestination) => {
|
|
|
203
209
|
args.push(`-oConnectTimeout=${hostInfo.ConnectTimeout}`);
|
|
204
210
|
}
|
|
205
211
|
if (Array.isArray(hostInfo.sshOpt)) {
|
|
206
|
-
args.push(...hostInfo.sshOpt.filter((e)
|
|
212
|
+
args.push(...hostInfo.sshOpt.filter((e)=>{
|
|
207
213
|
return typeof e === "string";
|
|
208
214
|
}));
|
|
209
215
|
}
|
|
216
|
+
|
|
210
217
|
return args;
|
|
211
218
|
};
|
|
212
219
|
|
|
213
|
-
const isArrayOfString = (target)
|
|
220
|
+
const isArrayOfString = (target)=>{
|
|
214
221
|
if (!Array.isArray(target)) {
|
|
215
222
|
return false;
|
|
216
223
|
}
|
|
217
|
-
return !target.some((e)
|
|
218
|
-
//
|
|
224
|
+
return !target.some((e)=>{
|
|
225
|
+
//target has non string member
|
|
219
226
|
return typeof e !== "string";
|
|
220
227
|
});
|
|
221
228
|
};
|
|
222
229
|
|
|
223
230
|
/**
|
|
224
231
|
* log and send message to pty
|
|
225
|
-
* @param {
|
|
232
|
+
* @param {object} pty - pty object which message will be send to
|
|
226
233
|
* @param {string} message - message
|
|
227
234
|
* @param {Function} logger - logging function
|
|
228
235
|
* @param {string} label - where this function is called
|
|
229
236
|
*/
|
|
230
|
-
const sendPty = async (pty, message, logger, label)
|
|
237
|
+
const sendPty = async (pty, message, logger, label)=>{
|
|
231
238
|
logger(`${label}: ${message}`);
|
|
232
239
|
pty.write(message);
|
|
233
240
|
};
|
|
@@ -239,15 +246,15 @@ const sendPty = async (pty, message, logger, label) => {
|
|
|
239
246
|
* @param {Function} cb - call back function which will be called after timeout
|
|
240
247
|
* @returns {AbortController} - ac to stop watch dog timer
|
|
241
248
|
*/
|
|
242
|
-
const watchDogTimer = (timeout, label, cb)
|
|
249
|
+
const watchDogTimer = (timeout, label, cb)=>{
|
|
243
250
|
const ac = new AbortController();
|
|
244
251
|
const signal = ac.signal;
|
|
245
252
|
setTimeoutPromise(timeout * 1000, "neverUsedValue", { signal })
|
|
246
|
-
.then(()
|
|
253
|
+
.then(()=>{
|
|
247
254
|
debug(`can not finish ${label} within ${timeout} sec`);
|
|
248
255
|
return typeof cb === "function" ? cb() : true;
|
|
249
256
|
})
|
|
250
|
-
.catch((err)
|
|
257
|
+
.catch((err)=>{
|
|
251
258
|
if (err.name === "AbortError") {
|
|
252
259
|
debug(`timeout was aborted because ${label} has finished or canceled`);
|
|
253
260
|
} else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ssh-client-wrapper",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.12.4",
|
|
4
4
|
"description": "wrapper library for open ssh client and rsync",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ssh",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"main": "lib/index.js",
|
|
15
15
|
"scripts": {
|
|
16
16
|
"mocha": "mocha --recursive test",
|
|
17
|
-
"lint": "eslint --fix
|
|
17
|
+
"lint": "eslint --fix ./",
|
|
18
18
|
"test": "nyc --reporter=html --reporter=text mocha",
|
|
19
19
|
"coverage": "nyc report --reporter=text-lcov > coverage-report.lcov",
|
|
20
20
|
"postversion": "git push && git push origin $(git describe --abbrev=0 --tags)",
|
|
@@ -34,24 +34,22 @@
|
|
|
34
34
|
"*.js": "eslint --fix"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"ajv": "^8.
|
|
37
|
+
"ajv": "^8.17.1",
|
|
38
38
|
"ajv-keywords": "^5.1.0",
|
|
39
|
-
"debug": "^4.
|
|
40
|
-
"
|
|
39
|
+
"debug": "^4.4.0",
|
|
40
|
+
"eslint-plugin-chai-friendly": "^1.0.1",
|
|
41
|
+
"glob": "^10.4.5",
|
|
41
42
|
"node-pty": "^0.10.1",
|
|
42
43
|
"promise-retry": "^2.0.1"
|
|
43
44
|
},
|
|
44
45
|
"devDependencies": {
|
|
46
|
+
"@eslint/js": "^9.19.0",
|
|
47
|
+
"@stylistic/eslint-plugin": "^3.0.1",
|
|
45
48
|
"chai": "^4.3.7",
|
|
46
49
|
"chai-as-promised": "^7.1.1",
|
|
47
50
|
"chai-fs": "^2.0.0",
|
|
48
|
-
"eslint": "^
|
|
49
|
-
"eslint-
|
|
50
|
-
"eslint-plugin-chai-expect": "^3.0.0",
|
|
51
|
-
"eslint-plugin-chai-friendly": "^0.7.4",
|
|
52
|
-
"eslint-plugin-import": "^2.29.1",
|
|
53
|
-
"eslint-plugin-n": "^16.6.2",
|
|
54
|
-
"eslint-plugin-promise": "^6.1.1",
|
|
51
|
+
"eslint-plugin-jsdoc": "^50.6.3",
|
|
52
|
+
"eslint-plugin-node": "^11.1.0",
|
|
55
53
|
"fs-extra": "^11.1.1",
|
|
56
54
|
"husky": "^8.0.0",
|
|
57
55
|
"lint-staged": "^13.2.1",
|
package/run_test_all.sh
CHANGED
|
@@ -1,21 +1,11 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
|
|
3
2
|
function cleanup(){
|
|
4
|
-
docker compose
|
|
3
|
+
docker compose down
|
|
5
4
|
}
|
|
6
|
-
|
|
7
5
|
trap cleanup EXIT
|
|
8
6
|
set -e -o pipefail
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
export TEST_HOST=localhost
|
|
12
|
-
export TEST_USER=testuser
|
|
13
|
-
export TEST_PW=passw0rd
|
|
14
|
-
export TEST_PORT=4100
|
|
15
|
-
ssh-keygen -R '[localhost]:4100'
|
|
16
|
-
|
|
17
|
-
npm run test
|
|
18
|
-
echo local test success
|
|
8
|
+
./run_test_local.sh keep
|
|
19
9
|
|
|
20
10
|
docker compose run --build --rm tester
|
|
21
11
|
echo test on latest ubuntu success
|
|
@@ -25,3 +15,4 @@ echo test on bullseye success
|
|
|
25
15
|
|
|
26
16
|
docker compose run --build --rm tester-buster
|
|
27
17
|
echo test on buster success
|
|
18
|
+
docker compose down
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
docker compose up testbed -d
|
|
3
|
+
export TEST_HOST=localhost
|
|
4
|
+
export TEST_USER=testuser
|
|
5
|
+
export TEST_PW=passw0rd
|
|
6
|
+
export TEST_PORT=4100
|
|
7
|
+
ssh-keygen -R '[localhost]:4100'
|
|
8
|
+
|
|
9
|
+
npm run test
|
|
10
|
+
echo local test success
|
|
11
|
+
if [ x${1} != xkeep ];then
|
|
12
|
+
docker compose down
|
|
13
|
+
fi
|
package/test/getSshOption.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
process.on("unhandledRejection", console.dir); //
|
|
2
|
+
process.on("unhandledRejection", console.dir); //eslint-disable-line no-console
|
|
3
3
|
|
|
4
|
-
//
|
|
4
|
+
//setup test framework
|
|
5
5
|
const chai = require("chai");
|
|
6
6
|
const { expect } = require("chai");
|
|
7
7
|
chai.use(require("chai-as-promised"));
|
|
8
8
|
|
|
9
|
-
//
|
|
9
|
+
//testee
|
|
10
10
|
const { getSshOption } = require("../lib/util.js");
|
|
11
11
|
|
|
12
|
-
describe("test for getSshOption", ()
|
|
12
|
+
describe("test for getSshOption", ()=>{
|
|
13
13
|
const defaultValues = {
|
|
14
14
|
host: "testHostName",
|
|
15
15
|
ControlPersist: 180,
|
|
16
16
|
maxRetry: 3,
|
|
17
17
|
retryDuration: 1000
|
|
18
18
|
};
|
|
19
|
-
it("should return minimum ssh option array", ()
|
|
19
|
+
it("should return minimum ssh option array", ()=>{
|
|
20
20
|
const sshOpts = getSshOption(defaultValues);
|
|
21
21
|
expect(sshOpts).to.be.an("array");
|
|
22
22
|
expect(sshOpts).to.have.lengthOf(4);
|
|
@@ -25,7 +25,7 @@ describe("test for getSshOption", () => {
|
|
|
25
25
|
expect(sshOpts[2]).to.equal("-oControlPath=~/.ssh/ssh-client-wrapper-%r@%h:%p");
|
|
26
26
|
expect(sshOpts[3]).to.equal("-oControlPersist=180");
|
|
27
27
|
});
|
|
28
|
-
it("should change ControlPath by ControlPersistDir", ()
|
|
28
|
+
it("should change ControlPath by ControlPersistDir", ()=>{
|
|
29
29
|
const hostInfo = { ...defaultValues };
|
|
30
30
|
hostInfo.ControlPersistDir = "/tmp";
|
|
31
31
|
const sshOpts = getSshOption(hostInfo);
|
|
@@ -36,7 +36,7 @@ describe("test for getSshOption", () => {
|
|
|
36
36
|
expect(sshOpts[2]).to.equal("-oControlPath=/tmp/ssh-client-wrapper-%r@%h:%p");
|
|
37
37
|
expect(sshOpts[3]).to.equal("-oControlPersist=180");
|
|
38
38
|
});
|
|
39
|
-
it("should change ControlPath by Environment variable", ()
|
|
39
|
+
it("should change ControlPath by Environment variable", ()=>{
|
|
40
40
|
process.env.SSH_CONTROL_PERSIST_DIR = "/tmp";
|
|
41
41
|
const sshOpts = getSshOption(defaultValues);
|
|
42
42
|
expect(sshOpts).to.be.an("array");
|