ssh-client-wrapper 2.13.2 → 2.15.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/README.md +34 -0
- package/lib/index.d.ts +61 -0
- package/lib/index.js +36 -0
- package/lib/rsyncExec.js +67 -0
- package/lib/util.js +6 -0
- package/package.json +4 -2
- package/.dockerignore +0 -2
- package/.github/dependabot.yml +0 -11
- package/.github/workflows/coverage_report.yml +0 -24
- package/.github/workflows/release.yml +0 -29
- package/.github/workflows/run_test.yml +0 -40
- package/.husky/_pre-commit +0 -1
- package/.husky/pre-commit +0 -1
- package/.releaserc.json +0 -16
- package/CHANGELOG.md +0 -27
- package/Dockerfile +0 -7
- package/Dockerfile-bullseye +0 -7
- package/Dockerfile-buster +0 -7
- package/compose.yml +0 -36
- package/eslint.config.mjs +0 -181
- package/run_test_all.sh +0 -18
- package/run_test_local.sh +0 -13
- package/test/getSshOption.js +0 -49
- package/test/interface.js +0 -182
- package/test/rsyncExec.js +0 -406
- package/test/sanityCheck.js +0 -106
- package/test/sshExec.js +0 -145
- package/test/testUtil/hostInfo.js +0 -9
- package/test/testUtil/testFiles.js +0 -101
- package/test/testUtil/testrc +0 -1
package/test/sanityCheck.js
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
process.on("unhandledRejection", console.dir);
|
|
2
|
-
|
|
3
|
-
//setup test framework
|
|
4
|
-
import * as chai from "chai";
|
|
5
|
-
import { expect } from "chai";
|
|
6
|
-
import chaiAsPromised from "chai-as-promised";
|
|
7
|
-
chai.use(chaiAsPromised);
|
|
8
|
-
|
|
9
|
-
//testee
|
|
10
|
-
import { sanityCheck } from "../lib/util.js";
|
|
11
|
-
|
|
12
|
-
describe("test for sanityCheck", ()=>{
|
|
13
|
-
const host = "testHostName";
|
|
14
|
-
const defaultValues = {
|
|
15
|
-
ControlPersist: 180,
|
|
16
|
-
maxRetry: 3,
|
|
17
|
-
retryDuration: 1000
|
|
18
|
-
};
|
|
19
|
-
it("should throw error for empty host", ()=>{
|
|
20
|
-
expect(sanityCheck.bind(null, { host: " ", user: " ", port: 22 })).to.throw(/empty host is not allowed/);
|
|
21
|
-
});
|
|
22
|
-
it("should throw error if host is not in argument object", ()=>{
|
|
23
|
-
expect(sanityCheck.bind(null, { user: " ", port: 22 })).to.throw(/host is required/);
|
|
24
|
-
});
|
|
25
|
-
it("should just remove empty string props", ()=>{
|
|
26
|
-
expect(sanityCheck({
|
|
27
|
-
host,
|
|
28
|
-
user: " ",
|
|
29
|
-
port: 22
|
|
30
|
-
})).to.deep.equal({
|
|
31
|
-
host,
|
|
32
|
-
...defaultValues,
|
|
33
|
-
port: 22
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
it("should remove empty string member in sshOpt", ()=>{
|
|
37
|
-
expect(sanityCheck({
|
|
38
|
-
host,
|
|
39
|
-
sshOpt: ["foo", " ", "bar"],
|
|
40
|
-
user: "user",
|
|
41
|
-
port: 33
|
|
42
|
-
})).to.deep.equal({
|
|
43
|
-
host,
|
|
44
|
-
...defaultValues,
|
|
45
|
-
sshOpt: ["foo", "bar"],
|
|
46
|
-
user: "user",
|
|
47
|
-
port: 33
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
it("should just change type if string value specified for number", ()=>{
|
|
51
|
-
expect(sanityCheck({
|
|
52
|
-
host,
|
|
53
|
-
port: "11",
|
|
54
|
-
ControlPersist: "22",
|
|
55
|
-
ConnectTimeout: "33",
|
|
56
|
-
maxRetry: "44",
|
|
57
|
-
retryDuration: "55"
|
|
58
|
-
})).to.deep.equal({
|
|
59
|
-
host,
|
|
60
|
-
port: 11,
|
|
61
|
-
ControlPersist: 22,
|
|
62
|
-
ConnectTimeout: 33,
|
|
63
|
-
maxRetry: 44,
|
|
64
|
-
retryDuration: 55
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
it("should remove out of range members", ()=>{
|
|
68
|
-
expect(sanityCheck({
|
|
69
|
-
host,
|
|
70
|
-
ControlPersist: "-1",
|
|
71
|
-
ConnectTimeout: "-1",
|
|
72
|
-
maxRetry: "-1",
|
|
73
|
-
retryDuration: "-1"
|
|
74
|
-
})).to.deep.equal({
|
|
75
|
-
host
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
it("should just change type if string value specified for boolean member", ()=>{
|
|
79
|
-
expect(sanityCheck({
|
|
80
|
-
host,
|
|
81
|
-
noStrictHostKeyChecking: "true"
|
|
82
|
-
})).to.deep.equal({
|
|
83
|
-
host,
|
|
84
|
-
...defaultValues,
|
|
85
|
-
noStrictHostKeyChecking: true
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
it("should just change type if number value specified for boolean member", ()=>{
|
|
89
|
-
expect(sanityCheck({
|
|
90
|
-
host,
|
|
91
|
-
noStrictHostKeyChecking: 0
|
|
92
|
-
})).to.deep.equal({
|
|
93
|
-
host,
|
|
94
|
-
...defaultValues,
|
|
95
|
-
noStrictHostKeyChecking: false
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
it("shold keep addtional properties", ()=>{
|
|
99
|
-
const testData = {
|
|
100
|
-
host,
|
|
101
|
-
hoge: 3,
|
|
102
|
-
foo: ()=>{}
|
|
103
|
-
};
|
|
104
|
-
expect(sanityCheck(testData)).to.deep.equal(testData);
|
|
105
|
-
});
|
|
106
|
-
});
|
package/test/sshExec.js
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import path from "path";
|
|
2
|
-
import { fileURLToPath } from "url";
|
|
3
|
-
|
|
4
|
-
process.on("unhandledRejection", console.dir);
|
|
5
|
-
Error.traceLimit = 100000;
|
|
6
|
-
|
|
7
|
-
//setup test framework
|
|
8
|
-
import * as chai from "chai";
|
|
9
|
-
import { expect } from "chai";
|
|
10
|
-
import sinon from "sinon";
|
|
11
|
-
import sinonChai from "sinon-chai";
|
|
12
|
-
import chaiAsPromised from "chai-as-promised";
|
|
13
|
-
chai.use(sinonChai);
|
|
14
|
-
chai.use(chaiAsPromised);
|
|
15
|
-
|
|
16
|
-
const sshout = sinon.stub();
|
|
17
|
-
|
|
18
|
-
//testee
|
|
19
|
-
import { sshExec, canConnect, disconnect } from "../lib/sshExec.js";
|
|
20
|
-
|
|
21
|
-
//test helpers
|
|
22
|
-
import hostInfo from "./testUtil/hostInfo.js";
|
|
23
|
-
import { clearRemoteTestFiles, clearLocalTestFiles, nonExisting } from "./testUtil/testFiles.js";
|
|
24
|
-
import { send } from "../lib/rsyncExec.js";
|
|
25
|
-
|
|
26
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
27
|
-
|
|
28
|
-
describe("test for ssh execution", function () {
|
|
29
|
-
const rcfilePath = "/home/testuser/testrc";
|
|
30
|
-
this.timeout(65000);
|
|
31
|
-
beforeEach(async ()=>{
|
|
32
|
-
sshout.reset();
|
|
33
|
-
await clearRemoteTestFiles(hostInfo);
|
|
34
|
-
});
|
|
35
|
-
before(async ()=>{
|
|
36
|
-
await send(hostInfo, [path.resolve(__dirname, "./testUtil/testrc")], rcfilePath, [], 0);
|
|
37
|
-
});
|
|
38
|
-
after(async ()=>{
|
|
39
|
-
if (!process.env.TEST_KEEP_FILES) {
|
|
40
|
-
await clearRemoteTestFiles(hostInfo);
|
|
41
|
-
await clearLocalTestFiles();
|
|
42
|
-
await sshExec(hostInfo, `rm ${rcfilePath}`);
|
|
43
|
-
}
|
|
44
|
-
await disconnect(hostInfo);
|
|
45
|
-
});
|
|
46
|
-
describe("#exec", ()=>{
|
|
47
|
-
const testText = "hoge";
|
|
48
|
-
it("should execute single command with stdout", async ()=>{
|
|
49
|
-
const rt = await sshExec(hostInfo, `echo ${testText}`, 0, sshout);
|
|
50
|
-
expect(rt).to.equal(0);
|
|
51
|
-
expect(sshout).to.be.called;
|
|
52
|
-
|
|
53
|
-
expect(sshout).to.be.calledWithMatch(/^hoge/);
|
|
54
|
-
});
|
|
55
|
-
it("should execute single command with stderr", async ()=>{
|
|
56
|
-
const rt = await sshExec(hostInfo, `echo ${testText} >&2`, 0, sshout);
|
|
57
|
-
expect(rt).to.equal(0);
|
|
58
|
-
expect(sshout).to.be.called;
|
|
59
|
-
expect(sshout).to.be.calledWithMatch(/^hoge/);
|
|
60
|
-
});
|
|
61
|
-
it("should execute 2 command by prependCmd ", async ()=>{
|
|
62
|
-
const rt = await sshExec(hostInfo, `echo ${testText}`, 0, sshout, null, "echo foo");
|
|
63
|
-
expect(rt).to.equal(0);
|
|
64
|
-
expect(sshout).to.be.calledOnce;
|
|
65
|
-
expect(sshout).to.be.calledWithMatch(/^foo\nhoge\n/);
|
|
66
|
-
});
|
|
67
|
-
it("should execute 2 command by hostInfo.prependCmd ", async ()=>{
|
|
68
|
-
hostInfo.prependCmd = "echo foo";
|
|
69
|
-
const rt = await sshExec(hostInfo, `echo ${testText}`, 0, sshout, null);
|
|
70
|
-
expect(rt).to.equal(0);
|
|
71
|
-
expect(sshout).to.be.calledOnce;
|
|
72
|
-
expect(sshout).to.be.calledWithMatch(/^foo\nhoge\n/);
|
|
73
|
-
delete hostInfo.prependCmd;
|
|
74
|
-
});
|
|
75
|
-
it("should execute 2 command by rcfile", async ()=>{
|
|
76
|
-
const rt = await sshExec(hostInfo, `echo ${testText}`, 0, sshout, rcfilePath);
|
|
77
|
-
expect(rt).to.equal(0);
|
|
78
|
-
expect(sshout).to.be.calledOnce;
|
|
79
|
-
expect(sshout).to.be.calledWithMatch(/^this is test rcfile\nhoge\n/);
|
|
80
|
-
});
|
|
81
|
-
it("should execute 2 command by hostInfo.prependCmd ", async ()=>{
|
|
82
|
-
hostInfo.rcfile = rcfilePath;
|
|
83
|
-
const rt = await sshExec(hostInfo, `echo ${testText}`, 0, sshout, null);
|
|
84
|
-
expect(rt).to.equal(0);
|
|
85
|
-
expect(sshout).to.be.calledOnce;
|
|
86
|
-
expect(sshout).to.be.calledWithMatch(/^this is test rcfile\nhoge\n/);
|
|
87
|
-
delete hostInfo.rcfile;
|
|
88
|
-
});
|
|
89
|
-
//please note that exec() resolves with non-zero value
|
|
90
|
-
//(126 permission deny or 127 file not found)
|
|
91
|
-
//but does not reject in following 2 cases
|
|
92
|
-
it("should not execute command which do not have exec permission", async ()=>{
|
|
93
|
-
await sshExec(hostInfo, "echo echo hoge >hoge");
|
|
94
|
-
await sshExec(hostInfo, "chmod ugo-x hoge");
|
|
95
|
-
const rt = await sshExec(hostInfo, "./hoge");
|
|
96
|
-
expect(rt).to.equal(126);
|
|
97
|
-
});
|
|
98
|
-
it("should resolve with 127 if command is not found", async ()=>{
|
|
99
|
-
const rt = await sshExec(hostInfo, `./${nonExisting}`);
|
|
100
|
-
expect(rt).to.equal(127);
|
|
101
|
-
});
|
|
102
|
-
it("should be rejected if timeout expired", async ()=>{
|
|
103
|
-
return expect(sshExec(hostInfo, "sleep 60", 1)).to.be.rejectedWith("watchdog timer expired");
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
describe("#canConnect", ()=>{
|
|
107
|
-
let hostInfo2;
|
|
108
|
-
beforeEach(async ()=>{
|
|
109
|
-
await disconnect(hostInfo);
|
|
110
|
-
hostInfo2 = { ...hostInfo };
|
|
111
|
-
hostInfo2.masterPty = null;
|
|
112
|
-
});
|
|
113
|
-
afterEach(async ()=>{
|
|
114
|
-
await disconnect(hostInfo2);
|
|
115
|
-
});
|
|
116
|
-
it("should be resolved with true", async ()=>{
|
|
117
|
-
return expect(await canConnect(hostInfo2, 5)).to.be.true;
|
|
118
|
-
});
|
|
119
|
-
it("should be rejected if user does not exist", async ()=>{
|
|
120
|
-
hostInfo2.user = "xxxx";
|
|
121
|
-
return expect(canConnect(hostInfo2, 2)).to.be.rejected;
|
|
122
|
-
});
|
|
123
|
-
it("should be rejected if password is wrong", async ()=>{
|
|
124
|
-
hostInfo2.password = "xxxx";
|
|
125
|
-
return expect(canConnect(hostInfo2, 2)).to.be.rejected;
|
|
126
|
-
});
|
|
127
|
-
it("should be rejected if host does not exist", async ()=>{
|
|
128
|
-
hostInfo2.host = "foo.bar.example.com";
|
|
129
|
-
return expect(canConnect(hostInfo2, 2)).to.be.rejectedWith(255);
|
|
130
|
-
});
|
|
131
|
-
it("should be rejected if host(ip address) does not exist", async ()=>{
|
|
132
|
-
hostInfo2.host = "192.0.2.1";
|
|
133
|
-
hostInfo2.ConnectTimeout = 8; //please note each test will be timed out in 20 seconds
|
|
134
|
-
return expect(canConnect(hostInfo2, 2)).to.be.rejectedWith(255);
|
|
135
|
-
});
|
|
136
|
-
it("should be rejected if port number is out of range(-1)", async ()=>{
|
|
137
|
-
hostInfo2.port = -1;
|
|
138
|
-
return expect(canConnect(hostInfo2, 2)).to.be.rejectedWith("invalid port specified -1");
|
|
139
|
-
});
|
|
140
|
-
it("should be rejected if port number is out of range(65536)", async ()=>{
|
|
141
|
-
hostInfo2.port = 65536;
|
|
142
|
-
return expect(canConnect(hostInfo2, 2)).to.be.rejectedWith("invalid port specified 65536");
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
});
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export default {
|
|
2
|
-
host: process.env.TEST_HOST || "localhost",
|
|
3
|
-
user: process.env.TEST_USER || null,
|
|
4
|
-
port: process.env.TEST_PORT || 22,
|
|
5
|
-
password: process.env.TEST_PW,
|
|
6
|
-
passphrase: process.env.TEST_PH,
|
|
7
|
-
keyFile: process.env.TEST_PRIVATE_KEY,
|
|
8
|
-
noStrictHostkeyChecking: true
|
|
9
|
-
};
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import fs from "fs-extra";
|
|
2
|
-
import path from "path";
|
|
3
|
-
|
|
4
|
-
import { sshExec } from "../../lib/sshExec.js";
|
|
5
|
-
|
|
6
|
-
/*
|
|
7
|
-
* test directory tree
|
|
8
|
-
* ${ROOT}
|
|
9
|
-
* +-- huga/ (empty directory)
|
|
10
|
-
* +-- foo
|
|
11
|
-
* +-- bar
|
|
12
|
-
* +-- baz
|
|
13
|
-
* +-- hoge
|
|
14
|
-
* +-- piyo
|
|
15
|
-
* +-- puyo
|
|
16
|
-
* +-- poyo
|
|
17
|
-
*
|
|
18
|
-
* ${ROOT} is "ARssh_testLocalDir" on local side
|
|
19
|
-
* it is ARssh_testLocalDir on remote side
|
|
20
|
-
*
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
const localRoot = "ARssh_testLocalDir";
|
|
24
|
-
const localEmptyDir = path.join(localRoot, "huga");
|
|
25
|
-
const localFiles = [
|
|
26
|
-
path.join(localRoot, "foo"),
|
|
27
|
-
path.join(localRoot, "bar"),
|
|
28
|
-
path.join(localRoot, "baz"),
|
|
29
|
-
path.join(localRoot, "hoge", "piyo"),
|
|
30
|
-
path.join(localRoot, "hoge", "puyo"),
|
|
31
|
-
path.join(localRoot, "hoge", "poyo")
|
|
32
|
-
];
|
|
33
|
-
|
|
34
|
-
const remoteRoot = "ARssh_testRemoteDir";
|
|
35
|
-
const remoteEmptyDir = `${remoteRoot}/huga`;
|
|
36
|
-
const remoteFiles = [
|
|
37
|
-
`${remoteRoot}/foo`,
|
|
38
|
-
`${remoteRoot}/bar`,
|
|
39
|
-
`${remoteRoot}/baz`,
|
|
40
|
-
`${remoteRoot}/hoge/piyo`,
|
|
41
|
-
`${remoteRoot}/hoge/puyo`,
|
|
42
|
-
`${remoteRoot}/hoge/poyo`
|
|
43
|
-
];
|
|
44
|
-
const nonExisting = "ARSSH_nonExisting";
|
|
45
|
-
|
|
46
|
-
/*
|
|
47
|
-
* prepare local files which contain its filename
|
|
48
|
-
*/
|
|
49
|
-
/**
|
|
50
|
-
*
|
|
51
|
-
*/
|
|
52
|
-
export async function createLocalFiles() {
|
|
53
|
-
const localDir2 = path.join(localRoot, "hoge");
|
|
54
|
-
const promises = [];
|
|
55
|
-
await fs.mkdir(localRoot);
|
|
56
|
-
await fs.mkdir(localDir2);
|
|
57
|
-
promises.push(fs.mkdir(localEmptyDir));
|
|
58
|
-
localFiles.forEach((localFile)=>{
|
|
59
|
-
promises.push(fs.writeFile(localFile, `${localFile}\n`));
|
|
60
|
-
});
|
|
61
|
-
return Promise.all(promises);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
*
|
|
66
|
-
*/
|
|
67
|
-
export async function clearLocalTestFiles() {
|
|
68
|
-
return fs.remove(localRoot);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
*
|
|
73
|
-
* @param hostInfo
|
|
74
|
-
*/
|
|
75
|
-
export async function createRemoteFiles(hostInfo) {
|
|
76
|
-
//create remote files
|
|
77
|
-
await sshExec(hostInfo, `mkdir -p ${remoteRoot}/hoge`);
|
|
78
|
-
await sshExec(hostInfo, `mkdir -p ${remoteEmptyDir}`);
|
|
79
|
-
const script = remoteFiles.reduce((a, remoteFile)=>{
|
|
80
|
-
return `${a}echo ${remoteFile} > ${remoteFile};`;
|
|
81
|
-
}, "");
|
|
82
|
-
return sshExec(hostInfo, `${script}`);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
*
|
|
87
|
-
* @param hostInfo
|
|
88
|
-
*/
|
|
89
|
-
export async function clearRemoteTestFiles(hostInfo) {
|
|
90
|
-
return sshExec(hostInfo, `rm -fr ${remoteRoot}`);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export {
|
|
94
|
-
localRoot,
|
|
95
|
-
localEmptyDir,
|
|
96
|
-
localFiles,
|
|
97
|
-
nonExisting,
|
|
98
|
-
remoteRoot,
|
|
99
|
-
remoteEmptyDir,
|
|
100
|
-
remoteFiles
|
|
101
|
-
};
|
package/test/testUtil/testrc
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
echo this is test rcfile
|