summaryrefslogtreecommitdiffstats
path: root/modules-available/sysconfig/inc/configtgz.inc.php
diff options
context:
space:
mode:
Diffstat (limited to 'modules-available/sysconfig/inc/configtgz.inc.php')
-rw-r--r--modules-available/sysconfig/inc/configtgz.inc.php253
1 files changed, 144 insertions, 109 deletions
diff --git a/modules-available/sysconfig/inc/configtgz.inc.php b/modules-available/sysconfig/inc/configtgz.inc.php
index c1df2c93..8ac87908 100644
--- a/modules-available/sysconfig/inc/configtgz.inc.php
+++ b/modules-available/sysconfig/inc/configtgz.inc.php
@@ -4,29 +4,28 @@ class ConfigTgz
{
private $configId = 0;
- private $configTitle = false;
- private $file = false;
+ private $configTitle = '';
+ private $file = '';
private $modules = array();
private function __construct()
{
- ;
}
- public function id()
+ public function id(): int
{
return $this->configId;
}
- public function title()
+ public function title(): string
{
return $this->configTitle;
}
- public function areAllModulesUpToDate()
+ public function areAllModulesUpToDate(): bool
{
if (!$this->configId > 0)
- Util::traceError('ConfigTgz::areAllModulesUpToDate called on un-inserted config.tgz!');
+ ErrorHandler::traceError('ConfigTgz::areAllModulesUpToDate called on un-inserted config.tgz!');
foreach ($this->modules as $module) {
if (!empty($module['filepath']) && file_exists($module['filepath'])) {
if ($module['status'] !== 'OK')
@@ -38,25 +37,32 @@ class ConfigTgz
return true;
}
- public function isActive()
+ public function isActive(): bool
{
return readlink(CONFIG_HTTP_DIR . '/default/config.tgz') === $this->file;
}
-
- public function getModuleIds()
+
+ /**
+ * @return int[]
+ */
+ public function getModuleIds(): array
{
- $ret = array();
+ $ret = [];
foreach ($this->modules as $module) {
- $ret[] = $module['moduleid'];
+ $ret[] = (int)$module['moduleid'];
}
return $ret;
}
-
- public function update($title, $moduleIds)
+
+ /**
+ * @param string $title New title for module
+ * @param int[] $moduleIds List of modules to include in this config
+ */
+ public function update(string $title, array $moduleIds): void
{
- if (!is_array($moduleIds))
- return false;
- $this->configTitle = $title;
+ if (!empty($title)) {
+ $this->configTitle = $title;
+ }
$this->modules = array();
// Get all modules to put in config
$idstr = '0'; // Passed directly in query. Make sure no SQL injection is possible
@@ -67,7 +73,7 @@ class ConfigTgz
// Delete old connections
Database::exec("DELETE FROM configtgz_x_module WHERE configid = :configid", array('configid' => $this->configId));
// Make connection
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
Database::exec("INSERT INTO configtgz_x_module (configid, moduleid) VALUES (:configid, :moduleid)", array(
'configid' => $this->configId,
'moduleid' => $row['moduleid']
@@ -77,41 +83,41 @@ class ConfigTgz
// Update name
Database::exec("UPDATE configtgz SET title = :title, status = :status, dateline = :now WHERE configid = :configid LIMIT 1", array(
'configid' => $this->configId,
- 'title' => $title,
+ 'title' => $this->configTitle,
'status' => 'OUTDATED',
'now' => time(),
));
- return true;
}
/**
*
- * @param bool $deleteOnError
- * @param int $timeoutMs
- * @return string - OK (success)
- * - OUTDATED (updating failed, but old version still exists)
- * - MISSING (failed and no old version available)
+ * @param bool $deleteOnError Delete this config in case of error?
+ * @param int $timeoutMs max time to wait for completion
+ * @param string|null $parentTask parent task to order this (re)build after
+ * @return string|bool true=success, false=error, string=taskid, still running
*/
- public function generate($deleteOnError = false, $timeoutMs = 0)
+ public function generate(bool $deleteOnError = false, int $timeoutMs = 0, ?string $parentTask = null)
{
- if (!($this->configId > 0) || !is_array($this->modules) || $this->file === false)
- Util::traceError ('configId <= 0 or modules not array in ConfigTgz::rebuild()');
+ if (!($this->configId > 0) || empty($this->file))
+ ErrorHandler::traceError('configId <= 0 or no file in ConfigTgz::rebuild()');
$files = array();
// Get all config modules for system config
foreach ($this->modules as $module) {
if (!empty($module['filepath']) && file_exists($module['filepath'])) {
- $files[] = $module['filepath'];
+ // Dupcheck only for custom modules for now
+ $files[$module['filepath']] = ($module['moduletype'] === 'CustomModule');
}
}
- $task = self::recompress($files, $this->file);
+ $task = self::recompress($files, $this->file, $parentTask);
// Wait for completion
- if ($timeoutMs > 0 && !Taskmanager::isFailed($task) && !Taskmanager::isFinished($task))
+ if ($timeoutMs > 0 && !Taskmanager::isFailed($task) && !Taskmanager::isFinished($task)) {
$task = Taskmanager::waitComplete($task, $timeoutMs);
- if ($task === true || (isset($task['statusCode']) && $task['statusCode'] === Taskmanager::TASK_FINISHED)) {
+ }
+ if (Taskmanager::isFinished($task)) {
// Success!
- $this->markUpdated();
+ $this->markUpdated($task);
return true;
}
if (!is_array($task) || !isset($task['id']) || Taskmanager::isFailed($task)) {
@@ -131,54 +137,81 @@ class ConfigTgz
return $task['id'];
}
- public function delete()
+ public function delete(): bool
{
if ($this->configId === 0)
- Util::traceError('ConfigTgz::delete called with invalid config id!');
+ ErrorHandler::traceError('ConfigTgz::delete called with invalid config id!');
$ret = Database::exec("DELETE FROM configtgz WHERE configid = :configid LIMIT 1",
['configid' => $this->configId], true);
if ($ret !== false) {
- if ($this->file !== false)
+ if (!empty($this->file)) {
Taskmanager::submit('DeleteFile', array('file' => $this->file), true);
+ }
$this->configId = 0;
- $this->modules = false;
- $this->file = false;
+ $this->modules = [];
+ $this->file = '';
}
return $ret !== false;
}
- public function markOutdated()
+ public function markOutdated(): void
{
if ($this->configId === 0)
- Util::traceError('ConfigTgz::markOutdated called with invalid config id!');
- return $this->mark('OUTDATED');
+ ErrorHandler::traceError('ConfigTgz::markOutdated called with invalid config id!');
+ $this->mark('OUTDATED');
}
- private function markUpdated()
+ private function markUpdated(array $task): void
{
if ($this->configId === 0)
- Util::traceError('ConfigTgz::markUpdated called with invalid config id!');
- if ($this->areAllModulesUpToDate())
- return $this->mark('OK');
- return $this->mark('OUTDATED');
+ ErrorHandler::traceError('ConfigTgz::markUpdated called with invalid config id!');
+ if ($this->areAllModulesUpToDate()) {
+ if (empty($task['data']['warnings'])) {
+ $warnings = '';
+ } else {
+ // There have been warnings while generating the combined archive
+ // Most likely duplicate file entries.
+ // Get mapping of moduleid to module name for prettier log display
+ $res = Database::simpleQuery('SELECT moduleid, title FROM configtgz_module');
+ $mods = [];
+ foreach ($res as $row) {
+ $mods[$row['moduleid']] = $row['title'];
+ }
+ // Now extract module id from filename and if applicable, replace filename by module name
+ $warnings = preg_replace_callback('#/opt/openslx/configs/modules/(\w+)_id-(\d+)__.*$#m', function ($m) use ($mods) {
+ if (!isset($mods[$m[2]]))
+ return $m[0];
+ return $mods[$m[2]] . ' (' . $m[1] . '#' . $m[2] . ')';
+ }, $task['data']['warnings']);
+ }
+ Database::exec("UPDATE configtgz SET status = :status, warnings = :warnings
+ WHERE configid = :configid LIMIT 1", [
+ 'configid' => $this->configId,
+ 'status' => 'OK',
+ 'warnings' => $warnings,
+ ]);
+ return;
+ }
+ $this->mark('OUTDATED');
}
- private function markFailed()
+ private function markFailed(): void
{
if ($this->configId === 0)
- Util::traceError('ConfigTgz::markFailed called with invalid config id!');
- if ($this->file === false || !file_exists($this->file))
- return $this->mark('MISSING');
- return $this->mark('OUTDATED');
+ ErrorHandler::traceError('ConfigTgz::markFailed called with invalid config id!');
+ if (empty($this->file) || !file_exists($this->file)) {
+ $this->mark('MISSING');
+ } else {
+ $this->mark('OUTDATED');
+ }
}
- private function mark($status)
+ private function mark($status): void
{
- Database::exec("UPDATE configtgz SET status = :status WHERE configid = :configid LIMIT 1", array(
+ Database::exec("UPDATE configtgz SET status = :status WHERE configid = :configid LIMIT 1", [
'configid' => $this->configId,
- 'status' => $status
- ));
- return $status;
+ 'status' => $status,
+ ]);
}
/*
@@ -186,28 +219,30 @@ class ConfigTgz
*/
/**
- * @param string[] $files source files to include
+ * @param bool[] $files source files to include key = file, value = dupCheck
* @param string $destFile where to store final result
* @return false|array taskmanager task
*/
- private static function recompress($files, $destFile)
+ private static function recompress(array $files, string $destFile, $parentTask = null)
{
// Get stuff other modules want to inject
$handler = function($hook) {
include $hook->file;
- return isset($file) ? $file : false;
+ return $file ?? false;
};
foreach (Hook::load('config-tgz') as $hook) {
$file = $handler($hook);
if ($file !== false) {
- $files[] = $file;
+ $files[$file] = true;
}
}
// Hand over to tm
return Taskmanager::submit('RecompressArchive', array(
'inputFiles' => $files,
- 'outputFile' =>$destFile
+ 'outputFile' =>$destFile,
+ 'parentTask' => $parentTask,
+ 'failOnParentFail' => false,
));
}
@@ -216,15 +251,15 @@ class ConfigTgz
* on each one. This mostly makes sense to call if a global module
* that is injected via a hook has changed.
*/
- public static function rebuildAllConfigs()
+ public static function rebuildAllConfigs(): void
{
Database::exec("UPDATE configtgz SET status = :status", array(
'status' => 'OUTDATED'
));
$res = Database::simpleQuery("SELECT configid FROM configtgz");
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
$configTgz = self::get($row['configid']);
- if ($configTgz !== false) {
+ if ($configTgz !== null) {
$configTgz->generate();
}
}
@@ -233,12 +268,10 @@ class ConfigTgz
/**
* @param string $title Title of config
* @param int[] $moduleIds Modules to include in config
- * @return false|ConfigTgz The module instance, false on error
+ * @return ConfigTgz The module instance
*/
- public static function insert($title, $moduleIds)
+ public static function insert(string $title, array $moduleIds): ConfigTgz
{
- if (!is_array($moduleIds))
- return false;
$instance = new ConfigTgz;
$instance->configTitle = $title;
// Create output file name (config.tgz)
@@ -260,7 +293,7 @@ class ConfigTgz
}
$res = Database::simpleQuery("SELECT moduleid, moduletype, filepath, status FROM configtgz_module WHERE moduleid IN ($idstr)");
// Make connection
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $row) {
Database::exec("INSERT INTO configtgz_x_module (configid, moduleid) VALUES (:configid, :moduleid)", array(
'configid' => $instance->configId,
'moduleid' => $row['moduleid']
@@ -270,53 +303,53 @@ class ConfigTgz
return $instance;
}
- public static function get($configId)
+ /**
+ * @param array{configid: int, title: string, filepath: string} $row Input data, fields mandatory
+ */
+ private static function instanceFromRow(array $row): ConfigTgz
{
- $ret = Database::queryFirst("SELECT configid, title, filepath FROM configtgz WHERE configid = :configid", array(
- 'configid' => $configId
- ));
- if ($ret === false)
- return false;
$instance = new ConfigTgz;
- $instance->configId = $ret['configid'];
- $instance->configTitle = $ret['title'];
- $instance->file = $ret['filepath'];
- $ret = Database::simpleQuery("SELECT moduleid, moduletype, filepath, status FROM configtgz_x_module "
+ $instance->configId = $row['configid'];
+ $instance->configTitle = $row['title'];
+ $instance->file = $row['filepath'];
+ $innerRes = Database::simpleQuery("SELECT moduleid, moduletype, filepath, status FROM configtgz_x_module "
. " INNER JOIN configtgz_module USING (moduleid) "
. " WHERE configid = :configid", array('configid' => $instance->configId));
$instance->modules = array();
- while ($row = $ret->fetch(PDO::FETCH_ASSOC)) {
- $instance->modules[] = $row;
+ foreach ($innerRes as $innerRow) {
+ $instance->modules[] = $innerRow;
}
return $instance;
}
+ public static function get(int $configId): ?ConfigTgz
+ {
+ $ret = Database::queryFirst("SELECT configid, title, filepath FROM configtgz WHERE configid = :configid", array(
+ 'configid' => $configId
+ ));
+ if ($ret === false)
+ return null;
+ return self::instanceFromRow($ret);
+ }
+
/**
* @param int $moduleId ID of config module
- * @return ConfigTgz[]|false
+ * @return ConfigTgz[]
*/
- public static function getAllForModule($moduleId)
+ public static function getAllForModule(int $moduleId): array
{
$res = Database::simpleQuery("SELECT configid, title, filepath FROM configtgz_x_module "
. " INNER JOIN configtgz USING (configid) "
. " WHERE moduleid = :moduleid", array(
'moduleid' => $moduleId
));
- if ($res === false)
- return false;
+ if ($res === false) {
+ EventLog::warning('ConfigTgz::getAllForModule failed: ' . Database::lastError());
+ return [];
+ }
$list = array();
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- $instance = new ConfigTgz;
- $instance->configId = $row['configid'];
- $instance->configTitle = $row['title'];
- $instance->file = $row['filepath'];
- $innerRes = Database::simpleQuery("SELECT moduleid, moduletype, filepath, status FROM configtgz_x_module "
- . " INNER JOIN configtgz_module USING (moduleid) "
- . " WHERE configid = :configid", array('configid' => $instance->configId));
- $instance->modules = array();
- while ($innerRow = $innerRes->fetch(PDO::FETCH_ASSOC)) {
- $instance->modules[] = $innerRow;
- }
+ foreach ($res as $row) {
+ $instance = self::instanceFromRow($row);
$list[] = $instance;
}
return $list;
@@ -326,50 +359,52 @@ class ConfigTgz
* Called when (re)generating a config tgz failed, so we can
* update the status in the DB and add a server log entry.
*
- * @param array $task
* @param array $args contains 'configid' and optionally 'deleteOnError'
*/
- public static function generateFailed($task, $args)
+ public static function generateFailed(array $task, array $args): void
{
if (!isset($args['configid']) || !is_numeric($args['configid'])) {
EventLog::warning('Ignoring generateFailed event as it has no configid assigned.');
return;
}
$config = self::get($args['configid']);
- if ($config === false) {
+ if ($config === null) {
EventLog::warning('generateFailed callback for config id ' . $args['configid'] . ', but no instance could be generated.');
return;
}
- if (isset($task['data']['error']))
+ if (isset($task['data']['error'])) {
$error = $task['data']['error'];
- elseif (isset($task['data']['messages']))
+ } elseif (isset($task['data']['messages'])) {
$error = $task['data']['messages'];
- else
+ } else {
$error = '';
+ }
EventLog::failure("Generating config.tgz '" . $config->configTitle . "' failed.", $error);
- if ($args['deleteOnError'])
+ if ($args['deleteOnError']) {
$config->delete();
- else
+ } else {
$config->markFailed();
+ }
}
/**
* (Re)generating a config tgz succeeded. Update db entry.
*
+ * @param array $task the task object
* @param array $args contains 'configid' and optionally 'deleteOnError'
*/
- public static function generateSucceeded($args)
+ public static function generateSucceeded(array $task, array $args): void
{
if (!isset($args['configid']) || !is_numeric($args['configid'])) {
EventLog::warning('Ignoring generateSucceeded event as it has no configid assigned.');
return;
}
$config = self::get($args['configid']);
- if ($config === false) {
+ if ($config === null) {
EventLog::warning('generateSucceeded callback for config id ' . $args['configid'] . ', but no instance could be generated.');
return;
}
- $config->markUpdated();
+ $config->markUpdated($task);
}
-
+
}