Initial Commit
This commit is contained in:
177
app/classes/logical/packages/APTManager.js
Normal file
177
app/classes/logical/packages/APTManager.js
Normal file
@@ -0,0 +1,177 @@
|
||||
const PackageManager = require('./PackageManager')
|
||||
|
||||
class APTManager extends PackageManager {
|
||||
_command_install_package = 'apt-get install -y %%PACKAGE%%'
|
||||
_command_uninstall_package = 'apt-get purge -y %%PACKAGE%%'
|
||||
_command_update_package = 'apt-get upgrade -y %%PACKAGE%%'
|
||||
_command_reinstall_package = 'apt-get install -y --reinstall %%PACKAGE%%'
|
||||
_command_clear_cache = 'apt-get clean'
|
||||
_command_count_installed = 'dpkg --list | wc -l'
|
||||
_command_count_available = 'apt-cache pkgnames | wc -l'
|
||||
_command_add_repo = `add-apt-repository '%%URI%%`
|
||||
_command_preflight = 'apt-get update'
|
||||
|
||||
_status_keymap = {
|
||||
package: 'name',
|
||||
filename: 'source',
|
||||
section: 'repository',
|
||||
'description-en': 'summary',
|
||||
}
|
||||
|
||||
async status(pkg) {
|
||||
await this.preflight()
|
||||
const result = await this._host.execute(`apt-cache show ${pkg}`)
|
||||
if ( ![0, 100].includes(result.exit_code) ) {
|
||||
throw new Error('Unable to determine package information: '+result.stderr.join('\n'))
|
||||
}
|
||||
|
||||
if ( result.exit_code === 100 ) {
|
||||
return {
|
||||
name: pkg,
|
||||
state: this.constructor.PACKAGE_STATE_UNKNOWN,
|
||||
}
|
||||
}
|
||||
|
||||
const data = {}
|
||||
let last_data_key = false
|
||||
for ( const line of result.clean_out ) {
|
||||
if ( last_data_key && line.startsWith(' ') ) {
|
||||
data[last_data_key] += line
|
||||
} else {
|
||||
const parts = line.trim().split(':')
|
||||
const key = parts[0].toLowerCase()
|
||||
const value = parts.slice(1).join(':').trim()
|
||||
data[key] = value
|
||||
last_data_key = key
|
||||
}
|
||||
}
|
||||
|
||||
for ( const key in this._status_keymap ) {
|
||||
if ( !this._status_keymap.hasOwnProperty(key) ) continue
|
||||
if ( data[key] ) {
|
||||
data[this._status_keymap[key]] = data[key]
|
||||
delete data[key]
|
||||
}
|
||||
}
|
||||
|
||||
// check installation state
|
||||
const install_result = await this._host.execute(`dpkg -s ${pkg}`)
|
||||
data.state = install_result.exit_code === 0 ? this.constructor.PACKAGE_STATE_INSTALLED : this.constructor.PACKAGE_STATE_AVAILABLE
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
async list_updates() {
|
||||
await this.preflight()
|
||||
const result = await this._host.execute(`apt-get --just-print upgrade`)
|
||||
if ( result.exit_code !== 0 ) {
|
||||
throw new Error('Unable to determine packages for update: '+result.stderr.join('\n'))
|
||||
}
|
||||
|
||||
let start_index = -1
|
||||
let end_index = -1
|
||||
for ( let i in result.clean_out ) {
|
||||
const line = result.clean_out[i]
|
||||
if ( line.toLowerCase().indexOf('packages will be upgraded') > -1 ) {
|
||||
start_index = Number(i)+1
|
||||
} else if ( start_index > -1 && i >= start_index ) {
|
||||
if ( line.indexOf('newly installed,') > -1 ) break
|
||||
else {
|
||||
end_index = Number(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const package_strings = result.clean_out.slice(start_index, end_index+1).join(' ').split(' ').filter(Boolean).map(x => x.trim())
|
||||
const updates = result.clean_out.filter(x => x.startsWith('Inst ')).map(x => {
|
||||
x = x.substring(5)
|
||||
x = x.split(' ')
|
||||
const data = {
|
||||
name: x[0],
|
||||
version: x[1].slice(1, -1),
|
||||
repository: x[3].replace(/,/g, ''),
|
||||
architecture: x.reverse()[0].replace(/[\[\])(]/g, '')
|
||||
}
|
||||
return data
|
||||
}).filter(x => package_strings.includes(x.name))
|
||||
return updates
|
||||
}
|
||||
|
||||
async list_repos() {
|
||||
await this.preflight()
|
||||
const result = await this._host.execute(`apt-cache policy`)
|
||||
if ( result.exit_code !== 0 ) {
|
||||
throw new Error('Unable to read repository list: '+result.stderr.join('\n'))
|
||||
}
|
||||
|
||||
const lines = []
|
||||
for ( const line of result.clean_out ) {
|
||||
if ( line.trim().startsWith('500') ) {
|
||||
lines.push(line)
|
||||
} else if ( line.toLowerCase().startsWith('pinned packages') ) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return lines.map(x => x.trim()).filter(x => x.startsWith('500')).map(x => x.split(' ')[2])
|
||||
}
|
||||
|
||||
async list_installed() {
|
||||
const result = await this._host.execute(`dpkg --list`)
|
||||
if ( result.exit_code !== 0 ) {
|
||||
throw new Error('Unable to determine installed packages: '+result.stderr.join('\n'))
|
||||
}
|
||||
|
||||
let header_line = result.clean_out.filter(x => x.trim().startsWith('+'))[0]
|
||||
let drop_count = 0
|
||||
while ( header_line.startsWith('+') ) {
|
||||
drop_count += 1
|
||||
header_line = header_line.substring(1)
|
||||
}
|
||||
|
||||
let start_at = 0
|
||||
for ( let i in result.clean_out ) {
|
||||
if ( result.clean_out[i].startsWith('+') ) {
|
||||
start_at = Number(i)+1
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return result.clean_out.slice(start_at).map(x => {
|
||||
x = x.substring(drop_count).trim().split(' ').filter(Boolean)
|
||||
return {
|
||||
name: x[0],
|
||||
version: x[1],
|
||||
architecture: x[2],
|
||||
summary: x.slice(3).join(' ')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async search(pkg) {
|
||||
await this.preflight()
|
||||
const result = await this._host.execute(`apt-cache search ${pkg}`)
|
||||
if ( result.exit_code !== 0 ) {
|
||||
throw new Error('Unable to search the package cache: '+result.stderr.join('\n'))
|
||||
}
|
||||
|
||||
const results = []
|
||||
for ( const line of result.clean_out ) {
|
||||
const parts = line.trim().split(' - ').map(x => x.trim())
|
||||
results.push({
|
||||
name: parts[0],
|
||||
summary: parts[1]
|
||||
})
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
async preflight() {
|
||||
const result = await this._host.execute(this._command_preflight)
|
||||
if ( result.exit_code !== 0 ) {
|
||||
throw new Error('Error encountered during preflight for APTManager: '+result.stderr.join('\n'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = exports = APTManager
|
||||
128
app/classes/logical/packages/DNFManager.js
Normal file
128
app/classes/logical/packages/DNFManager.js
Normal file
@@ -0,0 +1,128 @@
|
||||
const PackageManager = require('./PackageManager')
|
||||
|
||||
class DNFManager extends PackageManager {
|
||||
_command_install_package = 'dnf install -y %%PACKAGE%%'
|
||||
_command_uninstall_package = 'dnf remove -y %%PACKAGE%%'
|
||||
_command_update_package = 'dnf update -y %%PACKAGE%%'
|
||||
_command_reinstall_package = 'dnf reinstall -y %%PACKAGE%%'
|
||||
_command_add_repo = 'dnf config-manager -y --add-repo="%%URI%%"'
|
||||
_command_clear_cache = 'dnf clean all'
|
||||
_command_count_installed = 'dnf list installed -q | wc -l'
|
||||
_command_count_available = 'dnf list available -q | wc -l'
|
||||
|
||||
async status(pkg) {
|
||||
const result = await this._host.execute(`dnf info -q --installed ${pkg}`)
|
||||
if ( result.exit_code === 0 ) {
|
||||
const data = this._data_from_result(result.clean_out)
|
||||
data.state = this.constructor.PACKAGE_STATE_INSTALLED
|
||||
return data
|
||||
} else {
|
||||
const available_result = await this._host.execute(`dnf info -q --available ${pkg}`)
|
||||
if ( available_result.exit_code === 0 ) {
|
||||
const data = this._data_from_result(available_result.clean_out)
|
||||
data.state = this.constructor.PACKAGE_STATE_AVAILABLE
|
||||
return data
|
||||
} else {
|
||||
return {
|
||||
name: pkg,
|
||||
state: this.constructor.PACKAGE_STATE_UNKNOWN,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async list_updates() {
|
||||
const result = await this._host.execute(`dnf check-update -q`)
|
||||
// 100 is the known exit code for successful result, but updates pending
|
||||
if ( ![0, 100].includes(result.exit_code) ) {
|
||||
throw new Error('Unable to check for updates: '+result.stderr.join('\n'))
|
||||
}
|
||||
|
||||
const updates = []
|
||||
for ( const line of result.clean_out ) {
|
||||
const parts = line.trim().split(' ').filter(Boolean)
|
||||
const data = this._package_info_from_line_parts(parts)
|
||||
updates.push(data)
|
||||
}
|
||||
|
||||
return updates
|
||||
}
|
||||
|
||||
async list_repos() {
|
||||
const result = await this._host.execute(`dnf repolist all -q`)
|
||||
if ( result.exit_code !== 0 ) {
|
||||
throw new Error('Unable to determine repositories: '+result.stderr.join('\n'))
|
||||
}
|
||||
|
||||
const offset_length = result.clean_out[0].toLowerCase().indexOf('repo name')
|
||||
return result.clean_out.slice(1).map(line => line.substr(0, offset_length).trim())
|
||||
}
|
||||
|
||||
async list_installed() {
|
||||
const result = await this._host.execute(`dnf list installed -q`)
|
||||
if ( result.exit_code !== 0 ) {
|
||||
throw new Error('Unable to determine installed packages: '+result.stderr.join('\n'))
|
||||
}
|
||||
|
||||
const results = []
|
||||
for ( const line of result.clean_out.slice(1) ) {
|
||||
const parts = line.trim().split(' ').filter(Boolean)
|
||||
const data = this._package_info_from_line_parts(parts)
|
||||
results.push(data)
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
async search(term) {
|
||||
const result = await this._host.execute(`dnf search ${term} -q`)
|
||||
if ( result.exit_code !== 0 ) {
|
||||
throw new Error('Unable to complete search: '+result.stderr.join('\n'))
|
||||
}
|
||||
|
||||
const results = []
|
||||
for ( const line of result.clean_out ) {
|
||||
if ( !line.trim().startsWith('=') ) {
|
||||
const parts = line.split(':').map(x => x.trim())
|
||||
const data = this._package_info_from_line_parts(parts)
|
||||
if ( data.version ) {
|
||||
data.summary = data.version
|
||||
delete data.version
|
||||
}
|
||||
results.push(data)
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
_data_from_result(stdout) {
|
||||
const data = {}
|
||||
for ( const line of stdout ) {
|
||||
const parts = line.split(':').map(x => String(x).trim())
|
||||
if ( parts.length > 1 && parts[0] ) {
|
||||
let key = parts[0].toLowerCase()
|
||||
if ( key === 'from repo' ) key = 'repo'
|
||||
data[key] = parts.slice(1).join(':')
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
_package_info_from_line_parts(parts) {
|
||||
const data = {}
|
||||
if ( parts.length > 0 ) {
|
||||
data.name = parts[0]
|
||||
if ( data.name.indexOf('.') >= 0 ) {
|
||||
const arch_parts = data.name.split('.').reverse()
|
||||
data.architecture = arch_parts[0]
|
||||
data.name = arch_parts.slice(1).reverse().join('.')
|
||||
}
|
||||
}
|
||||
if ( parts.length > 1 ) data.version = parts[1]
|
||||
if ( parts.length > 2 ) data.repository = parts[2]
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = exports = DNFManager
|
||||
104
app/classes/logical/packages/PackageManager.js
Normal file
104
app/classes/logical/packages/PackageManager.js
Normal file
@@ -0,0 +1,104 @@
|
||||
const { Injectable } = require('flitter-di')
|
||||
const ImplementationError = require('libflitter/errors/ImplementationError')
|
||||
|
||||
class PackageManager extends Injectable {
|
||||
static get services() {
|
||||
return [...super.services, 'hosts']
|
||||
}
|
||||
|
||||
static PACKAGE_STATE_INSTALLED = 'installed'
|
||||
static PACKAGE_STATE_AVAILABLE = 'available'
|
||||
static PACKAGE_STATE_UNKNOWN = 'unknown'
|
||||
|
||||
_command_install_package = '%%PACKAGE%%'
|
||||
_command_uninstall_package = '%%PACKAGE%%'
|
||||
_command_update_package = '%%PACKAGE%%'
|
||||
_command_reinstall_package = '%%PACKAGE%%'
|
||||
_command_add_repo = '%%URI%%'
|
||||
_command_clear_cache = ''
|
||||
_command_count_installed = ''
|
||||
_command_count_available = ''
|
||||
|
||||
_group_packages_by = ' '
|
||||
|
||||
constructor(host_or_hostname) {
|
||||
super()
|
||||
if ( typeof host_or_hostname === 'string' ) {
|
||||
host_or_hostname = this.hosts.get(host_or_hostname)
|
||||
}
|
||||
|
||||
this._host = host_or_hostname
|
||||
}
|
||||
|
||||
async list_updates() {
|
||||
throw new ImplementationError()
|
||||
}
|
||||
|
||||
async list_repos() {
|
||||
throw new ImplementationError()
|
||||
}
|
||||
|
||||
async status(pkg) {
|
||||
throw new ImplementationError()
|
||||
}
|
||||
|
||||
async search(term) {
|
||||
throw new ImplementationError()
|
||||
}
|
||||
|
||||
async list_installed() {
|
||||
throw new ImplementationError()
|
||||
}
|
||||
|
||||
async is_installed(pkg) {
|
||||
const result = await this.status(pkg)
|
||||
return result.state === this.constructor.PACKAGE_STATE_INSTALLED
|
||||
}
|
||||
|
||||
async clear_cache() {
|
||||
await this._host.run_line_result(this._command_clear_cache)
|
||||
}
|
||||
|
||||
async count_installed() {
|
||||
return this._host.run_line_result(this._command_count_installed)
|
||||
}
|
||||
|
||||
async count_available() {
|
||||
return this._host.run_line_result(this._command_count_available)
|
||||
}
|
||||
|
||||
async install(...packages) {
|
||||
await this._standard_format_execution(packages, this._command_install_package)
|
||||
}
|
||||
|
||||
async uninstall(...packages) {
|
||||
await this._standard_format_execution(packages, this._command_uninstall_package)
|
||||
}
|
||||
|
||||
async update(...packages) {
|
||||
await this._standard_format_execution(packages, this._command_update_package)
|
||||
}
|
||||
|
||||
async reinstall(...packages) {
|
||||
await this._standard_format_execution(packages, this._command_reinstall_package)
|
||||
}
|
||||
|
||||
async add_repo(uri) {
|
||||
await this._standard_format_execution([uri], this._command_add_repo, '%%URI%%')
|
||||
}
|
||||
|
||||
async _standard_format_execution(packages, command, placeholder = '%%PACKAGE%%') {
|
||||
if ( this._group_packages_by ) {
|
||||
packages = [packages.join(this._group_packages_by)]
|
||||
}
|
||||
|
||||
for ( const package_name of packages ) {
|
||||
const result = await this._host.execute(command.replace(placeholder, package_name))
|
||||
if ( result.exit_code !== 0 ) {
|
||||
throw new Error(`Error encountered while executing command (${command.replace(placeholder, package_name)}): ${result.stderr.join('\n')}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = exports = PackageManager
|
||||
Reference in New Issue
Block a user