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
|
@@ -21,7 +21,6 @@ jobs:
|
|
|
21
21
|
TEST_USER: testuser
|
|
22
22
|
TEST_PW: passw0rd
|
|
23
23
|
TEST_PORT: 10022
|
|
24
|
-
DEBUG: 'ssh*'
|
|
25
24
|
runs-on: ubuntu-latest
|
|
26
25
|
strategy:
|
|
27
26
|
matrix:
|
|
@@ -35,7 +34,9 @@ jobs:
|
|
|
35
34
|
- run: npm ci
|
|
36
35
|
- run: npm run test
|
|
37
36
|
- run: npm run coverage
|
|
38
|
-
|
|
37
|
+
if: matrix.node-version == '20.x'
|
|
38
|
+
- uses: actions/upload-artifact@v4
|
|
39
|
+
if: matrix.node-version == '20.x'
|
|
39
40
|
with:
|
|
40
41
|
name: code-coverage-report
|
|
41
42
|
path: coverage-report.lcov
|
|
@@ -23,7 +23,7 @@ services:
|
|
|
23
23
|
TEST_PW: passw0rd
|
|
24
24
|
TEST_HOST: testbed
|
|
25
25
|
working_dir: /work/ssh-client-wrapper
|
|
26
|
-
command: bash -c "mkdir /root/.ssh 2>/dev/null;
|
|
26
|
+
command: bash -c "mkdir /root/.ssh 2>/dev/null; npm run mocha"
|
|
27
27
|
tester-buster:
|
|
28
28
|
build:
|
|
29
29
|
context: .
|
|
@@ -33,4 +33,4 @@ services:
|
|
|
33
33
|
TEST_PW: passw0rd
|
|
34
34
|
TEST_HOST: testbed
|
|
35
35
|
working_dir: /work/ssh-client-wrapper
|
|
36
|
-
command: bash -c "mkdir /root/.ssh 2>/dev/null;
|
|
36
|
+
command: bash -c "mkdir /root/.ssh 2>/dev/null; npm run mocha"
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
import js from "@eslint/js";
|
|
3
|
+
import globals from "globals";
|
|
4
|
+
import stylistic from "@stylistic/eslint-plugin";
|
|
5
|
+
import jsdoc from "eslint-plugin-jsdoc";
|
|
6
|
+
import node from "eslint-plugin-node";
|
|
7
|
+
import chaiFriendly from "eslint-plugin-chai-friendly";
|
|
8
|
+
|
|
9
|
+
const jsdocRules = {
|
|
10
|
+
"jsdoc/require-jsdoc": ["warn", { enableFixer: false }],
|
|
11
|
+
"jsdoc/require-param": ["warn", { enableFixer: false }],
|
|
12
|
+
"jsdoc/require-param-description": "warn",
|
|
13
|
+
"jsdoc/require-param-name": "warn",
|
|
14
|
+
"jsdoc/require-param-type": "warn",
|
|
15
|
+
"jsdoc/require-hyphen-before-param-description": [
|
|
16
|
+
"warn",
|
|
17
|
+
"always",
|
|
18
|
+
],
|
|
19
|
+
"jsdoc/require-returns": "warn",
|
|
20
|
+
"jsdoc/require-returns-check": "error",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const styleRules = {
|
|
24
|
+
"@stylistic/arrow-spacing": [
|
|
25
|
+
"error",
|
|
26
|
+
{
|
|
27
|
+
after: false,
|
|
28
|
+
before: false,
|
|
29
|
+
}],
|
|
30
|
+
"@stylistic/comma-dangle": [
|
|
31
|
+
"error",
|
|
32
|
+
"never",
|
|
33
|
+
],
|
|
34
|
+
"@stylistic/spaced-comment": [
|
|
35
|
+
"error",
|
|
36
|
+
"never",
|
|
37
|
+
],
|
|
38
|
+
"@stylistic/lines-around-comment": [
|
|
39
|
+
"error",
|
|
40
|
+
{
|
|
41
|
+
beforeBlockComment: true,
|
|
42
|
+
afterBlockComment: false,
|
|
43
|
+
beforeLineComment: false,
|
|
44
|
+
afterLineComment: false,
|
|
45
|
+
allowBlockStart: true,
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
"@stylistic/lines-between-class-members": [
|
|
49
|
+
"error",
|
|
50
|
+
"always",
|
|
51
|
+
{
|
|
52
|
+
exceptAfterSingleLine: true,
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
"@stylistic/newline-per-chained-call": [
|
|
56
|
+
"error",
|
|
57
|
+
{
|
|
58
|
+
ignoreChainWithDepth: 2,
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
"@stylistic/padded-blocks": [
|
|
62
|
+
"error",
|
|
63
|
+
"never",
|
|
64
|
+
],
|
|
65
|
+
"@stylistic/brace-style": [
|
|
66
|
+
"error",
|
|
67
|
+
"1tbs",
|
|
68
|
+
{
|
|
69
|
+
allowSingleLine: true,
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
"@stylistic/padding-line-between-statements": [
|
|
73
|
+
"error",
|
|
74
|
+
{
|
|
75
|
+
blankLine: "any",
|
|
76
|
+
prev: [
|
|
77
|
+
"const",
|
|
78
|
+
"let",
|
|
79
|
+
"var",
|
|
80
|
+
],
|
|
81
|
+
next: "*",
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
blankLine: "always",
|
|
85
|
+
prev: "*",
|
|
86
|
+
next: [
|
|
87
|
+
"class",
|
|
88
|
+
"do",
|
|
89
|
+
"for",
|
|
90
|
+
"function",
|
|
91
|
+
"switch",
|
|
92
|
+
"try",
|
|
93
|
+
"while",
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
blankLine: "any",
|
|
98
|
+
prev: [
|
|
99
|
+
"const",
|
|
100
|
+
"let",
|
|
101
|
+
"var",
|
|
102
|
+
"for",
|
|
103
|
+
"while",
|
|
104
|
+
"do",
|
|
105
|
+
"block-like",
|
|
106
|
+
"multiline-block-like",
|
|
107
|
+
],
|
|
108
|
+
next: [
|
|
109
|
+
"block-like",
|
|
110
|
+
"do",
|
|
111
|
+
"for",
|
|
112
|
+
"multiline-block-like",
|
|
113
|
+
"switch",
|
|
114
|
+
"try",
|
|
115
|
+
"while",
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export default [
|
|
122
|
+
js.configs.recommended,
|
|
123
|
+
jsdoc.configs["flat/recommended"],
|
|
124
|
+
stylistic.configs["disable-legacy"],
|
|
125
|
+
stylistic.configs.customize({
|
|
126
|
+
indent: 2,
|
|
127
|
+
quotes: "double",
|
|
128
|
+
semi: true,
|
|
129
|
+
arrowParens: true,
|
|
130
|
+
}),
|
|
131
|
+
{
|
|
132
|
+
ignores: ["node_modules/"],
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
files: ["test/**/*.js"],
|
|
136
|
+
plugins: {
|
|
137
|
+
chaiFriendly,
|
|
138
|
+
},
|
|
139
|
+
languageOptions: {
|
|
140
|
+
globals: {
|
|
141
|
+
...globals.nodeBuiltin,
|
|
142
|
+
...globals.node,
|
|
143
|
+
it: "readonly",
|
|
144
|
+
describe: "readonly",
|
|
145
|
+
before: "readonly",
|
|
146
|
+
after: "readonly",
|
|
147
|
+
beforeEach: "readonly",
|
|
148
|
+
afterEach: "readonly",
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
files: ["**/*.js"],
|
|
154
|
+
plugins: {
|
|
155
|
+
node,
|
|
156
|
+
"@stylistic": stylistic,
|
|
157
|
+
jsdoc,
|
|
158
|
+
},
|
|
159
|
+
languageOptions: {
|
|
160
|
+
globals: {
|
|
161
|
+
...globals.nodeBuiltin,
|
|
162
|
+
...globals.node,
|
|
163
|
+
},
|
|
164
|
+
sourceType: "commonjs",
|
|
165
|
+
},
|
|
166
|
+
rules: {
|
|
167
|
+
...styleRules,
|
|
168
|
+
...jsdocRules,
|
|
169
|
+
"node/exports-style": [
|
|
170
|
+
"error",
|
|
171
|
+
"module.exports",
|
|
172
|
+
],
|
|
173
|
+
"no-nested-ternary": "off",
|
|
174
|
+
"no-param-reassign": "warn",
|
|
175
|
+
"camelcase": [
|
|
176
|
+
"error",
|
|
177
|
+
{
|
|
178
|
+
properties: "never",
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
"eqeqeq": [
|
|
182
|
+
"error",
|
|
183
|
+
"always",
|
|
184
|
+
{
|
|
185
|
+
null: "ignore",
|
|
186
|
+
},
|
|
187
|
+
],
|
|
188
|
+
"func-style": [
|
|
189
|
+
"error",
|
|
190
|
+
"declaration",
|
|
191
|
+
{
|
|
192
|
+
allowArrowFunctions: true,
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
"no-use-before-define": [
|
|
196
|
+
"error",
|
|
197
|
+
{
|
|
198
|
+
functions: false,
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
"no-warning-comments": "warn",
|
|
202
|
+
"require-unicode-regexp": "off",
|
|
203
|
+
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
];
|
package/lib/fork.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const debug = require("debug")("sshClientWrapper:debug:fork");
|
|
3
3
|
const debugVerbose = require("debug")("sshClientWrapper:verbose:fork");
|
|
4
|
-
const debugSendPty = require("debug")("
|
|
4
|
+
const debugSendPty = require("debug")("sshClientWrapper:insecure:fork");
|
|
5
5
|
const { spawn } = require("node-pty");
|
|
6
6
|
|
|
7
7
|
const { rePwPrompt, rePhPrompt, reNewHostPrompt, reRemoteHostIdentificationHasChanged, buff2String, sendPty, watchDogTimer } = require("./util.js");
|
|
@@ -11,8 +11,8 @@ const nodePtyOpt = {
|
|
|
11
11
|
windowsHide: true
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
function sshLoginCallback
|
|
15
|
-
return new Promise((resolve, reject)
|
|
14
|
+
function sshLoginCallback(output, ptyProcess, pw, ph, logger = debugSendPty) {
|
|
15
|
+
return new Promise((resolve, reject)=>{
|
|
16
16
|
if (reRemoteHostIdentificationHasChanged.test(output)) {
|
|
17
17
|
debug("REMOTE HOST IDENTIFICATION HAS CHANGED");
|
|
18
18
|
const rt = /known_hosts:(\d+)/.exec(output);
|
|
@@ -38,10 +38,10 @@ function sshLoginCallback (output, ptyProcess, pw, ph, logger = debugSendPty) {
|
|
|
38
38
|
if (typeof pw === "function") {
|
|
39
39
|
debug("call password callback function");
|
|
40
40
|
pw()
|
|
41
|
-
.then((v)
|
|
41
|
+
.then((v)=>{
|
|
42
42
|
sendPty(ptyProcess, `${v}\n`, logger, 30);
|
|
43
43
|
})
|
|
44
|
-
.catch((e)
|
|
44
|
+
.catch((e)=>{
|
|
45
45
|
reject(e);
|
|
46
46
|
});
|
|
47
47
|
resolve();
|
|
@@ -56,10 +56,10 @@ function sshLoginCallback (output, ptyProcess, pw, ph, logger = debugSendPty) {
|
|
|
56
56
|
if (typeof ph === "function") {
|
|
57
57
|
debug("call passphrase callback function");
|
|
58
58
|
ph()
|
|
59
|
-
.then((v)
|
|
59
|
+
.then((v)=>{
|
|
60
60
|
sendPty(ptyProcess, `${v}\n`, logger, 45);
|
|
61
61
|
})
|
|
62
|
-
.catch((e)
|
|
62
|
+
.catch((e)=>{
|
|
63
63
|
reject(e);
|
|
64
64
|
});
|
|
65
65
|
}
|
|
@@ -70,7 +70,7 @@ function sshLoginCallback (output, ptyProcess, pw, ph, logger = debugSendPty) {
|
|
|
70
70
|
/**
|
|
71
71
|
* create pty for master ssh session and store to hostInfo.masterPty
|
|
72
72
|
*/
|
|
73
|
-
function createMasterPty
|
|
73
|
+
function createMasterPty(hostInfo) {
|
|
74
74
|
debug("fork pty for master connection");
|
|
75
75
|
|
|
76
76
|
if (hostInfo.masterPty) {
|
|
@@ -81,13 +81,13 @@ function createMasterPty (hostInfo) {
|
|
|
81
81
|
hostInfo.masterPty = ptyProcess;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
async function fork
|
|
85
|
-
return new Promise((resolve, reject)
|
|
84
|
+
async function fork(hostInfo, cmd, args, timeout, outputCallback, retryableExitCodes = [], expects = []) {
|
|
85
|
+
return new Promise((resolve, reject)=>{
|
|
86
86
|
debug(`cmd=${cmd}: args=${args}`);
|
|
87
87
|
const ptyProcess = spawn(cmd, args, nodePtyOpt);
|
|
88
88
|
let ac = null;
|
|
89
89
|
if (timeout > 0) {
|
|
90
|
-
ac = watchDogTimer(timeout, cmd, ()
|
|
90
|
+
ac = watchDogTimer(timeout, cmd, ()=>{
|
|
91
91
|
ptyProcess.kill();
|
|
92
92
|
const err = new Error(`watchdog timer expired ${timeout}`);
|
|
93
93
|
err.code = "TIMEEXPIRE";
|
|
@@ -97,7 +97,7 @@ async function fork (hostInfo, cmd, args, timeout, outputCallback, retryableExit
|
|
|
97
97
|
ptyProcess.onData(buff2String.bind(null, debugVerbose));
|
|
98
98
|
|
|
99
99
|
if (hostInfo !== null) {
|
|
100
|
-
ptyProcess.onData(
|
|
100
|
+
ptyProcess.onData((data)=>{
|
|
101
101
|
const output = data.toString();
|
|
102
102
|
sshLoginCallback(output, ptyProcess, hostInfo.password, hostInfo.passphrase)
|
|
103
103
|
.catch(reject);
|
|
@@ -108,13 +108,25 @@ async function fork (hostInfo, cmd, args, timeout, outputCallback, retryableExit
|
|
|
108
108
|
err.retryable = true;
|
|
109
109
|
reject(err);
|
|
110
110
|
}
|
|
111
|
+
|
|
112
|
+
if (Array.isArray(expects) && expects.length > 0) {
|
|
113
|
+
const { reExpect, send, numKeep } = expects.shift();
|
|
114
|
+
if (reExpect.test(output)) {
|
|
115
|
+
sendPty(ptyProcess, `${send}\n`, debugSendPty, "userDefinedHandler").catch(reject);
|
|
116
|
+
if (numKeep > 0) {
|
|
117
|
+
expects.unshift({ reExpect, send, numKeep: numKeep - 1 });
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
expects.unshift({ reExpect, send, numKeep });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
111
123
|
});
|
|
112
124
|
}
|
|
113
125
|
|
|
114
126
|
if (typeof outputCallback === "function") {
|
|
115
127
|
ptyProcess.onData(buff2String.bind(null, outputCallback));
|
|
116
128
|
}
|
|
117
|
-
ptyProcess.onExit(({ exitCode, signal })
|
|
129
|
+
ptyProcess.onExit(({ exitCode, signal })=>{
|
|
118
130
|
debug("onExit", exitCode, signal);
|
|
119
131
|
|
|
120
132
|
if (ac !== null) {
|
package/lib/index.js
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const fs = require("fs");
|
|
3
3
|
const path = require("path");
|
|
4
|
-
const debug = require("debug")("sshClientWrapper:interface");
|
|
4
|
+
const debug = require("debug")("sshClientWrapper:debug:interface");
|
|
5
5
|
const debugVerbose = require("debug")("sshClientWrapper:verbose:interface");
|
|
6
|
-
const { sshExec, canConnect, disconnect,
|
|
6
|
+
const { sshExec, canConnect, disconnect, expect } = require("./sshExec.js");
|
|
7
7
|
const { send, recv } = require("./rsyncExec.js");
|
|
8
8
|
const { isArrayOfString, sanityCheck } = require("./util.js");
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Integer means integer number but it is defined as Object for now
|
|
12
12
|
* workaround for eslint-plugin-jsdoc's no-undefined-types rule bug.
|
|
13
|
-
* @typedef {
|
|
13
|
+
* @typedef {object} Integer
|
|
14
|
+
* @property
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* host info object which contain all settings for 1 remote host
|
|
18
19
|
* it will be (shallow) copied in constructor.
|
|
19
|
-
* @param {
|
|
20
|
+
* @param {object} hostInfo - option object
|
|
20
21
|
* @param {string} hostInfo.host - destination
|
|
21
22
|
* @param {string} hostInfo.user - login name
|
|
22
23
|
* @param {number} hostInfo.port - port number
|
|
@@ -28,20 +29,22 @@ const { isArrayOfString, sanityCheck } = require("./util.js");
|
|
|
28
29
|
* @param {Integer} hostInfo.ConnectTimeout=60 - timeout used when connectiong to ths SSH server
|
|
29
30
|
* @param {Integer} hostInfo.maxRetry=3 - max number of retry
|
|
30
31
|
* @param {Integer} hostInfo.retryDuration=1000 - duration time (msec) between each retry
|
|
32
|
+
* @param {string} hostInfo.rcfile - rcfile path which will be sourced before executing actual command
|
|
33
|
+
* @param {string} hostInfo.prependCmd - command string which will be executed before executing actual command
|
|
31
34
|
* @param {string[]} hostInfo.sshOpt - additional options for ssh
|
|
32
35
|
*/
|
|
33
36
|
|
|
34
|
-
const logAndReject = (message)
|
|
37
|
+
const logAndReject = (message)=>{
|
|
35
38
|
debug(message);
|
|
36
39
|
return Promise.reject(new Error(message));
|
|
37
40
|
};
|
|
38
41
|
|
|
39
42
|
/**
|
|
40
43
|
* Facade class.
|
|
41
|
-
* @
|
|
44
|
+
* @class
|
|
42
45
|
*/
|
|
43
46
|
class SshClientWrapper {
|
|
44
|
-
constructor
|
|
47
|
+
constructor(hostInfo) {
|
|
45
48
|
debug("constructor called for", hostInfo.host);
|
|
46
49
|
debugVerbose("hostInfo=", hostInfo);
|
|
47
50
|
this.hostInfo = { ...hostInfo };
|
|
@@ -65,15 +68,52 @@ class SshClientWrapper {
|
|
|
65
68
|
* @param {string} cmd - Cmdline which will be executed.
|
|
66
69
|
* @param {Integer} timeout - timeout in secconds
|
|
67
70
|
* @param { Function } outputCallback - call back routine for output
|
|
71
|
+
* @param {string} rcfile - rcfile path which will be sourced before executing actual command
|
|
72
|
+
* @param {string} prependCmd - command string which will be executed before executing actual command
|
|
68
73
|
* @returns {Integer} - Return code of cmd.
|
|
74
|
+
*
|
|
75
|
+
* note rcfile and prependCmd override hostInfo.rcfile and hostInfo.prependCmd respectively
|
|
69
76
|
*/
|
|
70
|
-
async exec
|
|
77
|
+
async exec(cmd, timeout = 0, outputCallback = null, rcfile = null, prependCmd = null) {
|
|
71
78
|
debug("exec called", cmd);
|
|
72
79
|
|
|
73
80
|
if (typeof cmd !== "string" || cmd === "") {
|
|
74
81
|
return logAndReject("cmd must be string");
|
|
75
82
|
}
|
|
76
|
-
return sshExec(this.hostInfo, cmd, timeout, outputCallback);
|
|
83
|
+
return sshExec(this.hostInfo, cmd, timeout, outputCallback, rcfile, prependCmd);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* execute command on remote host and get output text
|
|
88
|
+
* @param {string} cmd - Cmdline which will be executed.
|
|
89
|
+
* @param {Integer} timeout - timeout in secconds
|
|
90
|
+
* @param {string} rcfile - rcfile path which will be sourced before executing actual command
|
|
91
|
+
* @param {string} prependCmd - command string which will be executed before executing actual command
|
|
92
|
+
* @returns {object} - output test array (output) and return value of cmd (rt)
|
|
93
|
+
*/
|
|
94
|
+
async execAndGetOutput(cmd, timeout = 0, rcfile = null, prependCmd = null) {
|
|
95
|
+
debug("execAndGetOutput called", cmd);
|
|
96
|
+
const outputArray = [];
|
|
97
|
+
const cb = (data)=>{
|
|
98
|
+
outputArray.push(data);
|
|
99
|
+
};
|
|
100
|
+
let rt = null;
|
|
101
|
+
try {
|
|
102
|
+
rt = await sshExec(this.hostInfo, cmd, timeout, cb, rcfile, prependCmd);
|
|
103
|
+
} catch (e) {
|
|
104
|
+
//non-zero retrun should be allowed
|
|
105
|
+
if (typeof e.rt === "undefined" || e.rt < 0) {
|
|
106
|
+
throw e;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const output = outputArray.length === 0
|
|
110
|
+
? []
|
|
111
|
+
: outputArray
|
|
112
|
+
.join("\n")
|
|
113
|
+
.replace(/\n+/g, "\n")
|
|
114
|
+
.replace(/\n$/, "")
|
|
115
|
+
.split("\n");
|
|
116
|
+
return { output, rt };
|
|
77
117
|
}
|
|
78
118
|
|
|
79
119
|
/**
|
|
@@ -83,9 +123,22 @@ class SshClientWrapper {
|
|
|
83
123
|
* @param {Integer} timeout - timeout in secconds
|
|
84
124
|
* @returns {string[]} - output from ls
|
|
85
125
|
*/
|
|
86
|
-
async ls
|
|
126
|
+
async ls(target, lsOpt = [], timeout = 0) {
|
|
87
127
|
debug("ls called", target, lsOpt);
|
|
88
|
-
|
|
128
|
+
const { output, rt } = await this.execAndGetOutput(`ls ${lsOpt.join(" ")} ${target}`, timeout);
|
|
129
|
+
return rt === 0 ? output : rt;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* execute series of comands like expect(3)
|
|
134
|
+
* @param {string} cmd - Cmdline which will be executed.
|
|
135
|
+
* @param {Array} expects - array of expect and send string pairs
|
|
136
|
+
* @param {Integer} timeout - timeout in secconds
|
|
137
|
+
* @returns {Integer} - Return code of cmd.
|
|
138
|
+
*/
|
|
139
|
+
async expect(cmd, expects, timeout = 180) {
|
|
140
|
+
debug("expect called", expects);
|
|
141
|
+
return expect(this.hostInfo, cmd, expects, timeout);
|
|
89
142
|
}
|
|
90
143
|
|
|
91
144
|
/**
|
|
@@ -96,13 +149,13 @@ class SshClientWrapper {
|
|
|
96
149
|
* @param {Integer} timeout - timeout in secconds
|
|
97
150
|
* @returns {Promise} - Resolved with undefined when file transfer is done.
|
|
98
151
|
*/
|
|
99
|
-
async send
|
|
152
|
+
async send(src, dst, opt = [], timeout = 0) {
|
|
100
153
|
debug("send called", src, dst, opt);
|
|
101
154
|
|
|
102
155
|
if (!isArrayOfString(src)) {
|
|
103
156
|
return logAndReject("src must be array of string");
|
|
104
157
|
}
|
|
105
|
-
if (!src.some((e)
|
|
158
|
+
if (!src.some((e)=>{
|
|
106
159
|
return e !== "";
|
|
107
160
|
})) {
|
|
108
161
|
return logAndReject("src must contain non-empty string");
|
|
@@ -127,13 +180,13 @@ class SshClientWrapper {
|
|
|
127
180
|
* @param {Integer} timeout - timeout in secconds
|
|
128
181
|
* @returns {Promise} - Resolved with undefined when file transfer is done.
|
|
129
182
|
*/
|
|
130
|
-
async recv
|
|
183
|
+
async recv(src, dst, opt = [], timeout = 0) {
|
|
131
184
|
debug("recv called", src, dst, opt);
|
|
132
185
|
|
|
133
186
|
if (!isArrayOfString(src)) {
|
|
134
187
|
return logAndReject("src must be array of string");
|
|
135
188
|
}
|
|
136
|
-
if (!src.some((e)
|
|
189
|
+
if (!src.some((e)=>{
|
|
137
190
|
return e !== "";
|
|
138
191
|
})) {
|
|
139
192
|
return logAndReject("src must contain non-empty string");
|
|
@@ -155,7 +208,7 @@ class SshClientWrapper {
|
|
|
155
208
|
* @param {Integer} timeout - timeout in secconds
|
|
156
209
|
* @returns {Promise} - Resolved with true on success, otherwise rejected with Error.
|
|
157
210
|
*/
|
|
158
|
-
async canConnect
|
|
211
|
+
async canConnect(timeout = 60) {
|
|
159
212
|
debug("canConnect called with timeout=", timeout);
|
|
160
213
|
debugVerbose("hostInfo=", this.hostInfo);
|
|
161
214
|
|
|
@@ -168,7 +221,7 @@ class SshClientWrapper {
|
|
|
168
221
|
/**
|
|
169
222
|
* Disconnect master session
|
|
170
223
|
*/
|
|
171
|
-
async disconnect
|
|
224
|
+
async disconnect() {
|
|
172
225
|
debug(`disconnect from ${this.hostInfo.host} called`);
|
|
173
226
|
disconnect(this.hostInfo);
|
|
174
227
|
}
|
package/lib/rsyncExec.js
CHANGED
|
@@ -10,9 +10,9 @@ const { fork } = require("./fork.js");
|
|
|
10
10
|
|
|
11
11
|
const { sshCmd, rsyncCmd, acceptableRsyncRetrunCodes, getSshOption } = require("./util.js");
|
|
12
12
|
|
|
13
|
-
async function checkRsyncVersion
|
|
13
|
+
async function checkRsyncVersion(timeout) {
|
|
14
14
|
const rt = {};
|
|
15
|
-
await fork(null, rsyncCmd, ["--version"], timeout, (output)
|
|
15
|
+
await fork(null, rsyncCmd, ["--version"], timeout, (output)=>{
|
|
16
16
|
const result = /^rsync *version *(\d+)\.(\d+)\.(\d+).*$/m.exec(output);
|
|
17
17
|
|
|
18
18
|
if (result) {
|
|
@@ -26,7 +26,7 @@ async function checkRsyncVersion (timeout) {
|
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Send file or directory and its child to server.
|
|
29
|
-
* @param {
|
|
29
|
+
* @param {object} hostInfo - hostinfo object
|
|
30
30
|
* @param {[string]} argSrc - File or directory name which to be send.
|
|
31
31
|
* @param {string} dst - Destination path.
|
|
32
32
|
* @param {[string]} rsyncOpt - optional arguments for rsync
|
|
@@ -34,13 +34,13 @@ async function checkRsyncVersion (timeout) {
|
|
|
34
34
|
*
|
|
35
35
|
* this function execute rsync -avv --copy-unsafe-links src rsync://[user@]host[:port]/dst
|
|
36
36
|
*/
|
|
37
|
-
async function send
|
|
37
|
+
async function send(hostInfo, argSrc, dst, rsyncOpt, timeout) {
|
|
38
38
|
await connect(hostInfo);
|
|
39
|
-
const promises = await Promise.allSettled(argSrc.map((e)
|
|
39
|
+
const promises = await Promise.allSettled(argSrc.map((e)=>{
|
|
40
40
|
return glob(e);
|
|
41
41
|
}));
|
|
42
42
|
const src = promises
|
|
43
|
-
.map((e, i)
|
|
43
|
+
.map((e, i)=>{
|
|
44
44
|
if (e.status !== "fulfilled") {
|
|
45
45
|
return null;
|
|
46
46
|
}
|
|
@@ -49,10 +49,10 @@ async function send (hostInfo, argSrc, dst, rsyncOpt, timeout) {
|
|
|
49
49
|
}
|
|
50
50
|
return e.value;
|
|
51
51
|
})
|
|
52
|
-
.filter((e)
|
|
52
|
+
.filter((e)=>{
|
|
53
53
|
return e !== null;
|
|
54
54
|
})
|
|
55
|
-
.flatMap((e)
|
|
55
|
+
.flatMap((e)=>{
|
|
56
56
|
return e;
|
|
57
57
|
});
|
|
58
58
|
|
|
@@ -85,14 +85,14 @@ async function send (hostInfo, argSrc, dst, rsyncOpt, timeout) {
|
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
87
|
* format src array to src string for recv
|
|
88
|
-
* @param {
|
|
88
|
+
* @param {object} hostInfo - hostinfo object
|
|
89
89
|
* @returns {boolean} - rsync command version is higher than 3.2.4 or not
|
|
90
90
|
*/
|
|
91
|
-
async function isNewRsync
|
|
92
|
-
if (!hostInfo.rsyncVersion
|
|
93
|
-
!Number.isInteger(hostInfo.rsyncVersion.major)
|
|
94
|
-
!Number.isInteger(hostInfo.rsyncVersion.minor)
|
|
95
|
-
!Number.isInteger(hostInfo.rsyncVersion.patch)) {
|
|
91
|
+
async function isNewRsync(hostInfo) {
|
|
92
|
+
if (!hostInfo.rsyncVersion
|
|
93
|
+
|| !Number.isInteger(hostInfo.rsyncVersion.major)
|
|
94
|
+
|| !Number.isInteger(hostInfo.rsyncVersion.minor)
|
|
95
|
+
|| !Number.isInteger(hostInfo.rsyncVersion.patch)) {
|
|
96
96
|
hostInfo.rsyncVersion = await checkRsyncVersion(3);
|
|
97
97
|
}
|
|
98
98
|
return hostInfo.rsyncVersion.major >= 3 && hostInfo.rsyncVersion.minor >= 2 && hostInfo.rsyncVersion.patch >= 4;
|
|
@@ -100,7 +100,7 @@ async function isNewRsync (hostInfo) {
|
|
|
100
100
|
|
|
101
101
|
/**
|
|
102
102
|
* Send file or directory and its child to server.
|
|
103
|
-
* @param {
|
|
103
|
+
* @param {object} hostInfo - hostinfo object
|
|
104
104
|
* @param {[string]} argSrc - File or directory name which to be send.
|
|
105
105
|
* @param {string} dst - Destination path.
|
|
106
106
|
* @param {[string]} rsyncOpt - optional arguments for rsync
|
|
@@ -108,7 +108,7 @@ async function isNewRsync (hostInfo) {
|
|
|
108
108
|
*
|
|
109
109
|
* this function execute rsync -av --copy-unsafe-links rsync://[user@]host[:port]/src dst
|
|
110
110
|
*/
|
|
111
|
-
async function recv
|
|
111
|
+
async function recv(hostInfo, argSrc, dst, rsyncOpt, timeout) {
|
|
112
112
|
await connect(hostInfo);
|
|
113
113
|
const src = `${hostInfo.host}:${argSrc.join(" ")}`;
|
|
114
114
|
const sshArgs = getSshOption(hostInfo, true);
|
|
@@ -118,7 +118,7 @@ async function recv (hostInfo, argSrc, dst, rsyncOpt, timeout) {
|
|
|
118
118
|
args.unshift("--old-args");
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
//
|
|
121
|
+
//make destination directory
|
|
122
122
|
const dstDir = argSrc.len > 1 || hasMagic(argSrc[0]) ? dst : path.dirname(dst);
|
|
123
123
|
await fs.mkdir(dstDir, { recursive: true });
|
|
124
124
|
|