Start adding state classes; update dependencies

This commit is contained in:
garrettmills
2020-02-27 00:31:07 -06:00
parent e2016069f2
commit a49a26da48
14 changed files with 333 additions and 80 deletions

View File

@@ -13,6 +13,11 @@ class ServiceConfig extends Injectable {
install_to: 'multi-user.target',
}
constructor(merge_data = {}) {
super()
this._data = {...this._data, ...merge_data}
}
name(set) { return this._get_or_set('name', set) }
description(set) { return this._get_or_set('description', set) }

View File

@@ -0,0 +1,11 @@
class Routine {
constructor(steps) {
this.steps = steps
}
apply_to(host) {
// TODO
}
}
module.exports = exports = Routine

View File

@@ -5,6 +5,8 @@ class DirectoryState extends State {
if ( !(await this.check()) ) {
const path = await this._path()
await path.touch(true)
// TODO directories
}
}

View File

@@ -1,64 +0,0 @@
const State = require('../State')
const UniversalPath = require('../../logical/UniversalPath')
class FileContentState extends State {
static get services() {
return [...super.services, 'hosts']
}
async apply() {
const target_path = await this._target()
if ( target_path.is_valid() ) {
}
if ( this._config.source ) {
const source_path = await this._source()
await source_path.copy_to(target_path)
} else {
await target_path.echo(this._config.contents)
}
}
async check() {
if ( this._config.source ) {
const source_path = await this._source()
const source_hash = await source_path.hash()
const target_path = await this._target()
if ( !target_path.is_file() ) return false
const target_hash = await target_path.hash()
return source_hash === target_hash
} else {
const localhost = this.hosts.get('localhost')
const temp_path = await localhost.get_temp_file()
await temp_path.echo(this._config.contents)
const source_hash = await temp_path.hash()
const target_path = await this._target()
if ( !target_path.is_file() ) return false
const target_hash = await target_path.hash()
return source_hash === target_hash
}
}
async reverse() {
await (await this._target()).echo('')
}
async _target() {
if ( typeof this._config.target === 'string' ) {
return UniversalPath.fromString(this._config.target)
} else return this._config.target
}
async _source() {
if ( typeof this._config.target === 'string' ) {
return UniversalPath.fromString(this._config.target)
} else return this._config.target
}
}
module.exports = exports = FileContentState

View File

@@ -5,6 +5,12 @@ class FileState extends State {
if ( !(await this.check()) ) {
const path = await this._path()
await path.touch()
if ( this._config.contents ) {
await path.echo(this._config.contents)
} else if ( this._config.template ) {
await path.copy_from(this._config.template)
}
}
}

View File

@@ -0,0 +1,28 @@
const State = require('../State')
class PackageCacheClearedState extends State {
static get services() {
return [...super.services, 'output']
}
constructor(host, config) {
if ( !host.packages ) {
throw new Error(`Cannot apply package state to host ${host.name}: missing package manager API.`)
}
super(host, config)
}
async apply() {
return this._host.packages.clear_cache()
}
async check() {
return false
}
async reverse() {
this.output.warn(`Package cache cleared state does not currently support reversal. (Host: ${this._host.name})`)
}
}
module.exports = exports = PackageCacheClearedState

View File

@@ -0,0 +1,24 @@
const State = require('../State')
class PackageState extends State {
constructor(host, config) {
if ( !host.packages ) {
throw new Error(`Cannot apply package state to host ${host.name}: missing package manager API.`)
}
super(host, config)
}
async apply() {
return this._host.packages.install(this._config.package)
}
async check() {
return this._host.packages.is_installed(this._config.package)
}
async reverse() {
return this._host.packages.uninstall(this._config.package)
}
}
module.exports = exports = PackageState

View File

@@ -0,0 +1,28 @@
const State = require('../State')
class ServiceDaemonReloadState extends State {
static get services() {
return [...super.services, 'output']
}
constructor(host, config) {
if ( !host.services ) {
throw new Error(`Cannot apply service state to host ${host.name}: missing service manager API.`)
}
super(host, config)
}
async apply() {
return this._host.services.daemon_reload()
}
async check() {
return false
}
async reverse() {
this.output.warn(`Service daemon reload state does not currently support reversal. (Host: ${this._host.name})`)
}
}
module.exports = exports = ServiceDaemonReloadState

View File

@@ -0,0 +1,29 @@
const State = require('../State')
class ServiceRestartState extends State {
static get services() {
return [...super.services, 'output']
}
constructor(host, config) {
if ( !host.services ) {
throw new Error(`Cannot apply service state to host ${host.name}: missing service manager API.`)
}
super(host, config)
}
async apply() {
const services = Array.isArray(this._config.service) ? this._config.service : [this._config.service]
return this._host.services.restart(...services)
}
async check() {
return false
}
async reverse() {
this.output.warn(`Service restart state does not currently support reversal. (Host: ${this._host.name})`)
}
}
module.exports = exports = ServiceRestartState

View File

@@ -0,0 +1,29 @@
const State = require('../State')
class ServiceState extends State {
constructor(host, config) {
if ( !host.services ) {
throw new Error(`Cannot apply service state to host ${host.name}: missing service manager API.`)
}
super(host, config)
}
async apply() {
const services = Array.isArray(this._config.service) ? this._config.service : [this._config.service]
return this._host.services.start(...services)
}
async check() {
const services = Array.isArray(this._config.service) ? this._config.service : [this._config.service]
let states = await this._host.services.status(...services)
if ( !Array.isArray(states) ) states = [states]
return states.every(x => x.status === 'running')
}
async reverse() {
const services = Array.isArray(this._config.service) ? this._config.service : [this._config.service]
return this._host.services.stop(...services)
}
}
module.exports = exports = ServiceState

View File

@@ -0,0 +1,29 @@
const State = require('../State')
class UpdateState extends State {
static get services() {
return [...super.services, 'output']
}
constructor(host, config) {
if ( !host.packages ) {
throw new Error(`Cannot apply update state to host ${host.name}: missing package manager API.`)
}
super(host, config)
}
async apply() {
const packages = (await this._host.packages.list_updates()).map(x => x.name)
await this._host.packages.update(...packages)
}
async check() {
return (await this._host.packages.list_updates()).length === 0
}
async reverse() {
this.output.warn(`Update state does not currently support reversing package updates. (Host: ${this._host.name})`)
}
}
module.exports = exports = UpdateState

View File

@@ -0,0 +1,50 @@
const { Service } = require('flitter-di')
/*
* states Service
* -------------------------------------------------------------
* This is a service file that will be made available through Flitter's
* dependency injector to the rest of the application based on its given
* canonical name.
*
* e.g. app.di().service("states")
*/
class StatesService extends Service {
static #state_map = {
// TODO apache and nginx states - virtual host, reverse proxy
// TODO file/directory permissions state - chmod & chown
// TODO file download state
// TODO file unpack state - zips, tarballs
// TODO package repository states - import keys, install repository
// TODO service manager states - service enabled, service installed
// TODO git states - clone repo, ref checked out
'fs.file': require('../classes/state/fs/FileState'),
'fs.directory': require('../classes/state/fs/DirectoryState'),
'package.present': require('../classes/state/os/PackageState'),
'package.updates': require('../classes/state/os/UpdateState'),
'package.cache.clear': require('../classes/state/os/PackageCacheClearedState'),
'service.running': require('../classes/state/os/ServiceState'),
'service.restarted': require('../classes/state/os/ServiceRestartState'),
'service.daemon.reloaded': require('../classes/state/os/ServiceDaemonReloadState'),
}
static get services() {
return [...super.services, 'app', 'configs']
}
from_config(host, state_config) {
const type = state_config.type
delete state_config.type
const StepClass = this.constructor.#state_map[type]
this.app.di().make(StepClass)
if ( !StepClass ) throw new Error(`Invalid or unknown step type: ${type}`)
return new StepClass(host, state_config)
}
}
module.exports = exports = StatesService