maestro/app/classes/metal/Host.js
2020-08-13 20:28:23 -05:00

254 lines
9.6 KiB
JavaScript

const { Injectable } = require('flitter-di')
const ImplementationError = require('libflitter/errors/ImplementationError')
const PermissionDeniedError = require('../logical/error/PermissionDeniedError')
const CommandNotFoundError = require('../logical/error/CommandNotFoundError')
const FilesystemResourceNotFoundError = require('../logical/error/FilesystemResourceNotFoundError')
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', 'app']
}
_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`
_change_directory_command = `cd "%%PATH%%"`
_file_directory_permission_fetch_command = `stat -c %a "%%PATH%%"`
_file_directory_permission_set_command_flat = `chmod %%LEVEL%% "%%PATH%%"`
_file_directory_permission_set_command_recursive = `chmod -R %%LEVEL%% "%%PATH%%"`
_file_directory_ownership_fetch_command = `stat -c "%U:%G" "%%PATH%%"`
_file_directory_ownership_set_command_flat = `chown %%OWNERS%% "%%PATH%%"`
_file_directory_ownership_set_command_recursive = `chown -R %%OWNERS%% "%%PATH%%"`
constructor(config) {
super()
this.config = config
this.name = config.name
const injector = this.app.di()
if ( config.packages && config.packages.type ) {
if ( config.packages.type === 'dnf' ) {
this.packages = injector.make(DNFManager, this)
} else if ( config.packages.type === 'apt' ) {
this.packages = injector.make(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 = injector.make(SystemDManager, this)
} else {
throw new Error(`Invalid or unknown service manager type: ${config.services.type}`)
}
}
this.injector = injector
}
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) {
this.output.debug(e)
return false
}
}
async get_temp_path() {
const path_string = await this.run_line_result(this._temp_path_command)
return this.injector.make(UniversalPath, this, path_string, UniversalPath.PATH_TYPE_DIRECTORY)
// 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 this.injector.make(UniversalPath, this, file_string, UniversalPath.PATH_TYPE_FILE)
// return new UniversalPath(this, file_string, UniversalPath.PATH_TYPE_FILE)
}
async get_path(local_path) {
const host_path = this.injector.make(UniversalPath, this, 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))
}
get_directory_change_command(path) {
if ( typeof path !== 'string' ) path = path.path
return this._change_directory_command.replace('%%PATH%%', 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_permissions_for_path(resource_path) {
resource_path = typeof resource_path === 'string' ? resource_path : resource_path.path
return this.run_line_result(this._file_directory_permission_fetch_command.replace('%%PATH%%', resource_path))
}
async set_permissions_for_path(resource_path, level, recursive = false) {
resource_path = typeof resource_path === 'string' ? resource_path : resource_path.path
let cmd
if ( recursive ) cmd = this._file_directory_permission_set_command_recursive
else cmd = this._file_directory_permission_set_command_flat
cmd = cmd.replace('%%PATH%%', resource_path)
cmd = cmd.replace('%%LEVEL%%', level)
await this.run(cmd)
}
async get_ownership_for_path(resource_path) {
resource_path = typeof resource_path === 'string' ? resource_path : resource_path.path
const cmd = this._file_directory_ownership_fetch_command.replace('%%PATH%%', resource_path)
const result = await this.run_line_result(cmd)
const parts = result.split(':')
return {user: parts[0], group: parts[1]}
}
async set_ownership_for_path(resource_path, { user, group }, recursive = false) {
resource_path = typeof resource_path === 'string' ? resource_path : resource_path.path
let cmd
if ( recursive ) cmd = this._file_directory_ownership_set_command_recursive
else cmd = this._file_directory_ownership_set_command_flat
cmd = cmd.replace('%%PATH%%', resource_path)
cmd = cmd.replace('%%OWNERS%%', `${user}:${group}`)
await this.run(cmd)
}
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 ) {
const E = this._get_result_error_class(result)
throw new E('Unable to get line output from command: '+command+'\n'+result.clean_err)
}
return this.utility.infer(result.clean_out[0].trim())
}
async run(command) {
const result = await this.execute(command)
if ( result.exit_code !== 0 ) {
const E = this._get_result_error_class(result)
throw new E('Unable to run command: '+command+'\n'+result.clean_err)
}
return result
}
async list_files_in_directory(local_path) {
throw new ImplementationError()
}
async _cleanup() {}
async reboot() {
await this.run_line_result(this._reboot_command)
}
_get_result_error_class(result) {
const both = `${result.clean_out}\n${result.clean_err}`.toLowerCase()
const access_denied_phrases = [
'not permitted',
'access denied',
'insufficient permission',
'permission denied'
]
const not_found_phrases = [
'command not found',
]
const resource_not_found = [
'no such file',
'no such directory',
'no such file or directory',
'no such directory or file',
]
for ( const phrase of access_denied_phrases ) {
if ( both.includes(phrase) ) return PermissionDeniedError
}
for ( const phrase of not_found_phrases ) {
if ( both.includes(phrase) ) return CommandNotFoundError
}
for ( const phrase of resource_not_found ) {
if ( both.includes(phrase) ) return FilesystemResourceNotFoundError
}
return Error
}
}
module.exports = exports = Host