Export: first bunch of exportable Director objects

ImportSource, SyncRule, Datafield and more

refs #1409
This commit is contained in:
Thomas Gelf 2018-05-29 12:34:18 +02:00
parent 5d4408862a
commit 927591501c
16 changed files with 678 additions and 28 deletions

View File

@ -0,0 +1,184 @@
<?php
namespace Icinga\Module\Director\Clicommands;
use Icinga\Module\Director\Cli\Command;
use Icinga\Module\Director\DirectorObject\Automation\ImportExport;
/**
* Export Director Config Objects
*/
class ExportCommand extends Command
{
/**
* Export all ImportSource definitions
*
* Use this command to delete a single Icinga object
*
* USAGE
*
* icingacli director export importsource [options]
*
* OPTIONS
*
* --no-pretty JSON is pretty-printed per default
* Use this flag to enforce unformatted JSON
*/
public function importsourceAction()
{
$export = new ImportExport($this->db());
echo $this->renderJson(
$export->serializeAllImportSources(),
!$this->params->shift('no-pretty')
);
}
/**
* Export all SyncRule definitions
*
* Use this command to delete a single Icinga object
*
* USAGE
*
* icingacli director syncrule export [options]
*
* OPTIONS
*
* --no-pretty JSON is pretty-printed per default
* Use this flag to enforce unformatted JSON
*/
public function syncruleAction()
{
$export = new ImportExport($this->db());
echo $this->renderJson(
$export->serializeAllSyncRules(),
!$this->params->shift('no-pretty')
);
}
/**
* Export all Job definitions
*
* USAGE
*
* icingacli director export job [options]
*
* OPTIONS
*
* --no-pretty JSON is pretty-printed per default
* Use this flag to enforce unformatted JSON
*/
public function jobAction()
{
$export = new ImportExport($this->db());
echo $this->renderJson(
$export->serializeAllJobs(),
!$this->params->shift('no-pretty')
);
}
/**
* Export all DataField definitions
*
* USAGE
*
* icingacli director export datafield [options]
*
* OPTIONS
*
* --no-pretty JSON is pretty-printed per default
* Use this flag to enforce unformatted JSON
*/
public function datafieldAction()
{
$export = new ImportExport($this->db());
echo $this->renderJson(
$export->serializeAllDataFields(),
!$this->params->shift('no-pretty')
);
}
/**
* Export all DataList definitions
*
* USAGE
*
* icingacli director export datafield [options]
*
* OPTIONS
*
* --no-pretty JSON is pretty-printed per default
* Use this flag to enforce unformatted JSON
*/
public function datalistAction()
{
$export = new ImportExport($this->db());
echo $this->renderJson(
$export->serializeAllDataLists(),
!$this->params->shift('no-pretty')
);
}
/**
* Export all IcingaHostGroup definitions
*
* USAGE
*
* icingacli director export hostgroup [options]
*
* OPTIONS
*
* --no-pretty JSON is pretty-printed per default
* Use this flag to enforce unformatted JSON
*/
public function hostgroupAction()
{
$export = new ImportExport($this->db());
echo $this->renderJson(
$export->serializeAllHostGroups(),
!$this->params->shift('no-pretty')
);
}
/**
* Export all IcingaServiceGroup definitions
*
* USAGE
*
* icingacli director export servicegroup [options]
*
* OPTIONS
*
* --no-pretty JSON is pretty-printed per default
* Use this flag to enforce unformatted JSON
*/
public function servicegroupAction()
{
$export = new ImportExport($this->db());
echo $this->renderJson(
$export->serializeAllServiceGroups(),
!$this->params->shift('no-pretty')
);
}
/**
* Export all IcingaTemplateChoiceHost definitions
*
* USAGE
*
* icingacli director export hosttemplatechoice [options]
*
* OPTIONS
*
* --no-pretty JSON is pretty-printed per default
* Use this flag to enforce unformatted JSON
*/
public function hosttemplatechoiceAction()
{
$export = new ImportExport($this->db());
echo $this->renderJson(
$export->serializeAllHostTemplateChoices(),
!$this->params->shift('no-pretty')
);
}
}

View File

@ -30,6 +30,10 @@ class ImportsourceController extends ActionController
public function indexAction()
{
$source = ImportSource::load($this->params->getRequired('id'), $this->db());
if ($this->params->get('format') === 'json') {
$this->sendJson($this->getResponse());
return;
}
$this->addTitle(
$this->translate('Import source: %s'),
$source->get('source_name')

View File

@ -42,6 +42,13 @@ abstract class DbObjectWithSettings extends DbObject
return parent::get($key);
}
public function setSettings(array $settings)
{
$this->settings = $settings;
return $this;
}
public function getSettings()
{
return $this->settings;

View File

@ -0,0 +1,103 @@
<?php
namespace Icinga\Module\Director\DirectorObject\Automation;
use Icinga\Module\Director\Db;
use Icinga\Module\Director\Objects\DirectorDatafield;
use Icinga\Module\Director\Objects\DirectorDatalist;
use Icinga\Module\Director\Objects\DirectorJob;
use Icinga\Module\Director\Objects\IcingaHostGroup;
use Icinga\Module\Director\Objects\IcingaServiceGroup;
use Icinga\Module\Director\Objects\IcingaTemplateChoiceHost;
use Icinga\Module\Director\Objects\ImportSource;
use Icinga\Module\Director\Objects\SyncRule;
class ImportExport
{
protected $connection;
public function __construct(Db $connection)
{
$this->connection = $connection;
}
public function serializeAllHostTemplateChoices()
{
$res = [];
foreach (IcingaTemplateChoiceHost::loadAll($this->connection) as $object) {
$res[] = $object->export();
}
return $res;
}
public function serializeAllHostGroups()
{
$res = [];
foreach (IcingaHostGroup::loadAll($this->connection) as $object) {
$res[] = $object->toPlainObject();
}
return $res;
}
public function serializeAllServiceGroups()
{
$res = [];
foreach (IcingaServiceGroup::loadAll($this->connection) as $object) {
$res[] = $object->toPlainObject();
}
return $res;
}
public function serializeAllDataFields()
{
$res = [];
foreach (DirectorDatafield::loadAll($this->connection) as $object) {
$res[] = $object->export();
}
return $res;
}
public function serializeAllDataLists()
{
$res = [];
foreach (DirectorDatalist::loadAll($this->connection) as $object) {
$res[] = $object->export();
}
return $res;
}
public function serializeAllJobs()
{
$res = [];
foreach (DirectorJob::loadAll($this->connection) as $object) {
$res[] = $object->export();
}
return $res;
}
public function serializeAllImportSources()
{
$res = [];
foreach (ImportSource::loadAll($this->connection) as $object) {
$res[] = $object->export();
}
return $res;
}
public function serializeAllSyncRules()
{
$res = [];
foreach (SyncRule::loadAll($this->connection) as $object) {
$res[] = $object->export();
}
return $res;
}
}

View File

@ -2,7 +2,6 @@
namespace Icinga\Module\Director\Hook;
use Icinga\Application\Logger;
use Icinga\Module\Director\Db;
use Icinga\Module\Director\Objects\DirectorJob;
use Icinga\Module\Director\Web\Form\QuickForm;
@ -53,6 +52,11 @@ abstract class JobHook
return $class;
}
public function exportSettings()
{
return $this->jobDefinition->getSettings();
}
public static function getSuggestedRunInterval(QuickForm $form)
{
return 900;

View File

@ -22,6 +22,24 @@ class ImportJob extends JobHook
}
}
public function exportSettings()
{
$settings = parent::exportSettings();
if (array_key_exists('source_id', $settings)) {
$id = $settings['source_id'];
if ($id !== '__ALL__') {
$settings['source'] = ImportSource::loadWithAutoIncId(
$id,
$this->db()
)->get('source_name');
}
unset($settings['source_id']);
}
return $settings;
}
protected function runForSource(ImportSource $source)
{
if ($this->getSetting('run_import') === 'y') {

View File

@ -24,6 +24,20 @@ class SyncJob extends JobHook
}
}
public function exportSettings()
{
$settings = [
'apply_changes' => $this->getSetting('apply_changes') === 'y'
];
$id = $this->getSetting('rule_id');
if ($id !== '__ALL__') {
$settings['rule'] = SyncRule::load((int) $id, $this->db())
->get('rule_name');
}
return $settings;
}
protected function runForRule(SyncRule $rule)
{
if ($this->getSetting('apply_changes') === 'y') {

View File

@ -50,6 +50,24 @@ class DirectorDatafield extends DbObjectWithSettings
return $obj;
}
public function export()
{
$plain = (object) $this->getProperties();
$plain->originalId = $plain->id;
unset($plain->id);
$plain->settings = (object) $this->getSettings();
if (property_exists($plain->settings, 'datalist_id')) {
$plain->settings->datalist = DirectorDatalist::loadWithAutoIncId(
$plain->settings->datalist_id,
$this->getConnection()
)->get('list_name');
unset($plain->settings->datalist_id);
}
return $plain;
}
protected function setObject(IcingaObject $object)
{
$this->object = $object;

View File

@ -17,4 +17,23 @@ class DirectorDatalist extends DbObject
'list_name' => null,
'owner' => null
);
public function export()
{
$plain = (object) $this->getProperties();
$plain->originalId = $plain->id;
unset($plain->id);
$plain->entries = [];
$entries = DirectorDatalistEntry::loadAllForList($this);
foreach ($entries as $key => $entry) {
$plainEntry = (object) $entry->getProperties();
unset($plainEntry->id);
unset($plainEntry->list_id);
$plain->entries[] = $plainEntry;
}
return $plain;
}
}

View File

@ -22,6 +22,22 @@ class DirectorDatalistEntry extends DbObject
'allowed_roles' => null,
);
/**
* @param DirectorDatalist $list
* @return static[]
* @throws IcingaException
*/
public static function loadAllForList(DirectorDatalist $list)
{
$query = $list->getDb()
->select()
->from('director_datalist_entry')
->where('list_id = ?', $list->get('id'))
->order('entry_name ASC');
return static::loadAll($list->getConnection(), $query, 'entry_name');
}
/**
* @param $roles
* @throws IcingaException

View File

@ -2,6 +2,7 @@
namespace Icinga\Module\Director\Objects;
use Icinga\Exception\IcingaException;
use Icinga\Module\Director\Data\Db\DbObjectWithSettings;
use Icinga\Module\Director\Hook\JobHook;
use Exception;
@ -17,7 +18,7 @@ class DirectorJob extends DbObjectWithSettings
protected $autoincKeyName = 'id';
protected $defaultProperties = array(
protected $defaultProperties = [
'id' => null,
'job_name' => null,
'job_class' => null,
@ -28,26 +29,40 @@ class DirectorJob extends DbObjectWithSettings
'ts_last_error' => null,
'last_error_message' => null,
'timeperiod_id' => null,
);
];
protected $stateProperties = [
'last_attempt_succeeded',
'last_error_message',
'ts_last_attempt',
'ts_last_error',
];
protected $settingsTable = 'director_job_setting';
protected $settingsRemoteId = 'job_id';
/**
* @return JobHook
*/
public function job()
{
if ($this->job === null) {
$class = $this->job_class;
$this->job = new $class;
$this->job->setDb($this->connection);
$this->job->setDefinition($this);
}
return $this->job;
}
/**
* @throws IcingaException
*/
public function run()
{
$job = $this->job()->setDefinition($this);
$job = $this->job();
$this->ts_last_attempt = date('Y-m-d H:i:s');
try {
@ -64,11 +79,21 @@ class DirectorJob extends DbObjectWithSettings
}
}
/**
* @return bool
* @throws IcingaException
* @throws \Icinga\Exception\NotFoundError
*/
public function shouldRun()
{
return (! $this->hasBeenDisabled()) && $this->isPending();
}
/**
* @return bool
* @throws IcingaException
* @throws \Icinga\Exception\NotFoundError
*/
public function isOverdue()
{
if (! $this->shouldRun()) {
@ -85,6 +110,11 @@ class DirectorJob extends DbObjectWithSettings
return $this->disabled === 'y';
}
/**
* @return bool
* @throws IcingaException
* @throws \Icinga\Exception\NotFoundError
*/
public function isPending()
{
if ($this->ts_last_attempt === null) {
@ -98,6 +128,11 @@ class DirectorJob extends DbObjectWithSettings
return false;
}
/**
* @return bool
* @throws IcingaException
* @throws \Icinga\Exception\NotFoundError
*/
public function isWithinTimeperiod()
{
if ($this->hasTimeperiod()) {
@ -117,6 +152,53 @@ class DirectorJob extends DbObjectWithSettings
return $this->timeperiod_id !== null;
}
/**
* @param $timeperiod
* @return $this
* @throws IcingaException
*/
public function setTimeperiod($timeperiod)
{
if (is_string($timeperiod)) {
$timeperiod = IcingaTimePeriod::load($timeperiod, $this->connection);
} elseif (! $timeperiod instanceof IcingaTimePeriod) {
throw new IcingaException('TimePeriod expected');
}
$this->set('timeperiod_id', $timeperiod->get('id'));
return $this;
}
/**
* @return object
* @throws IcingaException
* @throws \Icinga\Exception\NotFoundError
* @throws \Icinga\Exception\ProgrammingError
*/
public function export()
{
$plain = (object) $this->getProperties();
$plain->originalId = $plain->id;
unset($plain->id);
unset($plain->timeperiod_id);
if ($this->hasTimeperiod()) {
$plain->timeperiod = $this->timeperiod()->getObjectName();
}
foreach ($this->stateProperties as $key) {
unset($plain->$key);
}
$plain->settings = $this->job()->exportSettings();
return $plain;
}
/**
* @return IcingaTimePeriod
* @throws IcingaException
* @throws \Icinga\Exception\NotFoundError
*/
protected function timeperiod()
{
return IcingaTimePeriod::loadWithAutoIncId($this->timeperiod_id, $this->connection);

View File

@ -2336,12 +2336,13 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
public function getObjectName()
{
if ($this->hasProperty('object_name')) {
return $this->get('object_name');
$property = static::getKeyColumnName();
if ($this->hasProperty($property)) {
return $this->get($property);
} else {
// TODO: replace with an exception once finished
throw new LogicException(sprintf(
'Trying to access "object_name" for an instance of "%s"',
'Trying to access "%s" for an instance of "%s"',
$property,
get_class($this)
));
}
@ -2352,7 +2353,10 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
// allow for icinga_host and host
$type = lcfirst(preg_replace('/^icinga_/', '', $type));
if (strpos($type, 'data') === false) {
// Hint: Sync/Import are not IcingaObjects, this should be reconsidered:
if (strpos($type, 'import') === 0 || strpos($type, 'sync') === 0) {
$prefix = '';
} elseif (strpos($type, 'data') === false) {
$prefix = 'Icinga';
} else {
$prefix = 'Director';
@ -2422,18 +2426,32 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
return $class::exists($id, $db);
}
public static function loadAllByType($type, Db $db, $query = null, $keyColumn = 'object_name')
public static function getKeyColumnName()
{
return 'object_name';
}
public static function loadAllByType($type, Db $db, $query = null, $keyColumn = null)
{
/** @var DbObject $class */
$class = self::classByType($type);
if ($keyColumn === null) {
if (method_exists($class, 'getKeyColumnName')) {
$keyColumn = $class::getKeyColumnName();
}
}
if (is_array($class::create()->getKeyName())) {
return $class::loadAll($db, $query);
} else {
if (PrefetchCache::shouldBeUsed() && $query === null && $keyColumn === 'object_name') {
if (PrefetchCache::shouldBeUsed()
&& $query === null
&& $keyColumn === static::getKeyColumnName()
) {
$result = array();
foreach ($class::prefetchAll($db) as $row) {
$result[$row->object_name] = $row;
$result[$row->$keyColumn] = $row;
}
return $result;

View File

@ -28,6 +28,30 @@ class IcingaTemplateChoice extends IcingaObject
return substr(substr($this->table, 0, -16), 7);
}
public function export()
{
$plain = (object) $this->getProperties();
$plain->originalId = $plain->id;
unset($plain->id);
$requiredId = $plain->required_template_id;
unset($plain->required_template_id);
if ($requiredId) {
$db = $this->getDb();
$query = $db->select()->from(
['o' => $this->getObjectTableName()],
['o.id', 'o.object_name']
)->where("o.object_type = 'template'")
->where('o.template_choice_id = ?', $this->get('id'))
->order('o.object_name');
return $db->fetchPairs($query);
}
$plain->members = array_values($this->getMembers());
return $plain;
}
public function isMainChoice()
{
return $this->hasBeenLoadedFromDb()
@ -134,7 +158,9 @@ class IcingaTemplateChoice extends IcingaObject
['o' => $this->getObjectTableName()],
['o.id', 'o.object_name']
)->where("o.object_type = 'template'")
->where('o.template_choice_id = ?', $this->get('id'));
->where('o.template_choice_id = ?', $this->get('id'))
->order('o.object_name');
return $db->fetchPairs($query);
} else {
return [];

View File

@ -51,6 +51,19 @@ class ImportRowModifier extends DbObjectWithSettings
return $this->hookInstance;
}
/**
* @return \stdClass
*/
public function export()
{
$properties = $this->getProperties();
unset($properties['id']);
unset($properties['source_id']);
$properties['settings'] = (object) $this->getSettings();
return (object) $properties;
}
protected function beforeStore()
{
if (! $this->hasBeenLoadedFromDb()) {

View File

@ -6,6 +6,7 @@ use Icinga\Application\Benchmark;
use Icinga\Exception\ConfigurationError;
use Icinga\Exception\NotFoundError;
use Icinga\Module\Director\Data\Db\DbObjectWithSettings;
use Icinga\Module\Director\Db;
use Icinga\Module\Director\Hook\PropertyModifierHook;
use Icinga\Module\Director\Import\Import;
use Icinga\Module\Director\Import\SyncUtils;
@ -19,7 +20,7 @@ class ImportSource extends DbObjectWithSettings
protected $autoincKeyName = 'id';
protected $defaultProperties = array(
protected $defaultProperties = [
'id' => null,
'source_name' => null,
'provider_class' => null,
@ -28,7 +29,13 @@ class ImportSource extends DbObjectWithSettings
'last_error_message' => null,
'last_attempt' => null,
'description' => null,
);
];
protected $stateProperties = [
'import_state',
'last_error_message',
'last_attempt',
];
protected $settingsTable = 'import_source_setting';
@ -36,6 +43,70 @@ class ImportSource extends DbObjectWithSettings
private $rowModifiers;
/**
* @return \stdClass
*/
public function export()
{
$plain = (object) $this->getProperties();
$plain->originalId = $plain->id;
unset($plain->id);
foreach ($this->stateProperties as $key) {
unset($plain->$key);
}
$plain->settings = (object) $this->getSettings();
$plain->modifiers = $this->exportRowModifiers();
return $plain;
}
public static function import($plain, Db $db, $replace = false)
{
$properties = (array) $plain;
$id = $properties['originalId'];
unset($properties['originalId']);
if (static::existsWithNameAndId($properties['source_name'], $id, $db)) {
$object = static::loadWithAutoIncId($id, $db);
} else {
$object = static::create([], $db);
}
$object->importPropertyModifiers($properties['modifiers']);
unset($properties['modifiers']);
$object->setProperties($properties);
return $object;
}
protected function importPropertyModifiers($modifiers)
{
}
protected static function existsWithNameAndId($name, $id, Db $connection)
{
$db = $connection->getDbAdapter();
return (string) $id === (string) $db->fetchOne(
$db->select()
->from('import_source', 'id')
->where('id = ?', $id)
->where('name = ?', $name)
);
}
protected function exportRowModifiers()
{
$modifiers = [];
foreach ($this->fetchRowModifiers() as $modifier) {
$modifiers[] = $modifier->export();
}
return $modifiers;
}
/**
* @param bool $required
* @return ImportRun|null
@ -62,7 +133,7 @@ class ImportSource extends DbObjectWithSettings
$db = $this->getDb();
$query = $db->select()->from(
array('ir' => 'import_run'),
['ir' => 'import_run'],
'ir.id'
)->where('ir.source_id = ?', $this->id)
->where('ir.start_time < ?', date('Y-m-d H:i:s', $timestamp))
@ -119,6 +190,16 @@ class ImportSource extends DbObjectWithSettings
return $this;
}
public function getObjectName()
{
return $this->get('source_name');
}
public static function getKeyColumnName()
{
return 'source_name';
}
protected function applyPropertyModifierToRow(PropertyModifierHook $modifier, $key, $row)
{
if ($modifier->requiresRow()) {
@ -166,6 +247,9 @@ class ImportSource extends DbObjectWithSettings
return count($this->getRowModifiers()) > 0;
}
/**
* @return ImportRowModifier[]
*/
public function fetchRowModifiers()
{
$db = $this->getDb();
@ -193,11 +277,11 @@ class ImportSource extends DbObjectWithSettings
protected function prepareRowModifiers()
{
$modifiers = array();
$modifiers = [];
foreach ($this->fetchRowModifiers() as $mod) {
if (! array_key_exists($mod->property_name, $modifiers)) {
$modifiers[$mod->property_name] = array();
$modifiers[$mod->property_name] = [];
}
$modifiers[$mod->property_name][] = $mod->getInstance();
@ -208,7 +292,7 @@ class ImportSource extends DbObjectWithSettings
public function listModifierTargetProperties()
{
$list = array();
$list = [];
foreach ($this->getRowModifiers() as $rowMods) {
/** @var PropertyModifierHook $mod */
foreach ($rowMods as $mod) {

View File

@ -17,7 +17,7 @@ class SyncRule extends DbObject
protected $autoincKeyName = 'id';
protected $defaultProperties = array(
protected $defaultProperties = [
'id' => null,
'rule_name' => null,
'object_type' => null,
@ -28,7 +28,13 @@ class SyncRule extends DbObject
'last_error_message' => null,
'last_attempt' => null,
'description' => null,
);
];
protected $stateProperties = [
'import_state',
'last_error_message',
'last_attempt',
];
private $sync;
@ -51,15 +57,15 @@ class SyncRule extends DbObject
public function listInvolvedSourceIds()
{
if (! $this->hasBeenLoadedFromDb()) {
return array();
return [];
}
$db = $this->getDb();
return array_map('intval', array_unique(
$db->fetchCol(
$db->select()
->from(array('p' => 'sync_property'), 'p.source_id')
->join(array('s' => 'import_source'), 's.id = p.source_id', array())
->from(['p' => 'sync_property'], 'p.source_id')
->join(['s' => 'import_source'], 's.id = p.source_id', array())
->where('rule_id = ?', $this->get('id'))
->order('s.source_name')
)
@ -68,7 +74,7 @@ class SyncRule extends DbObject
public function fetchInvolvedImportSources()
{
$sources = array();
$sources = [];
foreach ($this->listInvolvedSourceIds() as $sourceId) {
$sources[$sourceId] = ImportSource::load($sourceId, $this->getConnection());
@ -85,7 +91,7 @@ class SyncRule extends DbObject
$db = $this->getDb();
$query = $db->select()->from(
array('sr' => 'sync_run'),
['sr' => 'sync_run'],
'sr.start_time'
)->where('sr.rule_id = ?', $this->get('id'))
->order('sr.start_time DESC')
@ -102,7 +108,7 @@ class SyncRule extends DbObject
$db = $this->getDb();
$query = $db->select()->from(
array('sr' => 'sync_run'),
['sr' => 'sync_run'],
'sr.id'
)->where('sr.rule_id = ?', $this->get('id'))
->order('sr.start_time DESC')
@ -234,6 +240,40 @@ class SyncRule extends DbObject
}
}
public function export()
{
$plain = (object) $this->getProperties();
$plain->originalId = $plain->id;
unset($plain->id);
foreach ($this->stateProperties as $key) {
unset($plain->$key);
}
$plain->properties = $this->exportSyncProperties();
return $plain;
}
public function exportSyncProperties()
{
$all = [];
$db = $this->getDb();
$sourceNames = $db->fetchPairs(
$db->select()->from('import_source', ['id', 'source_name'])
);
foreach ($this->getSyncProperties() as $property) {
$properties = $property->getProperties();
$properties['source'] = $sourceNames[$properties['source_id']];
unset($properties['id']);
unset($properties['rule_id']);
unset($properties['source_id']);
$all[] = (object) $properties;
}
return $all;
}
/**
* Whether we have a combined key (e.g. services on hosts)
*
@ -345,7 +385,7 @@ class SyncRule extends DbObject
public function getSyncProperties()
{
if (! $this->hasBeenLoadedFromDb()) {
return array();
return [];
}
if ($this->syncProperties === null) {