diff --git a/app/classes/logical/UniversalPath.js b/app/classes/logical/UniversalPath.js index 1c3c08e..4cdc485 100644 --- a/app/classes/logical/UniversalPath.js +++ b/app/classes/logical/UniversalPath.js @@ -63,6 +63,16 @@ class UniversalPath extends Injectable { return path.dirname(this.path) } + async parent() { + await this.classify() + if ( this.is_directory() ) return path.resolve(this.path, '..') + return this.directory() + } + + name() { + return path.basename(this.path) + } + async classify() { const dir_result = await this._host.execute(`${this._directory_classify_command} ${this._path}`) if ( dir_result.exit_code === 0 ) { diff --git a/app/classes/logical/error/CommandNotFoundError.js b/app/classes/logical/error/CommandNotFoundError.js new file mode 100644 index 0000000..10ba524 --- /dev/null +++ b/app/classes/logical/error/CommandNotFoundError.js @@ -0,0 +1,5 @@ +class CommandNotFoundError extends Error { + +} + +module.exports = exports = CommandNotFoundError diff --git a/app/classes/metal/Host.js b/app/classes/metal/Host.js index 6a3ad16..f9955b6 100644 --- a/app/classes/metal/Host.js +++ b/app/classes/metal/Host.js @@ -1,6 +1,7 @@ const { Injectable } = require('flitter-di') const ImplementationError = require('libflitter/errors/ImplementationError') const PermissionDeniedError = require('../logical/error/PermissionDeniedError') +const CommandNotFoundError = require('../logical/error/CommandNotFoundError') const uuid = require('uuid/v4') const UniversalPath = require('../logical/UniversalPath') const SystemMetrics = require('../logical/SystemMetrics') @@ -213,10 +214,18 @@ class Host extends Injectable { 'permission denied' ] + const not_found_phrases = [ + 'command not found', + ] + 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 + } + return Error } } diff --git a/app/classes/state/fs/PackState.js b/app/classes/state/fs/PackState.js new file mode 100644 index 0000000..af126f4 --- /dev/null +++ b/app/classes/state/fs/PackState.js @@ -0,0 +1,60 @@ +const State = require('../State') + +class PackState extends State { + async apply() { + if ( !(await this.check()) ) { + const path = await this._path() + const destination = await this._destination() + const type = await this._get_type() + const cd_cmd = await this._host.get_directory_change_command(await path.parent()) + + if ( type === 'tar' ) { + const tar_cmd = `${cd_cmd} && tar czf "${destination.path}" "${path.name()}"` + await this._host.run(tar_cmd) + } else if ( type === 'zip' ) { + const zip_cmd = `${cd_cmd} && zip -r "${destination.path}" "${path.name()}"` + await this._host.run(zip_cmd) + } + } + } + + async check() { + const dest = await this._destination() + return dest.is_valid() + } + + async reverse() { + if ( await this.check() ) { + const destination = await this._destination() + await destination.classify() + if (destination.is_valid()) await destination.unlink() + } + } + + async _get_type() { + if ( this._config.format ) { + if ( this._config.format === 'tar' ) return 'tar' + else if ( this._config.format === 'zip' ) return 'zip' + throw new Error('Invalid unpack format: ' + this._config.type) + } else { + const dest = await this._destination() + if ( dest.path.endsWith('.tar.gz') || dest.path.endsWith('.tgz') ) return 'tar' + else if ( dest.path.endsWith('.zip') ) return 'zip' + throw new Error(`Unable to determine unpack type from destination: ${dest}`) + } + } + + async _destination() { + if ( !this._config.destination ) throw new Error('Missing destination config for PackState.') + return await this._host.get_path(this._config.destination) + } + + async _path() { + const path = await this._host.get_path(this._config.path) + await path.classify() + if ( !path.is_valid() ) throw new Error(`Invalid path for PathState: ${path}`) + return path + } +} + +module.exports = exports = PackState diff --git a/app/classes/state/fs/UnpackState.js b/app/classes/state/fs/UnpackState.js index 9ab379c..b7c00b5 100644 --- a/app/classes/state/fs/UnpackState.js +++ b/app/classes/state/fs/UnpackState.js @@ -34,10 +34,10 @@ class UnpackState extends State { } async _get_type() { - if ( this._config.type ) { - if ( this._config.type === 'tar' ) return 'tar' - else if ( this._config.type === 'zip' ) return 'zip' - throw new Error('Invalid unpack type: ' + this._config.type) + if ( this._config.format ) { + if ( this._config.format === 'tar' ) return 'tar' + else if ( this._config.format === 'zip' ) return 'zip' + throw new Error('Invalid unpack format: ' + this._config.type) } else { const path = await this._path() if ( path.path.endsWith('.tar.gz') || path.path.endsWith('.tgz') ) return 'tar' diff --git a/app/services/states.service.js b/app/services/states.service.js index 62380c0..b4c1857 100644 --- a/app/services/states.service.js +++ b/app/services/states.service.js @@ -12,15 +12,15 @@ const { Service } = require('flitter-di') class StatesService extends Service { static #state_map = { // TODO apache and nginx states - virtual host, reverse proxy - // TODO file pack state - zip, tarball // TODO package repository states - import keys, install repository - // TODO service manager states - service enabled, service installed, running, stopped + // TODO service manager states - service enabled, service installed, stopped // TODO git states - clone repo, ref checked out - // TODO package states - installed, uninstalled + // TODO package states - uninstalled 'fs.file': require('../classes/state/fs/FileState'), 'fs.directory': require('../classes/state/fs/DirectoryState'), 'fs.unpack': require('../classes/state/fs/UnpackState'), + 'fs.pack': require('../classes/state/fs/PackState'), 'fs.permission': require('../classes/state/fs/PermissionState'), 'fs.ownership': require('../classes/state/fs/OwnerState'),