ssh-client-wrapper 2.10.2 → 2.11.0
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/{docker-compose.yml → compose.yml} +2 -2
- package/lib/index.js +8 -2
- package/lib/sshExec.js +14 -1
- package/lib/util.js +3 -0
- package/package.json +1 -1
- package/run_test_all.sh +3 -12
- package/run_test_local.sh +13 -0
- package/test/sshExec.js +35 -1
- package/test/testUtil/testrc +1 -0
|
@@ -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"
|
package/lib/index.js
CHANGED
|
@@ -28,6 +28,8 @@ const { isArrayOfString, sanityCheck } = require("./util.js");
|
|
|
28
28
|
* @param {Integer} hostInfo.ConnectTimeout=60 - timeout used when connectiong to ths SSH server
|
|
29
29
|
* @param {Integer} hostInfo.maxRetry=3 - max number of retry
|
|
30
30
|
* @param {Integer} hostInfo.retryDuration=1000 - duration time (msec) between each retry
|
|
31
|
+
* @param {string} hostInfo.rcfile - rcfile path which will be sourced before executing actual command
|
|
32
|
+
* @param {string} hostInfo.prependCmd - command string which will be executed before executing actual command
|
|
31
33
|
* @param {string[]} hostInfo.sshOpt - additional options for ssh
|
|
32
34
|
*/
|
|
33
35
|
|
|
@@ -65,15 +67,19 @@ class SshClientWrapper {
|
|
|
65
67
|
* @param {string} cmd - Cmdline which will be executed.
|
|
66
68
|
* @param {Integer} timeout - timeout in secconds
|
|
67
69
|
* @param { Function } outputCallback - call back routine for output
|
|
70
|
+
* @param {string} rcfile - rcfile path which will be sourced before executing actual command
|
|
71
|
+
* @param {string} prependCmd - command string which will be executed before executing actual command
|
|
68
72
|
* @returns {Integer} - Return code of cmd.
|
|
73
|
+
*
|
|
74
|
+
* note rcfile and prependCmd override hostInfo.rcfile and hostInfo.prependCmd respectively
|
|
69
75
|
*/
|
|
70
|
-
async exec (cmd, timeout = 0, outputCallback = null) {
|
|
76
|
+
async exec (cmd, timeout = 0, outputCallback = null, rcfile = null, prependCmd = null) {
|
|
71
77
|
debug("exec called", cmd);
|
|
72
78
|
|
|
73
79
|
if (typeof cmd !== "string" || cmd === "") {
|
|
74
80
|
return logAndReject("cmd must be string");
|
|
75
81
|
}
|
|
76
|
-
return sshExec(this.hostInfo, cmd, timeout, outputCallback);
|
|
82
|
+
return sshExec(this.hostInfo, cmd, timeout, outputCallback, rcfile, prependCmd);
|
|
77
83
|
}
|
|
78
84
|
|
|
79
85
|
/**
|
package/lib/sshExec.js
CHANGED
|
@@ -12,13 +12,26 @@ const { fork, createMasterPty, sshLoginCallback } = require("./fork.js");
|
|
|
12
12
|
* @param {Object} hostInfo - hostinfo object
|
|
13
13
|
* @param {string} cmd - Cmdline which will be executed on remote host.
|
|
14
14
|
* @param {number} timeout - timeout (in sec.)
|
|
15
|
+
* @param {string} rcfile - rcfile path which will be sourced before executing actual command
|
|
16
|
+
* @param {string} prependCmd - command string which will be executed before executing actual command
|
|
15
17
|
* @param {Function} outputCallback - Callback routine for stdout and stderr.
|
|
16
18
|
*/
|
|
17
|
-
async function sshExec (hostInfo, cmd, timeout, outputCallback) {
|
|
19
|
+
async function sshExec (hostInfo, cmd, timeout, outputCallback, rcfile, prependCmd) {
|
|
18
20
|
debug("sshExec called");
|
|
19
21
|
await connect(hostInfo);
|
|
20
22
|
debug("exec", cmd, "on remote server");
|
|
21
23
|
const args = getSshOption(hostInfo, false);
|
|
24
|
+
if (rcfile) {
|
|
25
|
+
args.push(`. ${rcfile};`);
|
|
26
|
+
} else if (hostInfo.rcfile) {
|
|
27
|
+
args.push(`. ${hostInfo.rcfile};`);
|
|
28
|
+
}
|
|
29
|
+
if (prependCmd) {
|
|
30
|
+
args.push(`${prependCmd};`);
|
|
31
|
+
} else if (hostInfo.prependCmd) {
|
|
32
|
+
args.push(`${hostInfo.prependCmd};`);
|
|
33
|
+
}
|
|
34
|
+
|
|
22
35
|
args.push(cmd);
|
|
23
36
|
debugVerbose(`exec: ${sshCmd} ${args.join(" ")}`);
|
|
24
37
|
|
package/lib/util.js
CHANGED
|
@@ -40,6 +40,8 @@ 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
|
+
rcfile: { type: "string", pattern: "\\S+", transform: ["trim"] },
|
|
44
|
+
prependCmd: { type: "string", pattern: "\\S+", transform: ["trim"] },
|
|
43
45
|
sshOpt: { type: "array", minItems: 1, items: { type: "string", pattern: "\\S+", transform: ["trim"] } }
|
|
44
46
|
},
|
|
45
47
|
required: ["host"]
|
|
@@ -207,6 +209,7 @@ const getSshOption = (hostInfo, withoutDestination) => {
|
|
|
207
209
|
return typeof e === "string";
|
|
208
210
|
}));
|
|
209
211
|
}
|
|
212
|
+
|
|
210
213
|
return args;
|
|
211
214
|
};
|
|
212
215
|
|
package/package.json
CHANGED
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/sshExec.js
CHANGED
|
@@ -19,17 +19,23 @@ const { sshExec, canConnect, disconnect, ls } = require("../lib/sshExec.js");
|
|
|
19
19
|
// test helpers
|
|
20
20
|
const hostInfo = require("./testUtil/hostInfo.js");
|
|
21
21
|
const { clearRemoteTestFiles, clearLocalTestFiles, createRemoteFiles, nonExisting, remoteRoot, remoteEmptyDir } = require("./testUtil/testFiles.js");
|
|
22
|
+
const { send } = require("../lib/rsyncExec.js");
|
|
22
23
|
|
|
23
24
|
describe("test for ssh execution", function () {
|
|
25
|
+
const rcfilePath = "/home/testuser/testrc";
|
|
24
26
|
this.timeout(65000);// eslint-disable-line no-invalid-this
|
|
25
27
|
beforeEach(async () => {
|
|
26
28
|
sshout.reset();
|
|
27
29
|
await clearRemoteTestFiles(hostInfo);
|
|
28
30
|
});
|
|
31
|
+
before(async () => {
|
|
32
|
+
await send(hostInfo, [path.resolve(__dirname, "./testUtil/testrc")], rcfilePath, [], 0);
|
|
33
|
+
});
|
|
29
34
|
after(async () => {
|
|
30
35
|
if (!process.env.TEST_KEEP_FILES) {
|
|
31
36
|
await clearRemoteTestFiles(hostInfo);
|
|
32
37
|
await clearLocalTestFiles();
|
|
38
|
+
await sshExec(hostInfo, `rm ${rcfilePath}`);
|
|
33
39
|
}
|
|
34
40
|
await disconnect(hostInfo);
|
|
35
41
|
});
|
|
@@ -48,8 +54,36 @@ describe("test for ssh execution", function () {
|
|
|
48
54
|
expect(sshout).to.be.called;
|
|
49
55
|
expect(sshout).to.be.calledWithMatch(/^hoge/);
|
|
50
56
|
});
|
|
57
|
+
it("should execute 2 command by prependCmd ", async () => {
|
|
58
|
+
const rt = await sshExec(hostInfo, `echo ${testText}`, 0, sshout, null, "echo foo");
|
|
59
|
+
expect(rt).to.equal(0);
|
|
60
|
+
expect(sshout).to.be.calledOnce;
|
|
61
|
+
expect(sshout).to.be.calledWithMatch(/^foo\nhoge\n/);
|
|
62
|
+
});
|
|
63
|
+
it("should execute 2 command by hostInfo.prependCmd ", async () => {
|
|
64
|
+
hostInfo.prependCmd = "echo foo";
|
|
65
|
+
const rt = await sshExec(hostInfo, `echo ${testText}`, 0, sshout, null);
|
|
66
|
+
expect(rt).to.equal(0);
|
|
67
|
+
expect(sshout).to.be.calledOnce;
|
|
68
|
+
expect(sshout).to.be.calledWithMatch(/^foo\nhoge\n/);
|
|
69
|
+
delete hostInfo.prependCmd;
|
|
70
|
+
});
|
|
71
|
+
it("should execute 2 command by rcfile", async () => {
|
|
72
|
+
const rt = await sshExec(hostInfo, `echo ${testText}`, 0, sshout, rcfilePath);
|
|
73
|
+
expect(rt).to.equal(0);
|
|
74
|
+
expect(sshout).to.be.calledOnce;
|
|
75
|
+
expect(sshout).to.be.calledWithMatch(/^this is test rcfile\nhoge\n/);
|
|
76
|
+
});
|
|
77
|
+
it("should execute 2 command by hostInfo.prependCmd ", async () => {
|
|
78
|
+
hostInfo.rcfile = rcfilePath;
|
|
79
|
+
const rt = await sshExec(hostInfo, `echo ${testText}`, 0, sshout, null);
|
|
80
|
+
expect(rt).to.equal(0);
|
|
81
|
+
expect(sshout).to.be.calledOnce;
|
|
82
|
+
expect(sshout).to.be.calledWithMatch(/^this is test rcfile\nhoge\n/);
|
|
83
|
+
delete hostInfo.rcfile;
|
|
84
|
+
});
|
|
51
85
|
// please note that exec() resolves with non-zero value
|
|
52
|
-
// (126
|
|
86
|
+
// (126 permission deny or 127 file not found)
|
|
53
87
|
// but does not reject in following 2 cases
|
|
54
88
|
it("should not execute command which do not have exec permission", async () => {
|
|
55
89
|
await sshExec(hostInfo, "echo echo hoge >hoge");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
echo this is test rcfile
|