mirror of
https://github.com/democratic-csi/democratic-csi.git
synced 2026-10-06 14:31:48 +02:00
249 lines
6.7 KiB
JavaScript
249 lines
6.7 KiB
JavaScript
const Client = require("ssh2").Client;
|
|
const { E_CANCELED, Mutex } = require("async-mutex");
|
|
const GeneralUtils = require("./general");
|
|
|
|
class SshClient {
|
|
constructor(options = {}) {
|
|
const client = this;
|
|
this.options = options;
|
|
this.options.connection = this.options.connection || {};
|
|
if (this.options.logger) {
|
|
this.logger = this.options.logger;
|
|
} else {
|
|
this.logger = console;
|
|
console.silly = console.debug;
|
|
}
|
|
|
|
if (!this.options.connection.hasOwnProperty("keepaliveInterval")) {
|
|
this.options.connection.keepaliveInterval = 10000;
|
|
}
|
|
|
|
if (this.options.connection.debug === true) {
|
|
this.options.connection.debug = function (msg) {
|
|
client.debug(msg);
|
|
};
|
|
}
|
|
|
|
this.conn_mutex = new Mutex();
|
|
this.conn_state;
|
|
this.conn_err;
|
|
this.ready_event_count = 0;
|
|
this.error_event_count = 0;
|
|
|
|
this.conn = new Client();
|
|
// invoked before close
|
|
this.conn.on("end", () => {
|
|
this.conn_state = "ended";
|
|
this.debug("Client :: end");
|
|
});
|
|
// invoked after end
|
|
this.conn.on("close", () => {
|
|
this.conn_state = "closed";
|
|
this.debug("Client :: close");
|
|
});
|
|
this.conn.on("error", (err) => {
|
|
this.conn_state = "error";
|
|
this.conn_err = err;
|
|
this.error_event_count++;
|
|
this.debug("Client :: error");
|
|
});
|
|
this.conn.on("ready", () => {
|
|
this.conn_state = "ready";
|
|
this.ready_event_count++;
|
|
this.debug("Client :: ready");
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Build a command line from the name and given args
|
|
* TODO: escape the arguments
|
|
*
|
|
* @param {*} name
|
|
* @param {*} args
|
|
*/
|
|
buildCommand(name, args = []) {
|
|
args.unshift(name);
|
|
return args.join(" ");
|
|
}
|
|
|
|
debug() {
|
|
this.logger.silly(...arguments);
|
|
}
|
|
|
|
async _connect() {
|
|
const start_ready_event_count = this.ready_event_count;
|
|
const start_error_event_count = this.error_event_count;
|
|
try {
|
|
await this.conn_mutex.runExclusive(async () => {
|
|
this.conn.connect(this.options.connection);
|
|
do {
|
|
if (start_error_event_count != this.error_event_count) {
|
|
throw this.conn_err;
|
|
}
|
|
|
|
if (start_ready_event_count != this.ready_event_count) {
|
|
break;
|
|
}
|
|
|
|
await GeneralUtils.sleep(100);
|
|
} while (true);
|
|
});
|
|
} catch (err) {
|
|
if (err === E_CANCELED) {
|
|
return;
|
|
}
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
async connect() {
|
|
if (this.conn_state == "ready") {
|
|
return;
|
|
}
|
|
|
|
return this._connect();
|
|
}
|
|
|
|
async exec(command, options = {}, stream_proxy = null) {
|
|
// default is to reuse
|
|
if (process.env.SSH_REUSE_CONNECTION == "0") {
|
|
return this._nexec(...arguments);
|
|
} else {
|
|
return this._rexec(...arguments);
|
|
}
|
|
}
|
|
|
|
async _rexec(command, options = {}, stream_proxy = null) {
|
|
const client = this;
|
|
const conn = this.conn;
|
|
|
|
return new Promise(async (resolve, reject) => {
|
|
do {
|
|
try {
|
|
await this.connect();
|
|
conn.exec(command, options, function (err, stream) {
|
|
if (err) {
|
|
reject(err);
|
|
return;
|
|
}
|
|
let stderr;
|
|
let stdout;
|
|
|
|
if (stream_proxy) {
|
|
stream_proxy.on("kill", (signal) => {
|
|
stream.destroy();
|
|
});
|
|
}
|
|
|
|
stream
|
|
.on("close", function (code, signal) {
|
|
client.debug(
|
|
"Stream :: close :: code: " + code + ", signal: " + signal
|
|
);
|
|
if (stream_proxy) {
|
|
stream_proxy.emit("close", ...arguments);
|
|
}
|
|
resolve({ stderr, stdout, code, signal });
|
|
//conn.end();
|
|
})
|
|
.on("data", function (data) {
|
|
client.debug("STDOUT: " + data);
|
|
if (stream_proxy) {
|
|
stream_proxy.stdout.emit("data", ...arguments);
|
|
}
|
|
if (stdout == undefined) {
|
|
stdout = "";
|
|
}
|
|
stdout = stdout.concat(data);
|
|
})
|
|
.stderr.on("data", function (data) {
|
|
client.debug("STDERR: " + data);
|
|
if (stream_proxy) {
|
|
stream_proxy.stderr.emit("data", ...arguments);
|
|
}
|
|
if (stderr == undefined) {
|
|
stderr = "";
|
|
}
|
|
stderr = stderr.concat(data);
|
|
});
|
|
});
|
|
break;
|
|
} catch (err) {
|
|
if (err.message && !err.message.includes("Not connected")) {
|
|
throw err;
|
|
}
|
|
}
|
|
await GeneralUtils.sleep(1000);
|
|
} while (true);
|
|
});
|
|
}
|
|
|
|
async _nexec(command, options = {}, stream_proxy = null) {
|
|
const client = this;
|
|
return new Promise((resolve, reject) => {
|
|
var conn = new Client();
|
|
|
|
conn
|
|
.on("error", function (err) {
|
|
client.debug("Client :: error");
|
|
reject(err);
|
|
})
|
|
.on("ready", function () {
|
|
client.debug("Client :: ready");
|
|
//options.pty = true;
|
|
//options.env = {
|
|
// TERM: "",
|
|
//};
|
|
conn.exec(command, options, function (err, stream) {
|
|
if (err) {
|
|
reject(err);
|
|
return;
|
|
}
|
|
let stderr;
|
|
let stdout;
|
|
stream
|
|
.on("close", function (code, signal) {
|
|
client.debug(
|
|
"Stream :: close :: code: " + code + ", signal: " + signal
|
|
);
|
|
if (stream_proxy) {
|
|
stream_proxy.emit("close", ...arguments);
|
|
}
|
|
resolve({ stderr, stdout, code, signal });
|
|
conn.end();
|
|
})
|
|
.on("data", function (data) {
|
|
client.debug("STDOUT: " + data);
|
|
if (stream_proxy) {
|
|
stream_proxy.stdout.emit("data", ...arguments);
|
|
}
|
|
if (stdout == undefined) {
|
|
stdout = "";
|
|
}
|
|
stdout = stdout.concat(data);
|
|
})
|
|
.stderr.on("data", function (data) {
|
|
client.debug("STDERR: " + data);
|
|
if (stream_proxy) {
|
|
stream_proxy.stderr.emit("data", ...arguments);
|
|
}
|
|
if (stderr == undefined) {
|
|
stderr = "";
|
|
}
|
|
stderr = stderr.concat(data);
|
|
});
|
|
});
|
|
})
|
|
.connect(client.options.connection);
|
|
|
|
if (stream_proxy) {
|
|
stream_proxy.on("kill", (signal) => {
|
|
conn.end();
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
module.exports.SshClient = SshClient;
|