Initial Commit
This commit is contained in:
160
app/classes/metal/Host.js
Normal file
160
app/classes/metal/Host.js
Normal file
@@ -0,0 +1,160 @@
|
||||
const { Injectable } = require('flitter-di')
|
||||
const ImplementationError = require('libflitter/errors/ImplementationError')
|
||||
const uuid = require('uuid/v4')
|
||||
const UniversalPath = require('../logical/UniversalPath')
|
||||
const SystemMetrics = require('../logical/SystemMetrics')
|
||||
|
||||
const DNFManager = require('../logical/packages/DNFManager')
|
||||
const APTManager = require('../logical/packages/APTManager')
|
||||
|
||||
const SystemDManager = require('../logical/services/SystemDManager')
|
||||
|
||||
class Host extends Injectable {
|
||||
static get services() {
|
||||
return [...super.services, 'utility']
|
||||
}
|
||||
|
||||
_temp_path_command = 'mktemp -d'
|
||||
_temp_file_command = 'mktemp'
|
||||
_cpu_percentage_command = `grep 'cpu ' /proc/stat | awk '{usage=($2+$4)*100/($2+$4+$5)} END {print usage}'`
|
||||
_ram_percentage_command = `free | grep Mem | awk '{print $3/$2 * 100.0}'`
|
||||
_mount_point_percentage_command = `df -hl | grep -w '%%MOUNTPOINT%%$' | awk '{print $5}'`
|
||||
_list_mount_points_command = `df -hl | grep '/' | awk '{print $6}'`
|
||||
_file_directory_delete_command = `rm -rf "%%RESOURCE%%"`
|
||||
_resolve_path_command = `readlink -f "%%PATH%%"`
|
||||
_reboot_command = `reboot`
|
||||
|
||||
constructor(config) {
|
||||
super()
|
||||
this.config = config
|
||||
this.name = config.name
|
||||
|
||||
if ( config.packages && config.packages.type ) {
|
||||
if ( config.packages.type === 'dnf' ) {
|
||||
this.packages = new DNFManager(this)
|
||||
} else if ( config.packages.type === 'apt' ) {
|
||||
this.packages = new APTManager(this)
|
||||
} else {
|
||||
throw new Error(`Invalid or unknown package manager type: ${config.packages.type}`)
|
||||
}
|
||||
}
|
||||
|
||||
if ( config.services && config.services.type ) {
|
||||
if ( config.services.type === 'systemd' ) {
|
||||
this.services = new SystemDManager(this)
|
||||
} else {
|
||||
throw new Error(`Invalid or unknown service manager type: ${config.services.type}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async execute(command) {
|
||||
throw new ImplementationError()
|
||||
}
|
||||
|
||||
async open_file_read_stream(file_path) {
|
||||
throw new ImplementationError()
|
||||
}
|
||||
|
||||
async open_file_write_stream(file_path) {
|
||||
throw new ImplementationError()
|
||||
}
|
||||
|
||||
async is_alive() {
|
||||
try {
|
||||
const unique_id = uuid()
|
||||
const result = await this.execute(`echo "${unique_id}"`)
|
||||
return (result.exit_code === 0 && (result.clean_out.length > 0 && result.clean_out[0] === unique_id))
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async get_temp_path() {
|
||||
const path_string = await this.run_line_result(this._temp_path_command)
|
||||
return new UniversalPath(this, path_string, UniversalPath.PATH_TYPE_DIRECTORY)
|
||||
}
|
||||
|
||||
async get_temp_file() {
|
||||
const file_string = await this.run_line_result(this._temp_file_command)
|
||||
return new UniversalPath(this, file_string, UniversalPath.PATH_TYPE_FILE)
|
||||
}
|
||||
|
||||
async get_path(local_path) {
|
||||
const host_path = new UniversalPath(this, local_path)
|
||||
await host_path.classify()
|
||||
return host_path
|
||||
}
|
||||
|
||||
async metrics() {
|
||||
const metric = new SystemMetrics()
|
||||
const cpu_percent = Number(await this.run_line_result(this._cpu_percentage_command))
|
||||
const ram_percent = Number(await this.run_line_result(this._ram_percentage_command))
|
||||
metric.cpu(cpu_percent)
|
||||
metric.ram(ram_percent)
|
||||
|
||||
const mount_points = await this.get_mount_points()
|
||||
for ( const point of mount_points ) {
|
||||
metric.mount(point, await this.get_mountpoint_utilization(point))
|
||||
}
|
||||
|
||||
return metric
|
||||
}
|
||||
|
||||
async delete_path(resource_path) {
|
||||
resource_path = typeof resource_path === 'string' ? resource_path : resource_path.path
|
||||
await this.execute(this._file_directory_delete_command.replace('%%RESOURCE%%', resource_path))
|
||||
}
|
||||
|
||||
async resolve_path(resource_path) {
|
||||
resource_path = typeof resource_path === 'string' ? resource_path : resource_path.path
|
||||
return this.run_line_result(this._resolve_path_command.replace('%%PATH%%', resource_path))
|
||||
}
|
||||
|
||||
async get_mount_points() {
|
||||
const result = await this.execute(this._list_mount_points_command)
|
||||
if ( result.exit_code !== 0 ) {
|
||||
throw new Error('Unable to determine mount points. Command execution error.')
|
||||
}
|
||||
|
||||
return result.clean_out
|
||||
}
|
||||
|
||||
async get_mountpoint_utilization(mountpoint) {
|
||||
const cmd = this._mount_point_percentage_command.replace('%%MOUNTPOINT%%', mountpoint)
|
||||
const result = await this.execute(cmd)
|
||||
if ( result.exit_code !== 0 ) {
|
||||
throw new Error('Unable to determine mount utilization. Command execution error.')
|
||||
}
|
||||
|
||||
return Number(result.clean_out[0].replace('%', ''))/100
|
||||
}
|
||||
|
||||
async run_line_result(command) {
|
||||
const result = await this.execute(command)
|
||||
if ( result.exit_code !== 0 || result.clean_out.length < 1 ) {
|
||||
throw new Error('Unable to get line output from command: '+command)
|
||||
}
|
||||
return this.utility.infer(result.clean_out[0].trim())
|
||||
}
|
||||
|
||||
async run(command) {
|
||||
const result = await this.execute(command)
|
||||
if ( result.exit_code !== 0 ) {
|
||||
throw new Error('Unable to run command: '+command)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
async list_files_in_directory(local_path) {
|
||||
throw new ImplementationError()
|
||||
}
|
||||
|
||||
async _cleanup() {}
|
||||
|
||||
async reboot() {
|
||||
await this.run_line_result(this._reboot_command)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = exports = Host
|
||||
30
app/classes/metal/LocalHost.js
Normal file
30
app/classes/metal/LocalHost.js
Normal file
@@ -0,0 +1,30 @@
|
||||
const Host = require('./Host')
|
||||
const child_process = require('child_process')
|
||||
const ExecutionResult = require('../logical/ExecutionResult')
|
||||
const fs = require('fs')
|
||||
|
||||
class LocalHost extends Host {
|
||||
async execute(command) {
|
||||
const result = new ExecutionResult()
|
||||
return new Promise((resolve) => {
|
||||
child_process.exec(command, (error, stdout, stderr) => {
|
||||
result.exit(error ? error.code : 0)
|
||||
result.out(stdout)
|
||||
result.error(stderr)
|
||||
resolve(result)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async open_file_read_stream(file_path) {
|
||||
file_path = typeof file_path === 'string' ? file_path : file_path.path
|
||||
return fs.createReadStream(file_path)
|
||||
}
|
||||
|
||||
async open_file_write_stream(file_path) {
|
||||
file_path = typeof file_path === 'string' ? file_path : file_path.path
|
||||
return fs.createWriteStream(file_path)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = exports = LocalHost
|
||||
77
app/classes/metal/RemoteHost.js
Normal file
77
app/classes/metal/RemoteHost.js
Normal file
@@ -0,0 +1,77 @@
|
||||
const Host = require('./Host')
|
||||
const ssh = require('ssh2')
|
||||
const ExecutionResult = require('../logical/ExecutionResult')
|
||||
const fs = require('fs').promises
|
||||
|
||||
class RemoteHost extends Host {
|
||||
_ssh = false
|
||||
|
||||
async execute(command) {
|
||||
const conn = await this._get_ssh_connection()
|
||||
const result = new ExecutionResult()
|
||||
await new Promise((resolve, reject) => {
|
||||
conn.exec(command, (error, stream) => {
|
||||
if ( error ) reject(error)
|
||||
else {
|
||||
stream.on('close', (code, signal) => {
|
||||
result.exit(code)
|
||||
resolve()
|
||||
}).on('data', (data) => {
|
||||
result.out(data)
|
||||
}).stderr.on('data', (data) => {
|
||||
result.error(data)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
async open_file_read_stream(file_path) {
|
||||
file_path = typeof file_path === 'string' ? file_path : file_path.path
|
||||
const sftp = await this._get_sftp_connection()
|
||||
return sftp.createReadStream(file_path)
|
||||
}
|
||||
|
||||
async open_file_write_stream(file_path) {
|
||||
file_path = typeof file_path === 'string' ? file_path : file_path.path
|
||||
const sftp = await this._get_sftp_connection()
|
||||
return sftp.createWriteStream(file_path)
|
||||
}
|
||||
|
||||
async _get_ssh_connection() {
|
||||
if ( this._ssh ) return this._ssh
|
||||
|
||||
if ( this.config.ssh_params && this.config.ssh_params.key_file && !this.config.ssh_params.privateKey ) {
|
||||
this.config.ssh_params.privateKey = await fs.readFile(this.config.ssh_params.key_file)
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const client = new ssh.Client()
|
||||
client.on('ready', () => {
|
||||
this._ssh = client
|
||||
resolve(client)
|
||||
}).connect(this.config.ssh_params)
|
||||
client.on('error', reject)
|
||||
})
|
||||
}
|
||||
|
||||
async _get_sftp_connection() {
|
||||
const ssh = await this._get_ssh_connection()
|
||||
return new Promise((resolve, reject) => {
|
||||
ssh.sftp((err, sftp) => {
|
||||
if ( err ) reject(err)
|
||||
else resolve(sftp)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async _cleanup() {
|
||||
if ( this._ssh ) {
|
||||
this._ssh.end()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = exports = RemoteHost
|
||||
Reference in New Issue
Block a user