mirror of
https://github.com/Icinga/icingaweb2-module-director.git
synced 2025-07-29 16:54:06 +02:00
Merge branch 'feature/serviceset-in-branches-refactored'
This commit is contained in:
commit
b6bfe913f8
@ -8,8 +8,10 @@ use gipfl\IcingaWeb2\Widget\NameValueTable;
|
||||
use Icinga\Module\Director\Data\Db\DbObjectStore;
|
||||
use Icinga\Module\Director\Data\Db\DbObjectTypeRegistry;
|
||||
use Icinga\Module\Director\Db\Branch\BranchActivity;
|
||||
use Icinga\Module\Director\Db\Branch\BranchStore;
|
||||
use Icinga\Module\Director\IcingaConfig\IcingaConfig;
|
||||
use Icinga\Module\Director\Objects\IcingaObject;
|
||||
use Icinga\Module\Director\Objects\SyncRule;
|
||||
use Icinga\Module\Director\PlainObjectRenderer;
|
||||
use Icinga\Module\Director\Web\Controller\ActionController;
|
||||
use Icinga\Module\Director\Web\Controller\BranchHelper;
|
||||
@ -24,6 +26,7 @@ class BranchController extends ActionController
|
||||
{
|
||||
parent::init();
|
||||
IcingaObject::setDbObjectStore(new DbObjectStore($this->db(), $this->getBranch()));
|
||||
SyncRule::setDbObjectStore(new DbObjectStore($this->db(), $this->getBranch()));
|
||||
}
|
||||
|
||||
protected function checkDirectorPermissions()
|
||||
@ -34,9 +37,17 @@ class BranchController extends ActionController
|
||||
{
|
||||
$this->assertPermission('director/showconfig');
|
||||
$ts = $this->params->getRequired('ts');
|
||||
$this->addSingleTab($this->translate('Activity'));
|
||||
$this->addTitle($this->translate('Branch Activity'));
|
||||
$activity = BranchActivity::load($ts, $this->db());
|
||||
$store = new BranchStore($this->db());
|
||||
$branch = $store->fetchBranchByUuid($activity->getBranchUuid());
|
||||
if ($branch->isSyncPreview()) {
|
||||
$this->addSingleTab($this->translate('Sync Preview'));
|
||||
$this->addTitle($this->translate('Expected Modification'));
|
||||
} else {
|
||||
$this->addSingleTab($this->translate('Activity'));
|
||||
$this->addTitle($this->translate('Branch Activity'));
|
||||
}
|
||||
|
||||
$this->content()->add($this->prepareActivityInfo($activity));
|
||||
$this->showActivity($activity);
|
||||
}
|
||||
|
@ -119,12 +119,10 @@ class HostController extends ObjectController
|
||||
$host = $this->getHostObject();
|
||||
$this->addServicesHeader();
|
||||
$this->addTitle($this->translate('Add Service Set to %s'), $host->getObjectName());
|
||||
if ($this->showNotInBranch($this->translate('Creating Service Sets'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->content()->add(
|
||||
IcingaServiceSetForm::load()
|
||||
->setBranch($this->getBranch())
|
||||
->setHost($host)
|
||||
->setDb($this->db())
|
||||
->handleRequest()
|
||||
@ -209,11 +207,11 @@ class HostController extends ObjectController
|
||||
$branch = $this->getBranch();
|
||||
$hostHasBeenCreatedInBranch = $branch->isBranch() && $host->get('id');
|
||||
$content = $this->content();
|
||||
$table = (new ObjectsTableService($this->db()))->setAuth($this->Auth())->setHost($host)
|
||||
$table = (new ObjectsTableService($this->db()))
|
||||
->setAuth($this->Auth())
|
||||
->setHost($host)
|
||||
->setBranch($branch)
|
||||
->setTitle($this->translate('Individual Service objects'));
|
||||
if ($branch->isBranch()) {
|
||||
$table->setBranchUuid($branch->getUuid());
|
||||
}
|
||||
|
||||
if (count($table)) {
|
||||
$content->add($table);
|
||||
@ -225,6 +223,7 @@ class HostController extends ObjectController
|
||||
foreach ($parents as $parent) {
|
||||
$table = (new ObjectsTableService($this->db()))
|
||||
->setAuth($this->Auth())
|
||||
->setBranch($branch)
|
||||
->setHost($parent)
|
||||
->setInheritedBy($host);
|
||||
if (count($table)) {
|
||||
@ -251,6 +250,7 @@ class HostController extends ObjectController
|
||||
$content->add(
|
||||
IcingaServiceSetServiceTable::load($set)
|
||||
// ->setHost($host)
|
||||
->setBranch($branch)
|
||||
->setAffectedHost($host)
|
||||
->setTitle($title)
|
||||
);
|
||||
@ -279,6 +279,7 @@ class HostController extends ObjectController
|
||||
$host = $this->getHostObject();
|
||||
$service = $this->params->getRequired('service');
|
||||
$db = $this->db();
|
||||
$branch = $this->getBranch();
|
||||
$this->controls()->setTabs(new Tabs());
|
||||
$this->addSingleTab($this->translate('Configuration (read-only)'));
|
||||
$this->addTitle($this->translate('Services on %s'), $host->getObjectName());
|
||||
@ -287,6 +288,7 @@ class HostController extends ObjectController
|
||||
$table = (new ObjectsTableService($db))
|
||||
->setAuth($this->Auth())
|
||||
->setHost($host)
|
||||
->setBranch($branch)
|
||||
->setReadonly()
|
||||
->highlightService($service)
|
||||
->setTitle($this->translate('Individual Service objects'));
|
||||
@ -301,6 +303,7 @@ class HostController extends ObjectController
|
||||
foreach ($parents as $parent) {
|
||||
$table = (new ObjectsTableService($db))
|
||||
->setReadonly()
|
||||
->setBranch($branch)
|
||||
->setHost($parent)
|
||||
->highlightService($service)
|
||||
->setInheritedBy($host);
|
||||
@ -326,6 +329,7 @@ class HostController extends ObjectController
|
||||
$content->add(
|
||||
IcingaServiceSetServiceTable::load($set)
|
||||
// ->setHost($host)
|
||||
->setBranch($branch)
|
||||
->setAffectedHost($host)
|
||||
->setReadonly()
|
||||
->highlightService($service)
|
||||
@ -377,6 +381,7 @@ class HostController extends ObjectController
|
||||
$title = sprintf($this->translate('%s (Service set)'), $name);
|
||||
$table = IcingaServiceSetServiceTable::load($set)
|
||||
->setHost($host)
|
||||
->setBranch($this->getBranch())
|
||||
->setAffectedHost($affectedHost)
|
||||
->setTitle($title);
|
||||
if ($roService) {
|
||||
@ -413,6 +418,7 @@ class HostController extends ObjectController
|
||||
$this->content()->add(
|
||||
IcingaServiceForm::load()
|
||||
->setDb($db)
|
||||
->setBranch($this->getBranch())
|
||||
->setHost($host)
|
||||
->setApplyGenerated($parent)
|
||||
->setObject($service)
|
||||
@ -453,6 +459,7 @@ class HostController extends ObjectController
|
||||
|
||||
$form = IcingaServiceForm::load()
|
||||
->setDb($db)
|
||||
->setBranch($this->getBranch())
|
||||
->setHost($host)
|
||||
->setInheritedFrom($from->getObjectName())
|
||||
->setObject($service)
|
||||
@ -530,6 +537,7 @@ class HostController extends ObjectController
|
||||
|
||||
$form = IcingaServiceForm::load()
|
||||
->setDb($db)
|
||||
->setBranch($this->getBranch())
|
||||
->setHost($host)
|
||||
->setServiceSet($set)
|
||||
->setObject($service)
|
||||
|
@ -41,6 +41,9 @@ class ServiceController extends ObjectController
|
||||
|
||||
public function init()
|
||||
{
|
||||
// This happens in parent::init() too, but is required to take place before the next two lines
|
||||
$this->enableStaticObjectLoader($this->getTableName());
|
||||
|
||||
// Hint: having Host and Set loaded first is important for UUID lookups with legacy URLs
|
||||
$this->host = $this->getOptionalRelatedObjectFromParams('host', 'host');
|
||||
$this->set = $this->getOptionalRelatedObjectFromParams('service_set', 'set');
|
||||
|
@ -71,7 +71,9 @@ class ServicesetController extends ObjectController
|
||||
['class' => 'icon-plus']
|
||||
));
|
||||
|
||||
IcingaServiceSetServiceTable::load($set)->renderTo($this);
|
||||
IcingaServiceSetServiceTable::load($set)
|
||||
->setBranch($this->getBranch())
|
||||
->renderTo($this);
|
||||
}
|
||||
|
||||
public function hostsAction()
|
||||
@ -98,17 +100,19 @@ class ServicesetController extends ObjectController
|
||||
|
||||
protected function addServiceSetTabs()
|
||||
{
|
||||
if ($this->branch->isBranch()) {
|
||||
return $this;
|
||||
}
|
||||
$hexUuid = $this->object->getUniqueId()->toString();
|
||||
$tabs = $this->tabs();
|
||||
$tabs->add('services', [
|
||||
'url' => 'director/serviceset/services',
|
||||
'urlParams' => ['uuid' => $this->object->getUniqueId()],
|
||||
'urlParams' => ['uuid' => $hexUuid],
|
||||
'label' => 'Services'
|
||||
])->add('hosts', [
|
||||
]);
|
||||
if ($this->branch->isBranch()) {
|
||||
return $this;
|
||||
}
|
||||
$tabs->add('hosts', [
|
||||
'url' => 'director/serviceset/hosts',
|
||||
'urlParams' => ['uuid' => $this->object->getUniqueId()],
|
||||
'urlParams' => ['uuid' => $hexUuid],
|
||||
'label' => 'Hosts'
|
||||
]);
|
||||
|
||||
|
@ -4,6 +4,15 @@ namespace Icinga\Module\Director\Controllers;
|
||||
|
||||
use gipfl\IcingaWeb2\Link;
|
||||
use gipfl\Web\Widget\Hint;
|
||||
use Icinga\Date\DateFormatter;
|
||||
use Icinga\Module\Director\Data\Db\DbObjectStore;
|
||||
use Icinga\Module\Director\Data\Db\DbObjectTypeRegistry;
|
||||
use Icinga\Module\Director\Db\Branch\Branch;
|
||||
use Icinga\Module\Director\Db\Branch\BranchStore;
|
||||
use Icinga\Module\Director\Db\Branch\BranchSupport;
|
||||
use Icinga\Module\Director\Web\Controller\BranchHelper;
|
||||
use Icinga\Module\Director\Web\Form\ClickHereForm;
|
||||
use Icinga\Module\Director\Web\Table\BranchActivityTable;
|
||||
use Icinga\Module\Director\Web\Widget\IcingaConfigDiff;
|
||||
use Icinga\Module\Director\Web\Widget\UnorderedList;
|
||||
use Icinga\Module\Director\Db\Cache\PrefetchCache;
|
||||
@ -26,11 +35,14 @@ use Icinga\Module\Director\Web\Table\SyncpropertyTable;
|
||||
use Icinga\Module\Director\Web\Table\SyncRunTable;
|
||||
use Icinga\Module\Director\Web\Tabs\SyncRuleTabs;
|
||||
use Icinga\Module\Director\Web\Widget\SyncRunDetails;
|
||||
use Icinga\Web\Notification;
|
||||
use ipl\Html\Form;
|
||||
use ipl\Html\Html;
|
||||
|
||||
class SyncruleController extends ActionController
|
||||
{
|
||||
use BranchHelper;
|
||||
|
||||
/**
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
@ -43,7 +55,18 @@ class SyncruleController extends ActionController
|
||||
$this->addTitle($this->translate('Sync rule: %s'), $ruleName);
|
||||
|
||||
$checkForm = SyncCheckForm::load()->setSyncRule($rule)->handleRequest();
|
||||
$runForm = SyncRunForm::load()->setSyncRule($rule)->handleRequest();
|
||||
$store = new DbObjectStore($this->db(), $this->getBranch());
|
||||
$runForm = new SyncRunForm($rule, $store);
|
||||
$runForm->on(SyncRunForm::ON_SUCCESS, function (SyncRunForm $form) {
|
||||
$message = $form->getSuccessMessage();
|
||||
if ($message === null) {
|
||||
Notification::error($this->translate('Synchronization failed'));
|
||||
} else {
|
||||
Notification::success($message);
|
||||
}
|
||||
$this->redirectNow($this->url());
|
||||
});
|
||||
$runForm->handleRequest($this->getServerRequest());
|
||||
|
||||
if ($lastRunId = $rule->getLastSyncRunId()) {
|
||||
$run = SyncRun::load($lastRunId, $this->db());
|
||||
@ -98,6 +121,15 @@ class SyncruleController extends ActionController
|
||||
}
|
||||
|
||||
$c->add($checkForm);
|
||||
if ($this->hasBranch()) {
|
||||
$objectType = $rule->get('object_type');
|
||||
$table = DbObjectTypeRegistry::tableNameByType($objectType);
|
||||
if (! BranchSupport::existsForTableName($table)) {
|
||||
$this->showNotInBranch(sprintf($this->translate("Synchronizing '%s'"), $objectType));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$c->add($runForm);
|
||||
|
||||
if ($run) {
|
||||
@ -142,31 +174,97 @@ class SyncruleController extends ActionController
|
||||
public function previewAction()
|
||||
{
|
||||
$rule = $this->requireSyncRule();
|
||||
// $rule->set('update_policy', 'replace');
|
||||
$branchSupport = BranchSupport::existsForSyncRule($rule);
|
||||
$branchStore = new BranchStore($this->db());
|
||||
$owner = $this->getAuth()->getUser()->getUsername();
|
||||
if ($branchSupport) {
|
||||
if ($this->getBranch()->isBranch()) {
|
||||
$tmpBranchName = sprintf(
|
||||
'%s/%s-%s',
|
||||
Branch::PREFIX_SYNC_PREVIEW,
|
||||
$this->getBranch()->getUuid()->toString(),
|
||||
$rule->get('id')
|
||||
);
|
||||
// We could keep changes for preview on branch too
|
||||
$branchStore->deleteByName($tmpBranchName);
|
||||
$tmpBranch = $branchStore->cloneBranchForSync($this->getBranch(), $tmpBranchName, $owner);
|
||||
$after = 1600000000; // a date in 2020, minus 10000000
|
||||
} else {
|
||||
$tmpBranchName = Branch::PREFIX_SYNC_PREVIEW . '/' . $rule->get('id');
|
||||
$tmpBranch = $branchStore->fetchOrCreateByName($tmpBranchName, $owner);
|
||||
$after = null;
|
||||
}
|
||||
$store = new DbObjectStore($this->db(), $tmpBranch);
|
||||
} else {
|
||||
$tmpBranch = $store = null;
|
||||
}
|
||||
|
||||
$this->tabs(new SyncRuleTabs($rule))->activate('preview');
|
||||
$this->addTitle('Sync Preview');
|
||||
$sync = new Sync($rule);
|
||||
try {
|
||||
$this->addTitle($this->translate('Sync Preview'));
|
||||
$sync = new Sync($rule, $store);
|
||||
$keepBranchPreview = false;
|
||||
if ($tmpBranch) {
|
||||
if ($lastTime = $branchStore->getLastActivityTime($tmpBranch, $after)) {
|
||||
if ((time() - $lastTime) > 100) {
|
||||
$branchStore->wipeBranch($tmpBranch, $after);
|
||||
} else {
|
||||
$here = (new ClickHereForm())->handleRequest($this->getServerRequest());
|
||||
if ($here->hasBeenClicked()) {
|
||||
$branchStore->wipeBranch($tmpBranch, $after);
|
||||
$this->redirectNow($this->url());
|
||||
} else {
|
||||
$keepBranchPreview = true;
|
||||
}
|
||||
$this->content()->add(Hint::info(Html::sprintf(
|
||||
$this->translate('This preview has been generated %s, please click %s to regenerate it'),
|
||||
DateFormatter::timeAgo($lastTime),
|
||||
$here
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$keepBranchPreview) {
|
||||
$modifications = $sync->getExpectedModifications();
|
||||
} catch (\Exception $e) {
|
||||
$this->content()->add(Hint::error($e->getMessage()));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($modifications)) {
|
||||
$this->content()->add(Hint::ok($this->translate(
|
||||
'This Sync Rule is in sync and would currently not apply any changes'
|
||||
)));
|
||||
if ($tmpBranch) {
|
||||
try {
|
||||
if (!$keepBranchPreview) {
|
||||
$sync->apply();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->content()->add(Hint::error($e->getMessage()));
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
$changes = new BranchActivityTable($tmpBranch->getUuid(), $this->db());
|
||||
$changes->disableObjectLink();
|
||||
if (count($changes) === 0) {
|
||||
$this->showInSync();
|
||||
}
|
||||
$changes->renderTo($this);
|
||||
} else {
|
||||
if (empty($modifications)) {
|
||||
$this->showInSync();
|
||||
return;
|
||||
}
|
||||
$this->showExpectedModificationSummary($modifications);
|
||||
}
|
||||
}
|
||||
|
||||
protected function showInSync()
|
||||
{
|
||||
$this->content()->add(Hint::ok($this->translate(
|
||||
'This Sync Rule is in sync and would currently not apply any changes'
|
||||
)));
|
||||
}
|
||||
|
||||
protected function showExpectedModificationSummary($modifications)
|
||||
{
|
||||
$create = [];
|
||||
$modify = [];
|
||||
$delete = [];
|
||||
$modifiedProperties = [];
|
||||
|
||||
/** @var IcingaObject $object */
|
||||
foreach ($modifications as $object) {
|
||||
if ($object->hasBeenLoadedFromDb()) {
|
||||
@ -416,9 +514,16 @@ class SyncruleController extends ActionController
|
||||
if (! $rule->hasSyncProperties()) {
|
||||
$this->addPropertyHint($rule);
|
||||
}
|
||||
if ($this->showNotInBranch($this->translate('Modifying Sync Rules'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
$this->addTitle($this->translate('Add sync rule'));
|
||||
$this->tabs(new SyncRuleTabs())->activate('add');
|
||||
if ($this->showNotInBranch($this->translate('Creating Sync Rules'))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$form->handleRequest();
|
||||
@ -451,6 +556,9 @@ class SyncruleController extends ActionController
|
||||
['class' => 'icon-paste']
|
||||
)
|
||||
);
|
||||
if ($this->showNotInBranch($this->translate('Cloning Sync Rules'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
$form = new CloneSyncRuleForm($rule);
|
||||
$this->content()->add($form);
|
||||
@ -499,6 +607,14 @@ class SyncruleController extends ActionController
|
||||
$ruleId = (int) $rule->get('id');
|
||||
|
||||
$form = SyncPropertyForm::load()->setDb($db);
|
||||
$this->tabs(new SyncRuleTabs($rule))->activate('property');
|
||||
$this->actions()->add(new Link(
|
||||
$this->translate('back'),
|
||||
'director/syncrule/property',
|
||||
['rule_id' => $ruleId],
|
||||
['class' => 'icon-left-big']
|
||||
));
|
||||
|
||||
if ($id = $this->params->get('id')) {
|
||||
$form->loadObject((int) $id);
|
||||
$this->addTitle(
|
||||
@ -506,24 +622,21 @@ class SyncruleController extends ActionController
|
||||
$form->getObject()->get('destination_field'),
|
||||
$rule->get('rule_name')
|
||||
);
|
||||
if ($this->showNotInBranch($this->translate('Modifying Sync Rules'))) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
$this->addTitle(
|
||||
$this->translate('Add sync property: %s'),
|
||||
$rule->get('rule_name')
|
||||
);
|
||||
if ($this->showNotInBranch($this->translate('Modifying Sync Rules'))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
$form->setRule($rule);
|
||||
$form->setSuccessUrl('director/syncrule/property', ['rule_id' => $ruleId]);
|
||||
|
||||
$this->actions()->add(new Link(
|
||||
$this->translate('back'),
|
||||
'director/syncrule/property',
|
||||
['rule_id' => $ruleId],
|
||||
['class' => 'icon-left-big']
|
||||
));
|
||||
|
||||
$this->content()->add($form->handleRequest());
|
||||
$this->tabs(new SyncRuleTabs($rule))->activate('property');
|
||||
SyncpropertyTable::create($rule)
|
||||
->handleSortPriorityActions($this->getRequest(), $this->getResponse())
|
||||
->renderTo($this);
|
||||
|
@ -128,6 +128,8 @@ class IcingaServiceForm extends DirectorObjectForm
|
||||
if (! $this->providesOverrides()) {
|
||||
return;
|
||||
}
|
||||
$hasDeleteButton = false;
|
||||
$isBranch = $this->branch && $this->branch->isBranch();
|
||||
|
||||
if ($this->hasBeenBlacklisted()) {
|
||||
$this->addHtml(
|
||||
@ -135,7 +137,10 @@ class IcingaServiceForm extends DirectorObjectForm
|
||||
['name' => 'HINT_blacklisted']
|
||||
);
|
||||
$group = null;
|
||||
$this->addDeleteButton($this->translate('Reactivate'));
|
||||
if (! $isBranch) {
|
||||
$this->addDeleteButton($this->translate('Reactivate'));
|
||||
$hasDeleteButton = true;
|
||||
}
|
||||
$this->setSubmitLabel(false);
|
||||
} else {
|
||||
$this->addOverrideHint();
|
||||
@ -164,10 +169,13 @@ class IcingaServiceForm extends DirectorObjectForm
|
||||
$this->setSubmitLabel(false);
|
||||
}
|
||||
|
||||
$this->addDeleteButton($this->translate('Deactivate'));
|
||||
if (! $isBranch) {
|
||||
$this->addDeleteButton($this->translate('Deactivate'));
|
||||
$hasDeleteButton = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $this->hasSubmitButton()) {
|
||||
if (! $this->hasSubmitButton() && $hasDeleteButton) {
|
||||
$this->addDisplayGroup([$this->deleteButtonName], 'buttons', [
|
||||
'decorators' => [
|
||||
'FormElements',
|
||||
@ -216,13 +224,19 @@ class IcingaServiceForm extends DirectorObjectForm
|
||||
|
||||
if ($this->blacklisted === null) {
|
||||
$host = $this->host;
|
||||
// Safety check, branches
|
||||
$hostId = $host->get('id');
|
||||
$service = $this->getServiceToBeBlacklisted();
|
||||
$serviceId = $service->get('id');
|
||||
if (! $hostId || ! $serviceId) {
|
||||
return false;
|
||||
}
|
||||
$db = $this->db->getDbAdapter();
|
||||
if ($this->providesOverrides()) {
|
||||
$this->blacklisted = 1 === (int)$db->fetchOne(
|
||||
$db->select()->from('icinga_host_service_blacklist', 'COUNT(*)')
|
||||
->where('host_id = ?', $host->get('id'))
|
||||
->where('service_id = ?', $service->get('id'))
|
||||
->where('host_id = ?', $hostId)
|
||||
->where('service_id = ?', $serviceId)
|
||||
);
|
||||
} else {
|
||||
$this->blacklisted = false;
|
||||
|
@ -69,7 +69,8 @@ class IcingaServiceSetForm extends DirectorObjectForm
|
||||
}
|
||||
|
||||
$this->addHidden('object_type', 'object');
|
||||
$this->addHidden('host_id', $this->host->id);
|
||||
$this->addHidden('host', $this->host->getObjectName());
|
||||
$this->groupMainProperties();
|
||||
}
|
||||
|
||||
public function setHost(IcingaHost $host)
|
||||
@ -77,6 +78,7 @@ class IcingaServiceSetForm extends DirectorObjectForm
|
||||
$this->host = $host;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function addSingleImportsElement()
|
||||
{
|
||||
$enum = $this->enumAllowedTemplates();
|
||||
|
@ -2,44 +2,66 @@
|
||||
|
||||
namespace Icinga\Module\Director\Forms;
|
||||
|
||||
use gipfl\Translation\TranslationHelper;
|
||||
use gipfl\Web\Form;
|
||||
use Icinga\Module\Director\Data\Db\DbObjectStore;
|
||||
use Icinga\Module\Director\Import\Sync;
|
||||
use Icinga\Module\Director\Objects\SyncRule;
|
||||
use Icinga\Module\Director\Web\Form\DirectorForm;
|
||||
|
||||
class SyncRunForm extends DirectorForm
|
||||
class SyncRunForm extends Form
|
||||
{
|
||||
use TranslationHelper;
|
||||
|
||||
protected $defaultDecoratorClass = null;
|
||||
|
||||
/** @var ?string */
|
||||
protected $successMessage = null;
|
||||
|
||||
/** @var SyncRule */
|
||||
protected $rule;
|
||||
|
||||
public function setSyncRule(SyncRule $rule)
|
||||
/** @var DbObjectStore */
|
||||
protected $store;
|
||||
|
||||
public function __construct(SyncRule $rule, DbObjectStore $store)
|
||||
{
|
||||
$this->rule = $rule;
|
||||
return $this;
|
||||
$this->store = $store;
|
||||
}
|
||||
|
||||
public function setup()
|
||||
public function assemble()
|
||||
{
|
||||
$this->submitLabel = false;
|
||||
$this->addElement('submit', 'submit', array(
|
||||
'label' => $this->translate('Trigger this Sync'),
|
||||
'decorators' => array('ViewHelper')
|
||||
));
|
||||
if ($this->store->getBranch()->isBranch()) {
|
||||
$label = sprintf($this->translate('Sync to Branch: %s'), $this->store->getBranch()->getName());
|
||||
} else {
|
||||
$label = $this->translate('Trigger this Sync');
|
||||
}
|
||||
$this->addElement('submit', 'submit', [
|
||||
'label' => $label,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getSuccessMessage()
|
||||
{
|
||||
return $this->successMessage;
|
||||
}
|
||||
|
||||
public function onSuccess()
|
||||
{
|
||||
$rule = $this->rule;
|
||||
$changed = $rule->applyChanges();
|
||||
|
||||
if ($changed) {
|
||||
$this->setSuccessMessage(
|
||||
$this->translate(('Source has successfully been synchronized'))
|
||||
);
|
||||
} elseif ($rule->get('sync_state') === 'in-sync') {
|
||||
$this->notifySuccess(
|
||||
$this->translate('Nothing changed, rule is in sync')
|
||||
);
|
||||
$sync = new Sync($this->rule, $this->store);
|
||||
if ($sync->hasModifications()) {
|
||||
if ($sync->apply()) {
|
||||
// and changed
|
||||
$this->successMessage = $this->translate(('Source has successfully been synchronized'));
|
||||
} else {
|
||||
$this->successMessage = $this->translate('Nothing changed, rule is in sync');
|
||||
}
|
||||
} else {
|
||||
$this->addError($this->translate('Synchronization failed'));
|
||||
// Used to be $rule->get('sync_state') === 'in-sync', $changed = $rule->applyChanges();
|
||||
$this->successMessage = $this->translate('Nothing to do, rule is in sync');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,10 @@ use Icinga\Module\Director\Db;
|
||||
use Icinga\Module\Director\Db\Branch\Branch;
|
||||
use Icinga\Module\Director\Db\Branch\BranchActivity;
|
||||
use Icinga\Module\Director\Db\Branch\BranchedObject;
|
||||
use Icinga\Module\Director\Db\Branch\MergeErrorDeleteMissingObject;
|
||||
use Icinga\Module\Director\Db\Branch\MergeErrorModificationForMissingObject;
|
||||
use Icinga\Module\Director\Db\Branch\MergeErrorRecreateOnMerge;
|
||||
use Icinga\Module\Director\Objects\IcingaObject;
|
||||
use Ramsey\Uuid\UuidInterface;
|
||||
|
||||
/**
|
||||
@ -48,6 +52,75 @@ class DbObjectStore
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $tableName
|
||||
* @param string $arrayIdx
|
||||
* @return DbObject[]|IcingaObject[]
|
||||
* @throws MergeErrorRecreateOnMerge
|
||||
* @throws MergeErrorDeleteMissingObject
|
||||
* @throws MergeErrorModificationForMissingObject
|
||||
*/
|
||||
public function loadAll($tableName, $arrayIdx = 'uuid')
|
||||
{
|
||||
$db = $this->connection->getDbAdapter();
|
||||
$class = DbObjectTypeRegistry::classByType($tableName);
|
||||
$query = $db->select()->from($tableName)->order('uuid');
|
||||
$result = [];
|
||||
foreach ($db->fetchAll($query) as $row) {
|
||||
$result[$row->uuid] = $class::create((array) $row, $this->connection);
|
||||
$result[$row->uuid]->setBeingLoadedFromDb();
|
||||
}
|
||||
if ($this->branch && $this->branch->isBranch()) {
|
||||
$query = $db->select()
|
||||
->from(BranchActivity::DB_TABLE)
|
||||
->where('branch_uuid = ?', $this->connection->quoteBinary($this->branch->getUuid()->getBytes()))
|
||||
->order('timestamp_ns ASC');
|
||||
$rows = $db->fetchAll($query);
|
||||
foreach ($rows as $row) {
|
||||
$activity = BranchActivity::fromDbRow($row);
|
||||
if ($activity->getObjectTable() !== $tableName) {
|
||||
continue;
|
||||
}
|
||||
$uuid = $activity->getObjectUuid();
|
||||
$binaryUuid = $uuid->getBytes();
|
||||
|
||||
$exists = isset($result[$binaryUuid]);
|
||||
if ($activity->isActionCreate()) {
|
||||
if ($exists) {
|
||||
throw new MergeErrorRecreateOnMerge($activity);
|
||||
} else {
|
||||
$new = $activity->createDbObject($this->connection);
|
||||
$new->setBeingLoadedFromDb();
|
||||
$result[$binaryUuid] = $new;
|
||||
}
|
||||
} elseif ($activity->isActionDelete()) {
|
||||
if ($exists) {
|
||||
unset($result[$binaryUuid]);
|
||||
} else {
|
||||
throw new MergeErrorDeleteMissingObject($activity);
|
||||
}
|
||||
} else {
|
||||
if ($exists) {
|
||||
$activity->applyToDbObject($result[$binaryUuid])->setBeingLoadedFromDb();
|
||||
} else {
|
||||
throw new MergeErrorModificationForMissingObject($activity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($arrayIdx === 'uuid') {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$indexedResult = [];
|
||||
foreach ($result as $object) {
|
||||
$indexedResult[$object->get($arrayIdx)] = $object;
|
||||
}
|
||||
|
||||
return $indexedResult;
|
||||
}
|
||||
|
||||
public function exists($tableName, UuidInterface $uuid)
|
||||
{
|
||||
return BranchedObject::exists($this->connection, $tableName, $uuid, $this->branch->getUuid());
|
||||
|
158
library/Director/Data/Db/ServiceSetQueryBuilder.php
Normal file
158
library/Director/Data/Db/ServiceSetQueryBuilder.php
Normal file
@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace Icinga\Module\Director\Data\Db;
|
||||
|
||||
use Icinga\Module\Director\Db;
|
||||
use Icinga\Module\Director\Db\Branch\BranchSupport;
|
||||
use Icinga\Module\Director\Db\DbSelectParenthesis;
|
||||
use Icinga\Module\Director\Db\DbUtil;
|
||||
use Icinga\Module\Director\Objects\IcingaService;
|
||||
use Icinga\Module\Director\Objects\IcingaServiceSet;
|
||||
use Icinga\Module\Director\Web\Table\TableWithBranchSupport;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Ramsey\Uuid\UuidInterface;
|
||||
|
||||
class ServiceSetQueryBuilder
|
||||
{
|
||||
use TableWithBranchSupport;
|
||||
|
||||
const TABLE = BranchSupport::TABLE_ICINGA_SERVICE;
|
||||
const BRANCHED_TABLE = BranchSupport::BRANCHED_TABLE_ICINGA_SERVICE;
|
||||
const SET_TABLE = BranchSupport::TABLE_ICINGA_SERVICE_SET;
|
||||
const BRANCHED_SET_TABLE = BranchSupport::BRANCHED_TABLE_ICINGA_SERVICE_SET;
|
||||
|
||||
/** @var Db */
|
||||
protected $connection;
|
||||
|
||||
/** @var \Zend_Db_Adapter_Abstract */
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* @param ?UuidInterface $uuid
|
||||
*/
|
||||
public function __construct(Db $connection, $uuid = null)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
$this->db = $connection->getDbAdapter();
|
||||
if ($uuid) {
|
||||
$this->setBranchUuid($uuid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Zend_Db_Select
|
||||
* @throws \Zend_Db_Select_Exception
|
||||
*/
|
||||
public function selectServicesForSet(IcingaServiceSet $set)
|
||||
{
|
||||
$db = $this->connection->getDbAdapter();
|
||||
if ($this->branchUuid) {
|
||||
$right = $this->selectRightBranchedServices($set)->columns($this->getRightBranchedColumns());
|
||||
$left = $this->selectLeftBranchedServices($set)->columns($this->getLeftBranchedColumns());
|
||||
$query = $this->db->select()->from(['u' => $db->select()->union([
|
||||
'l' => new DbSelectParenthesis($left),
|
||||
'r' => new DbSelectParenthesis($right),
|
||||
])]);
|
||||
$query->order('service_set');
|
||||
} else {
|
||||
$query = $this->selectServices($set)->columns($this->getColumns());
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function selectServices(IcingaServiceSet $set)
|
||||
{
|
||||
return $this->db
|
||||
->select()
|
||||
->from(['o' =>self::TABLE], [])
|
||||
->joinLeft(['os' => self::SET_TABLE], 'os.id = o.service_set_id', [])
|
||||
->where('os.uuid = ?', $this->connection->quoteBinary($set->getUniqueId()->getBytes()));
|
||||
}
|
||||
|
||||
protected function selectLeftBranchedServices(IcingaServiceSet $set)
|
||||
{
|
||||
return $this
|
||||
->selectServices($set)
|
||||
->joinLeft(
|
||||
['bo' => self::BRANCHED_TABLE],
|
||||
$this->db->quoteInto('bo.uuid = o.uuid AND bo.branch_uuid = ?', $this->getQuotedBranchUuid()),
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
protected function selectRightBranchedServices(IcingaServiceSet $set)
|
||||
{
|
||||
return $this->db
|
||||
->select()
|
||||
->from(['o' => self::TABLE], [])
|
||||
->joinRight(['bo' => self::BRANCHED_TABLE], 'bo.uuid = o.uuid', [])
|
||||
->where('bo.service_set = ?', $set->get('object_name'))
|
||||
->where('bo.branch_uuid = ?', $this->getQuotedBranchUuid());
|
||||
}
|
||||
|
||||
protected static function resetQueryProperties(\Zend_Db_Select $query)
|
||||
{
|
||||
// TODO: Keep existing UUID, becomes important when using this for other tables too (w/o UNION)
|
||||
// $columns = $query->getPart($query::COLUMNS);
|
||||
$query->reset($query::COLUMNS);
|
||||
$query->columns('uuid');
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function fetchServicesWithQuery(\Zend_Db_Select $query)
|
||||
{
|
||||
static::resetQueryProperties($query);
|
||||
$db = $this->connection->getDbAdapter();
|
||||
$uuids = $db->fetchCol($query);
|
||||
|
||||
$services = [];
|
||||
foreach ($uuids as $uuid) {
|
||||
$service = IcingaService::loadWithUniqueId(Uuid::fromBytes(DbUtil::binaryResult($uuid)), $this->connection);
|
||||
$service->set('service_set', null); // TODO: CHECK THIS!!!!
|
||||
|
||||
$services[$service->getObjectName()] = $service;
|
||||
}
|
||||
|
||||
return $services;
|
||||
}
|
||||
|
||||
protected function getColumns()
|
||||
{
|
||||
return [
|
||||
'uuid' => 'o.uuid', // MUST be first because of UNION column order, see branchifyColumns()
|
||||
'id' => 'o.id',
|
||||
'branch_uuid' => '(null)',
|
||||
'service_set' => 'os.object_name',
|
||||
'service' => 'o.object_name',
|
||||
'disabled' => 'o.disabled',
|
||||
'object_type' => 'o.object_type',
|
||||
'blacklisted' => "('n')",
|
||||
];
|
||||
}
|
||||
|
||||
protected function getLeftBranchedColumns()
|
||||
{
|
||||
$columns = $this->getColumns();
|
||||
$columns['branch_uuid'] = 'bo.branch_uuid';
|
||||
$columns['service_set'] = 'COALESCE(os.object_name, bo.service_set)';
|
||||
|
||||
return $this->branchifyColumns($columns);
|
||||
}
|
||||
|
||||
protected function getRightBranchedColumns()
|
||||
{
|
||||
$columns = $this->getColumns();
|
||||
$columns = $this->branchifyColumns($columns);
|
||||
$columns['branch_uuid'] = 'bo.branch_uuid';
|
||||
$columns['service_set'] = 'bo.service_set';
|
||||
$columns['id'] = '(NULL)';
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
protected function getQuotedBranchUuid()
|
||||
{
|
||||
return $this->connection->quoteBinary($this->branchUuid->getBytes());
|
||||
}
|
||||
}
|
@ -28,7 +28,7 @@ class AppliedServiceSetLoader
|
||||
/**
|
||||
* @return IcingaServiceSet[]
|
||||
*/
|
||||
public function fetchAppliedServiceSets()
|
||||
protected function fetchAppliedServiceSets()
|
||||
{
|
||||
$sets = array();
|
||||
$matcher = HostApplyMatches::prepare($this->host);
|
||||
|
@ -18,6 +18,8 @@ use stdClass;
|
||||
*/
|
||||
class Branch
|
||||
{
|
||||
const PREFIX_SYNC_PREVIEW = '/syncpreview';
|
||||
|
||||
/** @var UuidInterface|null */
|
||||
protected $branchUuid;
|
||||
|
||||
@ -186,4 +188,9 @@ class Branch
|
||||
{
|
||||
return $this->owner;
|
||||
}
|
||||
|
||||
public function isSyncPreview()
|
||||
{
|
||||
return (bool) preg_match('/^' . preg_quote(self::PREFIX_SYNC_PREVIEW, '/') . '\//', $this->getName());
|
||||
}
|
||||
}
|
||||
|
@ -121,6 +121,16 @@ class BranchActivity
|
||||
);
|
||||
}
|
||||
|
||||
public static function fixFakeTimestamp($timestampNs)
|
||||
{
|
||||
if ($timestampNs < 1600000000 * 1000000) {
|
||||
// fake TS for cloned branch in sync preview
|
||||
return (int) $timestampNs * 1000000;
|
||||
}
|
||||
|
||||
return $timestampNs;
|
||||
}
|
||||
|
||||
public function applyToDbObject(DbObject $object)
|
||||
{
|
||||
if (!$this->isActionModify()) {
|
||||
@ -260,7 +270,7 @@ class BranchActivity
|
||||
*/
|
||||
public function getTimestamp()
|
||||
{
|
||||
return (int) floor($this->timestampNs / 1000000);
|
||||
return (int) floor(BranchActivity::fixFakeTimestamp($this->timestampNs) / 1000000);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -31,16 +31,21 @@ class BranchModificationInspection
|
||||
public function describeBranch(UuidInterface $uuid)
|
||||
{
|
||||
$tables = [
|
||||
$this->translate('API Users') => 'branched_icinga_apiuser',
|
||||
$this->translate('Endpoints') => 'branched_icinga_endpoint',
|
||||
$this->translate('Zones') => 'branched_icinga_zone',
|
||||
$this->translate('Commands') => 'branched_icinga_command',
|
||||
$this->translate('Hosts') => 'branched_icinga_host',
|
||||
$this->translate('Hostgroups') => 'branched_icinga_hostgroup',
|
||||
$this->translate('Services') => 'branched_icinga_service',
|
||||
$this->translate('Servicegroups') => 'branched_icinga_servicegroup',
|
||||
$this->translate('Users') => 'branched_icinga_user',
|
||||
$this->translate('Timeperiods') => 'branched_icinga_timeperiod',
|
||||
$this->translate('API Users') => BranchSupport::BRANCHED_TABLE_ICINGA_APIUSER,
|
||||
$this->translate('Endpoints') => BranchSupport::BRANCHED_TABLE_ICINGA_COMMAND,
|
||||
$this->translate('Zones') => BranchSupport::BRANCHED_TABLE_ICINGA_DEPENDENCY,
|
||||
$this->translate('Commands') => BranchSupport::BRANCHED_TABLE_ICINGA_ENDPOINT,
|
||||
$this->translate('Hosts') => BranchSupport::BRANCHED_TABLE_ICINGA_HOST,
|
||||
$this->translate('Hostgroups') => BranchSupport::BRANCHED_TABLE_ICINGA_HOSTGROUP,
|
||||
$this->translate('Services') => BranchSupport::BRANCHED_TABLE_ICINGA_NOTIFICATION,
|
||||
$this->translate('Servicegroups') => BranchSupport::BRANCHED_TABLE_ICINGA_SCHEDULED_DOWNTIME,
|
||||
$this->translate('Servicesets') => BranchSupport::BRANCHED_TABLE_ICINGA_SERVICE_SET,
|
||||
$this->translate('Users') => BranchSupport::BRANCHED_TABLE_ICINGA_SERVICE,
|
||||
$this->translate('Usergroups') => BranchSupport::BRANCHED_TABLE_ICINGA_SERVICEGROUP,
|
||||
$this->translate('Timeperiods') => BranchSupport::BRANCHED_TABLE_ICINGA_TIMEPERIOD,
|
||||
$this->translate('Notifications') => BranchSupport::BRANCHED_TABLE_ICINGA_USER,
|
||||
$this->translate('Dependencies') => BranchSupport::BRANCHED_TABLE_ICINGA_USERGROUP,
|
||||
$this->translate('Scheduled Downtimes') => BranchSupport::BRANCHED_TABLE_ICINGA_ZONE,
|
||||
];
|
||||
|
||||
$parts = new HtmlDocument();
|
||||
|
@ -3,17 +3,19 @@
|
||||
namespace Icinga\Module\Director\Db\Branch;
|
||||
|
||||
use Icinga\Module\Director\Db;
|
||||
use Icinga\Module\Director\Db\DbUtil;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Ramsey\Uuid\UuidInterface;
|
||||
|
||||
class BranchStore
|
||||
{
|
||||
const TABLE = 'director_branch';
|
||||
const TABLE_ACTIVITY = 'director_branch_activity';
|
||||
|
||||
protected $connection;
|
||||
|
||||
protected $db;
|
||||
|
||||
protected $table = 'director_branch';
|
||||
|
||||
public function __construct(Db $connection)
|
||||
{
|
||||
$this->connection = $connection;
|
||||
@ -40,6 +42,67 @@ class BranchStore
|
||||
return $this->newFromDbResult($this->select()->where('b.branch_name = ?', $name));
|
||||
}
|
||||
|
||||
public function cloneBranchForSync(Branch $branch, $newName, $owner)
|
||||
{
|
||||
$this->runTransaction(function ($db) use ($branch, $newName, $owner) {
|
||||
$tables = BranchSupport::OBJECT_TABLES;
|
||||
$tables[] = self::TABLE_ACTIVITY;
|
||||
$newBranch = $this->createBranchByName($newName, $owner);
|
||||
$oldQuotedUuid = DbUtil::quoteBinaryCompat($branch->getUuid()->getBytes(), $db);
|
||||
$quotedUuid = DbUtil::quoteBinaryCompat($newBranch->getUuid()->getBytes(), $db);
|
||||
// $timestampNs = (int)floor(microtime(true) * 1000000);
|
||||
// Hint: would love to do SELECT *, $quotedUuid AS branch_uuid FROM $table INTO $table
|
||||
foreach ($tables as $table) {
|
||||
$rows = $db->fetchAll($db->select()->from($table)->where('branch_uuid = ?', $oldQuotedUuid));
|
||||
foreach ($rows as $row) {
|
||||
$modified = (array)$row;
|
||||
$modified['branch_uuid'] = $quotedUuid;
|
||||
if ($table === self::TABLE_ACTIVITY) {
|
||||
$modified['timestamp_ns'] = round($modified['timestamp_ns'] / 1000000);
|
||||
}
|
||||
$db->insert($table, $modified);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return $this->fetchBranchByName($newName);
|
||||
}
|
||||
|
||||
protected function runTransaction($callback)
|
||||
{
|
||||
$db = $this->db;
|
||||
$db->beginTransaction();
|
||||
try {
|
||||
$callback($db);
|
||||
$db->commit();
|
||||
} catch (\Exception $e) {
|
||||
try {
|
||||
$db->rollBack();
|
||||
} catch (\Exception $ignored) {
|
||||
//
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function wipeBranch(Branch $branch, $after = null)
|
||||
{
|
||||
$this->runTransaction(function ($db) use ($branch, $after) {
|
||||
$tables = BranchSupport::OBJECT_TABLES;
|
||||
$tables[] = self::TABLE_ACTIVITY;
|
||||
$quotedUuid = DbUtil::quoteBinaryCompat($branch->getUuid()->getBytes(), $db);
|
||||
$where = $db->quoteInto('branch_uuid = ?', $quotedUuid);
|
||||
foreach ($tables as $table) {
|
||||
if ($after && $table === self::TABLE_ACTIVITY) {
|
||||
$db->delete($table, $where . ' AND timestamp_ns > ' . (int) $after);
|
||||
} else {
|
||||
$db->delete($table, $where);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
protected function newFromDbResult($query)
|
||||
{
|
||||
if ($row = $this->db->fetchRow($query)) {
|
||||
@ -78,7 +141,7 @@ class BranchStore
|
||||
'ts_merge_request' => 'b.ts_merge_request',
|
||||
'cnt_activities' => 'COUNT(ba.timestamp_ns)',
|
||||
])->joinLeft(
|
||||
['ba' => 'director_branch_activity'],
|
||||
['ba' => self::TABLE_ACTIVITY],
|
||||
'b.uuid = ba.branch_uuid',
|
||||
[]
|
||||
)->group('b.uuid');
|
||||
@ -114,7 +177,7 @@ class BranchStore
|
||||
'description' => null,
|
||||
'ts_merge_request' => null,
|
||||
];
|
||||
$this->db->insert($this->table, $properties);
|
||||
$this->db->insert(self::TABLE, $properties);
|
||||
|
||||
if ($branch = static::fetchBranchByUuid($uuid)) {
|
||||
return $branch;
|
||||
@ -128,14 +191,49 @@ class BranchStore
|
||||
|
||||
public function deleteByUuid(UuidInterface $uuid)
|
||||
{
|
||||
return $this->db->delete($this->table, $this->db->quoteInto(
|
||||
return $this->db->delete(self::TABLE, $this->db->quoteInto(
|
||||
'uuid = ?',
|
||||
$this->connection->quoteBinary($uuid->getBytes())
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return int
|
||||
*/
|
||||
public function deleteByName($name)
|
||||
{
|
||||
return $this->db->delete(self::TABLE, $this->db->quoteInto(
|
||||
'branch_name = ?',
|
||||
$name
|
||||
));
|
||||
}
|
||||
|
||||
public function delete(Branch $branch)
|
||||
{
|
||||
return $this->deleteByUuid($branch->getUuid());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Branch $branch
|
||||
* @param ?int $after
|
||||
* @return float|null
|
||||
*/
|
||||
public function getLastActivityTime(Branch $branch, $after = null)
|
||||
{
|
||||
$db = $this->db;
|
||||
$query = $db->select()
|
||||
->from(self::TABLE_ACTIVITY, 'MAX(timestamp_ns)')
|
||||
->where('branch_uuid = ?', DbUtil::quoteBinaryCompat($branch->getUuid()->getBytes(), $db));
|
||||
if ($after) {
|
||||
$query->where('timestamp_ns > ?', (int) $after);
|
||||
}
|
||||
|
||||
$last = $db->fetchOne($query);
|
||||
if ($last) {
|
||||
return $last / 1000000;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
91
library/Director/Db/Branch/BranchSupport.php
Normal file
91
library/Director/Db/Branch/BranchSupport.php
Normal file
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace Icinga\Module\Director\Db\Branch;
|
||||
|
||||
use Icinga\Module\Director\Data\Db\DbObjectTypeRegistry;
|
||||
use Icinga\Module\Director\Objects\SyncRule;
|
||||
|
||||
class BranchSupport
|
||||
{
|
||||
const BRANCHED_TABLE_PREFIX = 'branched_';
|
||||
|
||||
const TABLE_ICINGA_APIUSER = 'icinga_apiuser';
|
||||
const TABLE_ICINGA_COMMAND = 'icinga_command';
|
||||
const TABLE_ICINGA_DEPENDENCY = 'icinga_dependency';
|
||||
const TABLE_ICINGA_ENDPOINT = 'icinga_endpoint';
|
||||
const TABLE_ICINGA_HOST = 'icinga_host';
|
||||
const TABLE_ICINGA_HOSTGROUP = 'icinga_hostgroup';
|
||||
const TABLE_ICINGA_NOTIFICATION = 'icinga_notification';
|
||||
const TABLE_ICINGA_SCHEDULED_DOWNTIME = 'icinga_scheduled_downtime';
|
||||
const TABLE_ICINGA_SERVICE = 'icinga_service';
|
||||
const TABLE_ICINGA_SERVICEGROUP = 'icinga_servicegroup';
|
||||
const TABLE_ICINGA_SERVICE_SET = 'icinga_service_set';
|
||||
const TABLE_ICINGA_TIMEPERIOD = 'icinga_timeperiod';
|
||||
const TABLE_ICINGA_USER = 'icinga_user';
|
||||
const TABLE_ICINGA_USERGROUP = 'icinga_usergroup';
|
||||
const TABLE_ICINGA_ZONE = 'icinga_zone';
|
||||
|
||||
const BRANCHED_TABLE_ICINGA_APIUSER = self::BRANCHED_TABLE_PREFIX. self::TABLE_ICINGA_APIUSER;
|
||||
const BRANCHED_TABLE_ICINGA_COMMAND = self::BRANCHED_TABLE_PREFIX. self::TABLE_ICINGA_COMMAND;
|
||||
const BRANCHED_TABLE_ICINGA_DEPENDENCY = self::BRANCHED_TABLE_PREFIX. self::TABLE_ICINGA_DEPENDENCY;
|
||||
const BRANCHED_TABLE_ICINGA_ENDPOINT = self::BRANCHED_TABLE_PREFIX. self::TABLE_ICINGA_ENDPOINT;
|
||||
const BRANCHED_TABLE_ICINGA_HOST = self::BRANCHED_TABLE_PREFIX. self::TABLE_ICINGA_HOST;
|
||||
const BRANCHED_TABLE_ICINGA_HOSTGROUP = self::BRANCHED_TABLE_PREFIX. self::TABLE_ICINGA_HOSTGROUP;
|
||||
const BRANCHED_TABLE_ICINGA_NOTIFICATION = self::BRANCHED_TABLE_PREFIX. self::TABLE_ICINGA_NOTIFICATION;
|
||||
const BRANCHED_TABLE_ICINGA_SCHEDULED_DOWNTIME = self::BRANCHED_TABLE_PREFIX. self::TABLE_ICINGA_SCHEDULED_DOWNTIME;
|
||||
const BRANCHED_TABLE_ICINGA_SERVICE = self::BRANCHED_TABLE_PREFIX. self::TABLE_ICINGA_SERVICE;
|
||||
const BRANCHED_TABLE_ICINGA_SERVICEGROUP = self::BRANCHED_TABLE_PREFIX. self::TABLE_ICINGA_SERVICEGROUP;
|
||||
const BRANCHED_TABLE_ICINGA_SERVICE_SET = self::BRANCHED_TABLE_PREFIX. self::TABLE_ICINGA_SERVICE_SET;
|
||||
const BRANCHED_TABLE_ICINGA_TIMEPERIOD = self::BRANCHED_TABLE_PREFIX. self::TABLE_ICINGA_TIMEPERIOD;
|
||||
const BRANCHED_TABLE_ICINGA_USER = self::BRANCHED_TABLE_PREFIX. self::TABLE_ICINGA_USER;
|
||||
const BRANCHED_TABLE_ICINGA_USERGROUP = self::BRANCHED_TABLE_PREFIX. self::TABLE_ICINGA_USERGROUP;
|
||||
const BRANCHED_TABLE_ICINGA_ZONE = self::BRANCHED_TABLE_PREFIX. self::TABLE_ICINGA_ZONE;
|
||||
|
||||
const OBJECT_TABLES = [
|
||||
self::TABLE_ICINGA_APIUSER,
|
||||
self::TABLE_ICINGA_COMMAND,
|
||||
self::TABLE_ICINGA_DEPENDENCY,
|
||||
self::TABLE_ICINGA_ENDPOINT,
|
||||
self::TABLE_ICINGA_HOST,
|
||||
self::TABLE_ICINGA_HOSTGROUP,
|
||||
self::TABLE_ICINGA_NOTIFICATION,
|
||||
self::TABLE_ICINGA_SCHEDULED_DOWNTIME,
|
||||
self::TABLE_ICINGA_SERVICE,
|
||||
self::TABLE_ICINGA_SERVICEGROUP,
|
||||
self::TABLE_ICINGA_SERVICE_SET,
|
||||
self::TABLE_ICINGA_TIMEPERIOD,
|
||||
self::TABLE_ICINGA_USER,
|
||||
self::TABLE_ICINGA_USERGROUP,
|
||||
self::TABLE_ICINGA_ZONE,
|
||||
];
|
||||
|
||||
const BRANCHED_TABLES = [
|
||||
self::BRANCHED_TABLE_ICINGA_APIUSER,
|
||||
self::BRANCHED_TABLE_ICINGA_COMMAND,
|
||||
self::BRANCHED_TABLE_ICINGA_DEPENDENCY,
|
||||
self::BRANCHED_TABLE_ICINGA_ENDPOINT,
|
||||
self::BRANCHED_TABLE_ICINGA_HOST,
|
||||
self::BRANCHED_TABLE_ICINGA_HOSTGROUP,
|
||||
self::BRANCHED_TABLE_ICINGA_NOTIFICATION,
|
||||
self::BRANCHED_TABLE_ICINGA_SCHEDULED_DOWNTIME,
|
||||
self::BRANCHED_TABLE_ICINGA_SERVICE,
|
||||
self::BRANCHED_TABLE_ICINGA_SERVICEGROUP,
|
||||
self::BRANCHED_TABLE_ICINGA_SERVICE_SET,
|
||||
self::BRANCHED_TABLE_ICINGA_TIMEPERIOD,
|
||||
self::BRANCHED_TABLE_ICINGA_USER,
|
||||
self::BRANCHED_TABLE_ICINGA_USERGROUP,
|
||||
self::BRANCHED_TABLE_ICINGA_ZONE,
|
||||
];
|
||||
|
||||
public static function existsForTableName($table)
|
||||
{
|
||||
return in_array($table, self::OBJECT_TABLES, true);
|
||||
}
|
||||
|
||||
public static function existsForSyncRule(SyncRule $rule)
|
||||
{
|
||||
return static::existsForTableName(
|
||||
DbObjectTypeRegistry::tableNameByType($rule->get('object_type'))
|
||||
);
|
||||
}
|
||||
}
|
@ -47,15 +47,21 @@ class UuidLookup
|
||||
|
||||
if ($uuid === null && $branch->isBranch()) {
|
||||
// TODO: use different tables?
|
||||
$query = $db->select()->from('branched_icinga_service', 'uuid')->where('object_type = ?', $objectType);
|
||||
$query = $db->select()
|
||||
->from('branched_icinga_service', 'uuid')
|
||||
->where('branch_uuid = ?', $connection->quoteBinary($branch->getUuid()->getBytes()));
|
||||
if ($objectType) {
|
||||
$query->where('object_type = ?', $objectType);
|
||||
}
|
||||
$query = self::addKeyToQuery($connection, $query, $key);
|
||||
if ($host) {
|
||||
// TODO: uuid?
|
||||
$query->add('host = ?', $host->getObjectName());
|
||||
$query->where('host = ?', $host->getObjectName());
|
||||
}
|
||||
if ($set) {
|
||||
$query->add('service_set = ?', $set->getObjectName());
|
||||
$query->where('service_set = ?', $set->getObjectName());
|
||||
}
|
||||
|
||||
$uuid = self::fetchOptionalUuid($connection, $query);
|
||||
}
|
||||
|
||||
@ -93,7 +99,12 @@ class UuidLookup
|
||||
$query = self::addKeyToQuery($connection, $db->select()->from($table, 'uuid'), $key);
|
||||
$uuid = self::fetchOptionalUuid($connection, $query);
|
||||
if ($uuid === null && $branch->isBranch()) {
|
||||
if (is_array($key) && isset($key['host_id'])) {
|
||||
$key['host'] = IcingaHost::load($key['host_id'], $connection)->getObjectName();
|
||||
unset($key['host_id']);
|
||||
}
|
||||
$query = self::addKeyToQuery($connection, $db->select()->from("branched_$table", 'uuid'), $key);
|
||||
$query->where('branch_uuid = ?', $connection->quoteBinary($branch->getUuid()->getBytes()));
|
||||
$uuid = self::fetchOptionalUuid($connection, $query);
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,8 @@ use Icinga\Application\Benchmark;
|
||||
use Icinga\Data\Filter\Filter;
|
||||
use Icinga\Module\Director\Application\MemoryLimit;
|
||||
use Icinga\Module\Director\Data\Db\DbObject;
|
||||
use Icinga\Module\Director\Data\Db\DbObjectStore;
|
||||
use Icinga\Module\Director\Data\Db\DbObjectTypeRegistry;
|
||||
use Icinga\Module\Director\Db;
|
||||
use Icinga\Module\Director\Db\Cache\PrefetchCache;
|
||||
use Icinga\Module\Director\Objects\HostGroupMembershipResolver;
|
||||
@ -46,6 +48,9 @@ class Sync
|
||||
/** @var bool Whether we already prepared your sync */
|
||||
protected $isPrepared = false;
|
||||
|
||||
/** @var bool Whether we applied strtolower() to existing object keys */
|
||||
protected $usedLowerCasedKeys = false;
|
||||
|
||||
protected $modify = [];
|
||||
|
||||
protected $remove = [];
|
||||
@ -76,13 +81,18 @@ class Sync
|
||||
/** @var HostGroupMembershipResolver|bool */
|
||||
protected $hostGroupMembershipResolver;
|
||||
|
||||
/** @var ?DbObjectStore */
|
||||
protected $store;
|
||||
|
||||
/**
|
||||
* @param SyncRule $rule
|
||||
* @param ?DbObjectStore $store
|
||||
*/
|
||||
public function __construct(SyncRule $rule)
|
||||
public function __construct(SyncRule $rule, DbObjectStore $store = null)
|
||||
{
|
||||
$this->rule = $rule;
|
||||
$this->db = $rule->getConnection();
|
||||
$this->store = $store;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -306,6 +316,9 @@ class Sync
|
||||
foreach ($rows as $row) {
|
||||
if ($combinedKey) {
|
||||
$key = SyncUtils::fillVariables($sourceKeyPattern, $row);
|
||||
if ($this->usedLowerCasedKeys) {
|
||||
$key = strtolower($key);
|
||||
}
|
||||
|
||||
if (array_key_exists($key, $this->imported[$sourceId])) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
@ -334,7 +347,11 @@ class Sync
|
||||
if ($combinedKey) {
|
||||
$this->imported[$sourceId][$key] = $row;
|
||||
} else {
|
||||
$this->imported[$sourceId][$row->$key] = $row;
|
||||
if ($this->usedLowerCasedKeys) {
|
||||
$this->imported[$sourceId][strtolower($row->$key)] = $row;
|
||||
} else {
|
||||
$this->imported[$sourceId][$row->$key] = $row;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -360,13 +377,13 @@ class Sync
|
||||
|
||||
if ($listId === null) {
|
||||
throw new InvalidArgumentException(
|
||||
'Cannot sync datalist entry without list_ist'
|
||||
'Cannot sync datalist entry without list_id'
|
||||
);
|
||||
}
|
||||
|
||||
$no = [];
|
||||
foreach ($this->objects as $k => $o) {
|
||||
if ((int) $o->get('list_id') !== (int) $listId) {
|
||||
if ((int) $o->get('list_id') !== $listId) {
|
||||
$no[] = $k;
|
||||
}
|
||||
}
|
||||
@ -384,15 +401,18 @@ class Sync
|
||||
Benchmark::measure('Begin loading existing objects');
|
||||
|
||||
$ruleObjectType = $this->rule->get('object_type');
|
||||
$useLowerCaseKeys = $ruleObjectType !== 'datalistEntry';
|
||||
// TODO: Make object_type (template, object...) and object_name mandatory?
|
||||
if ($this->rule->hasCombinedKey()) {
|
||||
$this->objects = [];
|
||||
$destinationKeyPattern = $this->rule->getDestinationKeyPattern();
|
||||
if ($this->store) {
|
||||
$objects = $this->store->loadAll(DbObjectTypeRegistry::tableNameByType($ruleObjectType));
|
||||
} else {
|
||||
$objects = IcingaObject::loadAllByType($ruleObjectType, $this->db);
|
||||
}
|
||||
|
||||
foreach (IcingaObject::loadAllByType(
|
||||
$ruleObjectType,
|
||||
$this->db
|
||||
) as $object) {
|
||||
foreach ($objects as $object) {
|
||||
if ($object instanceof IcingaService) {
|
||||
if (strstr($destinationKeyPattern, '${host}')
|
||||
&& $object->get('host_id') === null
|
||||
@ -409,6 +429,9 @@ class Sync
|
||||
$destinationKeyPattern,
|
||||
$object
|
||||
);
|
||||
if ($useLowerCaseKeys) {
|
||||
$key = strtolower($key);
|
||||
}
|
||||
|
||||
if (array_key_exists($key, $this->objects)) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
@ -421,12 +444,23 @@ class Sync
|
||||
$this->objects[$key] = $object;
|
||||
}
|
||||
} else {
|
||||
$this->objects = IcingaObject::loadAllByType(
|
||||
$ruleObjectType,
|
||||
$this->db
|
||||
);
|
||||
if ($this->store) {
|
||||
$objects = $this->store->loadAll(DbObjectTypeRegistry::tableNameByType($ruleObjectType), 'object_name');
|
||||
} else {
|
||||
$objects = IcingaObject::loadAllByType($ruleObjectType, $this->db);
|
||||
}
|
||||
|
||||
if ($useLowerCaseKeys) {
|
||||
$this->objects = [];
|
||||
foreach ($objects as $key => $object) {
|
||||
$this->objects[strtolower($key)] = $object;
|
||||
}
|
||||
} else {
|
||||
$this->objects = $objects;
|
||||
}
|
||||
}
|
||||
|
||||
$this->usedLowerCasedKeys = $useLowerCaseKeys;
|
||||
// TODO: should be obsoleted by a better "loadFiltered" method
|
||||
if ($ruleObjectType === 'datalistEntry') {
|
||||
$this->removeForeignListEntries();
|
||||
@ -453,6 +487,9 @@ class Sync
|
||||
foreach ($this->imported[$sourceId] as $key => $row) {
|
||||
// Workaround: $a["10"] = "val"; -> array_keys($a) = [(int) 10]
|
||||
$key = (string) $key;
|
||||
if ($this->usedLowerCasedKeys) {
|
||||
$key = strtolower($key);
|
||||
}
|
||||
if (! array_key_exists($key, $objects)) {
|
||||
// Safe default values for object_type and object_name
|
||||
if ($ruleObjectType === 'datalistEntry') {
|
||||
@ -759,7 +796,9 @@ class Sync
|
||||
$objects = $this->prepare();
|
||||
$db = $this->db;
|
||||
$dba = $db->getDbAdapter();
|
||||
$dba->beginTransaction();
|
||||
if (! $this->store) { // store has it's own transaction
|
||||
$dba->beginTransaction();
|
||||
}
|
||||
|
||||
$object = null;
|
||||
$updateOnly = $this->rule->get('update_policy') === 'update-only';
|
||||
@ -777,7 +816,11 @@ class Sync
|
||||
foreach ($objects as $object) {
|
||||
$this->setResolver($object);
|
||||
if (! $updateOnly && $object->shouldBeRemoved()) {
|
||||
$object->delete();
|
||||
if ($this->store) {
|
||||
$this->store->delete($object);
|
||||
} else {
|
||||
$object->delete();
|
||||
}
|
||||
$deleted++;
|
||||
continue;
|
||||
}
|
||||
@ -785,10 +828,18 @@ class Sync
|
||||
if ($object->hasBeenModified()) {
|
||||
$existing = $object->hasBeenLoadedFromDb();
|
||||
if ($existing) {
|
||||
$object->store($db);
|
||||
if ($this->store) {
|
||||
$this->store->store($object);
|
||||
} else {
|
||||
$object->store($db);
|
||||
}
|
||||
$modified++;
|
||||
} elseif ($allowCreate) {
|
||||
$object->store($db);
|
||||
if ($this->store) {
|
||||
$this->store->store($object);
|
||||
} else {
|
||||
$object->store($db);
|
||||
}
|
||||
$created++;
|
||||
}
|
||||
}
|
||||
@ -810,7 +861,9 @@ class Sync
|
||||
|
||||
$this->run->setProperties($runProperties)->store();
|
||||
$this->notifyResolvers();
|
||||
$dba->commit();
|
||||
if (! $this->store) {
|
||||
$dba->commit();
|
||||
}
|
||||
|
||||
// Store duration after commit, as the commit might take some time
|
||||
$this->run->set('duration_ms', (int) round(
|
||||
@ -819,13 +872,15 @@ class Sync
|
||||
|
||||
Benchmark::measure('Done applying objects');
|
||||
} catch (Exception $e) {
|
||||
$dba->rollBack();
|
||||
if (! $this->store) {
|
||||
$dba->rollBack();
|
||||
}
|
||||
|
||||
if ($object !== null && $object instanceof IcingaObject) {
|
||||
if ($object instanceof IcingaObject) {
|
||||
throw new IcingaException(
|
||||
'Exception while syncing %s %s: %s',
|
||||
get_class($object),
|
||||
$object->get('object_name'),
|
||||
$object->getObjectName(),
|
||||
$e->getMessage(),
|
||||
$e
|
||||
);
|
||||
@ -839,6 +894,9 @@ class Sync
|
||||
|
||||
protected function prepareCache()
|
||||
{
|
||||
if ($this->store) {
|
||||
return $this;
|
||||
}
|
||||
PrefetchCache::initialize($this->db);
|
||||
IcingaTemplateRepository::clear();
|
||||
|
||||
|
@ -565,7 +565,9 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
|
||||
} catch (NotFoundError $e) {
|
||||
// Hint: eventually a NotFoundError would be better
|
||||
throw new RuntimeException(sprintf(
|
||||
'Unable to load object referenced from %s "%s", %s',
|
||||
'Unable to load object (%s: %s) referenced from %s "%s", %s',
|
||||
$short,
|
||||
$this->unresolvedRelatedProperties[$name],
|
||||
$this->getShortTableName(),
|
||||
$this->getObjectName(),
|
||||
lcfirst($e->getMessage())
|
||||
|
@ -4,6 +4,7 @@ namespace Icinga\Module\Director\Objects;
|
||||
|
||||
use Exception;
|
||||
use Icinga\Data\Filter\Filter;
|
||||
use Icinga\Module\Director\Data\Db\ServiceSetQueryBuilder;
|
||||
use Icinga\Module\Director\Db;
|
||||
use Icinga\Module\Director\Db\Cache\PrefetchCache;
|
||||
use Icinga\Module\Director\Db\DbUtil;
|
||||
@ -94,7 +95,9 @@ class IcingaServiceSet extends IcingaObject implements ExportInterface
|
||||
if (empty($imports)) {
|
||||
return array();
|
||||
}
|
||||
return $this->getServiceObjectsForSet(array_shift($imports));
|
||||
$parent = array_shift($imports);
|
||||
assert($parent instanceof IcingaServiceSet);
|
||||
return $this->getServiceObjectsForSet($parent);
|
||||
} else {
|
||||
return $this->getServiceObjectsForSet($this);
|
||||
}
|
||||
@ -102,30 +105,20 @@ class IcingaServiceSet extends IcingaObject implements ExportInterface
|
||||
|
||||
/**
|
||||
* @param IcingaServiceSet $set
|
||||
* @return array
|
||||
* @return IcingaService[]
|
||||
* @throws \Icinga\Exception\NotFoundError
|
||||
*/
|
||||
protected function getServiceObjectsForSet(IcingaServiceSet $set)
|
||||
{
|
||||
if ($set->get('id') === null) {
|
||||
return array();
|
||||
}
|
||||
$connection = $this->getConnection();
|
||||
$db = $this->getDb();
|
||||
$uuids = $db->fetchCol(
|
||||
$db->select()->from('icinga_service', 'uuid')
|
||||
->where('service_set_id = ?', $set->get('id'))
|
||||
);
|
||||
|
||||
$services = array();
|
||||
foreach ($uuids as $uuid) {
|
||||
$service = IcingaService::loadWithUniqueId(Uuid::fromBytes(DbUtil::binaryResult($uuid)), $connection);
|
||||
$service->set('service_set', null);
|
||||
|
||||
$services[$service->getObjectName()] = $service;
|
||||
if (self::$dbObjectStore !== null) {
|
||||
$branchUuid = self::$dbObjectStore->getBranch()->getUuid();
|
||||
} else {
|
||||
$branchUuid = null;
|
||||
}
|
||||
|
||||
return $services;
|
||||
$builder = new ServiceSetQueryBuilder($connection, $branchUuid);
|
||||
return $builder->fetchServicesWithQuery($builder->selectServicesForSet($set));
|
||||
}
|
||||
|
||||
public function getUniqueIdentifier()
|
||||
@ -276,7 +269,9 @@ class IcingaServiceSet extends IcingaObject implements ExportInterface
|
||||
if ($hostId) {
|
||||
$deleteIds = [];
|
||||
foreach ($this->getServiceObjects() as $service) {
|
||||
$deleteIds[] = (int) $service->get('id');
|
||||
if ($idToDelete = $service->get('id')) {
|
||||
$deleteIds[] = (int) $idToDelete;
|
||||
}
|
||||
}
|
||||
|
||||
if (! empty($deleteIds)) {
|
||||
@ -483,11 +478,16 @@ class IcingaServiceSet extends IcingaObject implements ExportInterface
|
||||
public function getRenderingZone(IcingaConfig $config = null)
|
||||
{
|
||||
if ($this->get('host_id') === null) {
|
||||
return $this->connection->getDefaultGlobalZoneName();
|
||||
if ($hostname = $this->get('host')) {
|
||||
$host = IcingaHost::load($hostname, $this->getConnection());
|
||||
} else {
|
||||
return $this->connection->getDefaultGlobalZoneName();
|
||||
}
|
||||
} else {
|
||||
$host = $this->getRelatedObject('host', $this->get('host_id'));
|
||||
return $host->getRenderingZone($config);
|
||||
}
|
||||
|
||||
return $host->getRenderingZone($config);
|
||||
}
|
||||
|
||||
public function createWhere()
|
||||
@ -507,17 +507,13 @@ class IcingaServiceSet extends IcingaObject implements ExportInterface
|
||||
*/
|
||||
public function fetchServices()
|
||||
{
|
||||
$connection = $this->getConnection();
|
||||
$db = $connection->getDbAdapter();
|
||||
|
||||
/** @var IcingaService[] $services */
|
||||
$services = IcingaService::loadAll(
|
||||
$connection,
|
||||
$db->select()->from('icinga_service')
|
||||
->where('service_set_id = ?', $this->get('id'))
|
||||
);
|
||||
|
||||
return $services;
|
||||
if ($store = self::$dbObjectStore) {
|
||||
$uuid = $store->getBranch()->getUuid();
|
||||
} else {
|
||||
$uuid = null;
|
||||
}
|
||||
$builder = new ServiceSetQueryBuilder($this->getConnection(), $uuid);
|
||||
return $builder->fetchServicesWithQuery($builder->selectServicesForSet($this));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -557,7 +553,7 @@ class IcingaServiceSet extends IcingaObject implements ExportInterface
|
||||
|
||||
$name = $this->getObjectName();
|
||||
|
||||
if ($this->isObject() && $this->get('host_id') === null) {
|
||||
if ($this->isObject() && $this->get('host_id') === null && $this->get('host') === null) {
|
||||
throw new InvalidArgumentException(
|
||||
'A Service Set cannot be an object with no related host'
|
||||
);
|
||||
@ -585,7 +581,7 @@ class IcingaServiceSet extends IcingaObject implements ExportInterface
|
||||
$config->configFile(
|
||||
'failed-to-render'
|
||||
)->prepend(
|
||||
"/** Failed to render this object **/\n"
|
||||
"/** Failed to render this Service Set **/\n"
|
||||
. '/* ' . $e->getMessage() . ' */'
|
||||
);
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
namespace Icinga\Module\Director\PropertyModifier;
|
||||
|
||||
use Icinga\Module\Director\Hook\PropertyModifierHook;
|
||||
use Icinga\Module\Director\Web\Form\QuickForm;
|
||||
use function iconv;
|
||||
|
||||
class PropertyModifierFromLatin1 extends PropertyModifierHook
|
||||
{
|
||||
@ -18,6 +18,6 @@ class PropertyModifierFromLatin1 extends PropertyModifierHook
|
||||
return null;
|
||||
}
|
||||
|
||||
return utf8_encode($value);
|
||||
return iconv('ISO-8859-15', 'UTF-8', $value);
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ use Icinga\Module\Director\Data\Db\DbObjectStore;
|
||||
use Icinga\Module\Director\Data\Db\DbObjectTypeRegistry;
|
||||
use Icinga\Module\Director\Db\Branch\Branch;
|
||||
use Icinga\Module\Director\Db\Branch\BranchStore;
|
||||
use Icinga\Module\Director\Db\Branch\BranchSupport;
|
||||
use Icinga\Module\Director\Objects\IcingaObject;
|
||||
use Icinga\Module\Director\Web\Widget\NotInBranchedHint;
|
||||
|
||||
@ -17,23 +18,6 @@ trait BranchHelper
|
||||
/** @var BranchStore */
|
||||
protected $branchStore;
|
||||
|
||||
protected static $banchedTables = [
|
||||
'icinga_apiuser',
|
||||
'icinga_command',
|
||||
'icinga_dependency',
|
||||
'icinga_endpoint',
|
||||
'icinga_host',
|
||||
'icinga_hostgroup',
|
||||
'icinga_notification',
|
||||
'icinga_scheduled_downtime',
|
||||
'icinga_service',
|
||||
'icinga_servicegroup',
|
||||
'icinga_timeperiod',
|
||||
'icinga_user',
|
||||
'icinga_usergroup',
|
||||
'icinga_zone',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return false|\Ramsey\Uuid\UuidInterface
|
||||
*/
|
||||
@ -69,14 +53,9 @@ trait BranchHelper
|
||||
return $this->getBranchUuid() !== null;
|
||||
}
|
||||
|
||||
protected function tableHasBranchSupport($table)
|
||||
{
|
||||
return in_array($table, self::$banchedTables, true);
|
||||
}
|
||||
|
||||
protected function enableStaticObjectLoader($table)
|
||||
{
|
||||
if ($this->tableHasBranchSupport($table)) {
|
||||
if (BranchSupport::existsForTableName($table)) {
|
||||
IcingaObject::setDbObjectStore(new DbObjectStore($this->db(), $this->getBranch()));
|
||||
}
|
||||
}
|
||||
|
@ -140,13 +140,6 @@ abstract class ObjectController extends ActionController
|
||||
if ($oType = $this->params->get('type', 'object')) {
|
||||
$form->setPreferredObjectType($oType);
|
||||
}
|
||||
if ($this->getTableName() === 'icinga_service_set'
|
||||
&& $this->showNotInBranch($this->translate('Creating Service Sets'))
|
||||
) {
|
||||
$this->addTitle($this->translate('Create a new Service Set'));
|
||||
return;
|
||||
}
|
||||
|
||||
if ($oType === 'template') {
|
||||
if ($this->showNotInBranch($this->translate('Creating Templates'))) {
|
||||
$this->addTitle($this->translate('Create a new Template'));
|
||||
@ -157,6 +150,10 @@ abstract class ObjectController extends ActionController
|
||||
} else {
|
||||
$this->addObject();
|
||||
}
|
||||
$branch = $this->getBranch();
|
||||
if ($branch->isBranch() && ! $this->getRequest()->isApiRequest()) {
|
||||
$this->content()->add(new BranchedObjectHint($branch, $this->Auth()));
|
||||
}
|
||||
|
||||
$form->handleRequest();
|
||||
$this->content()->add($form);
|
||||
@ -170,7 +167,11 @@ abstract class ObjectController extends ActionController
|
||||
$object = $this->requireObject();
|
||||
$this->tabs()->activate('modify');
|
||||
$this->addObjectTitle();
|
||||
if ($object->isTemplate() && $this->showNotInBranch($this->translate('Modifying Templates'))) {
|
||||
// Hint: Service Sets are 'templates' (as long as not being assigned to a host
|
||||
if ($this->getTableName() !== 'icinga_service_set'
|
||||
&& $object->isTemplate()
|
||||
&& $this->showNotInBranch($this->translate('Modifying Templates'))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if ($object->isApplyRule() && $this->showNotInBranch($this->translate('Modifying Apply Rules'))) {
|
||||
|
@ -367,7 +367,9 @@ abstract class ObjectsController extends ActionController
|
||||
)
|
||||
);
|
||||
|
||||
ObjectSetTable::create($type, $this->db(), $this->getAuth())->renderTo($this);
|
||||
ObjectSetTable::create($type, $this->db(), $this->getAuth())
|
||||
->setBranch($this->getBranch())
|
||||
->renderTo($this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
31
library/Director/Web/Form/ClickHereForm.php
Normal file
31
library/Director/Web/Form/ClickHereForm.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Icinga\Module\Director\Web\Form;
|
||||
|
||||
use gipfl\Translation\TranslationHelper;
|
||||
use gipfl\Web\InlineForm;
|
||||
|
||||
class ClickHereForm extends InlineForm
|
||||
{
|
||||
use TranslationHelper;
|
||||
|
||||
protected $hasBeenClicked = false;
|
||||
|
||||
protected function assemble()
|
||||
{
|
||||
$this->addElement('submit', 'submit', [
|
||||
'label' => $this->translate('here'),
|
||||
'class' => 'link-button'
|
||||
]);
|
||||
}
|
||||
|
||||
public function hasBeenClicked()
|
||||
{
|
||||
return $this->hasBeenClicked;
|
||||
}
|
||||
|
||||
public function onSuccess()
|
||||
{
|
||||
$this->hasBeenClicked = true;
|
||||
}
|
||||
}
|
@ -23,6 +23,8 @@ class BranchActivityTable extends ZfQueryBasedTable
|
||||
/** @var LocalTimeFormat */
|
||||
protected $timeFormat;
|
||||
|
||||
protected $linkToObject = true;
|
||||
|
||||
public function __construct(UuidInterface $branchUuid, $db, UuidInterface $objectUuid = null)
|
||||
{
|
||||
$this->branchUuid = $branchUuid;
|
||||
@ -38,7 +40,7 @@ class BranchActivityTable extends ZfQueryBasedTable
|
||||
|
||||
public function renderRow($row)
|
||||
{
|
||||
$ts = (int) floor($row->timestamp_ns / 1000000);
|
||||
$ts = (int) floor(BranchActivity::fixFakeTimestamp($row->timestamp_ns) / 1000000);
|
||||
$this->splitByDay($ts);
|
||||
$activity = BranchActivity::fromDbRow($row);
|
||||
return $this::tr([
|
||||
@ -47,8 +49,17 @@ class BranchActivityTable extends ZfQueryBasedTable
|
||||
])->addAttributes(['class' => ['action-' . $activity->getAction(), 'branched']]);
|
||||
}
|
||||
|
||||
public function disableObjectLink()
|
||||
{
|
||||
$this->linkToObject = false;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function linkObject(BranchActivity $activity)
|
||||
{
|
||||
if (! $this->linkToObject) {
|
||||
return $activity->getObjectName();
|
||||
}
|
||||
// $type, UuidInterface $uuid
|
||||
// Later on replacing, service_set -> serviceset
|
||||
$type = preg_replace('/^icinga_/', '', $activity->getObjectTable());
|
||||
|
@ -2,18 +2,21 @@
|
||||
|
||||
namespace Icinga\Module\Director\Web\Table;
|
||||
|
||||
use Icinga\Module\Director\Data\Db\ServiceSetQueryBuilder;
|
||||
use Icinga\Module\Director\Db;
|
||||
use ipl\Html\BaseHtmlElement;
|
||||
use ipl\Html\Html;
|
||||
use Icinga\Module\Director\Forms\RemoveLinkForm;
|
||||
use Icinga\Module\Director\Objects\IcingaHost;
|
||||
use Icinga\Module\Director\Objects\IcingaServiceSet;
|
||||
use ipl\Html\HtmlElement;
|
||||
use gipfl\IcingaWeb2\Link;
|
||||
use gipfl\IcingaWeb2\Table\ZfQueryBasedTable;
|
||||
use gipfl\IcingaWeb2\Url;
|
||||
|
||||
class IcingaServiceSetServiceTable extends ZfQueryBasedTable
|
||||
{
|
||||
use TableWithBranchSupport;
|
||||
|
||||
/** @var IcingaServiceSet */
|
||||
protected $set;
|
||||
|
||||
@ -138,89 +141,48 @@ class IcingaServiceSetServiceTable extends ZfQueryBasedTable
|
||||
$tr = $this::row([
|
||||
$this->getServiceLink($row)
|
||||
]);
|
||||
|
||||
$classes = $this->getRowClasses($row);
|
||||
if ($row->disabled === 'y') {
|
||||
$tr->getAttributes()->add('class', 'disabled');
|
||||
$classes[] = 'disabled';
|
||||
}
|
||||
if ($row->blacklisted === 'y') {
|
||||
$tr->getAttributes()->add('class', 'strike-links');
|
||||
$classes[] = 'strike-links';
|
||||
}
|
||||
if (! empty($classes)) {
|
||||
$tr->getAttributes()->add('class', $classes);
|
||||
}
|
||||
|
||||
return $tr;
|
||||
}
|
||||
|
||||
protected function getRowClasses($row)
|
||||
{
|
||||
if ($row->branch_uuid !== null) {
|
||||
return ['branch_modified'];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function getTitle()
|
||||
{
|
||||
return $this->title ?: $this->translate('Servicename');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param HtmlElement $parent
|
||||
*/
|
||||
protected function renderTitleColumns()
|
||||
{
|
||||
if (! $this->host || ! $this->affectedHost) {
|
||||
return Html::tag('th', $this->getTitle());
|
||||
}
|
||||
|
||||
if (! $this->host) {
|
||||
$deleteLink = '';
|
||||
} elseif ($this->readonly) {
|
||||
$deleteLink = Html::tag('span', [
|
||||
'class' => 'icon-paste',
|
||||
'style' => 'float: right; font-weight: normal',
|
||||
], $this->host->getObjectName());
|
||||
if ($this->readonly) {
|
||||
$link = $this->createFakeRemoveLinkForReadonlyView();
|
||||
} elseif ($this->affectedHost->get('id') !== $this->host->get('id')) {
|
||||
$host = $this->host;
|
||||
$deleteLink = Link::create(
|
||||
$host->getObjectName(),
|
||||
'director/host/services',
|
||||
['name' => $host->getObjectName()],
|
||||
[
|
||||
'class' => 'icon-paste',
|
||||
'style' => 'float: right; font-weight: normal',
|
||||
'data-base-target' => '_next',
|
||||
'title' => sprintf(
|
||||
$this->translate('This set has been inherited from %s'),
|
||||
$host->getObjectName()
|
||||
)
|
||||
]
|
||||
);
|
||||
$link = $this->linkToHost($this->host);
|
||||
} else {
|
||||
$deleteLink = new RemoveLinkForm(
|
||||
$this->translate('Remove'),
|
||||
sprintf(
|
||||
$this->translate('Remove "%s" from this host'),
|
||||
$this->getTitle()
|
||||
),
|
||||
Url::fromPath('director/host/services', [
|
||||
'name' => $this->host->getObjectName()
|
||||
]),
|
||||
['title' => $this->getTitle()]
|
||||
);
|
||||
$deleteLink->runOnSuccess(function () {
|
||||
$conn = $this->set->getConnection();
|
||||
$db = $conn->getDbAdapter();
|
||||
$query = $db->select()->from(
|
||||
['ss' => 'icinga_service_set'],
|
||||
'ss.id'
|
||||
)->join(
|
||||
['ssih' => 'icinga_service_set_inheritance'],
|
||||
'ssih.service_set_id = ss.id',
|
||||
[]
|
||||
)->where(
|
||||
'ssih.parent_service_set_id = ?',
|
||||
$this->set->get('id')
|
||||
)->where('ss.host_id = ?', $this->host->get('id'));
|
||||
IcingaServiceSet::loadWithAutoIncId(
|
||||
$db->fetchOne($query),
|
||||
$conn
|
||||
)->delete();
|
||||
});
|
||||
$deleteLink->handleRequest();
|
||||
$link = $this->createRemoveLinkForm();
|
||||
}
|
||||
|
||||
return $this::th([$this->getTitle(), $deleteLink]);
|
||||
return $this::th([$this->getTitle(), $link]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -229,43 +191,60 @@ class IcingaServiceSetServiceTable extends ZfQueryBasedTable
|
||||
*/
|
||||
public function prepareQuery()
|
||||
{
|
||||
$db = $this->db();
|
||||
$query = $db->select()->from(
|
||||
['s' => 'icinga_service'],
|
||||
[
|
||||
'id' => 's.id',
|
||||
'uuid' => 's.uuid',
|
||||
'service_set_id' => 's.service_set_id',
|
||||
'host_id' => 'ss.host_id',
|
||||
'service_set' => 'ss.object_name',
|
||||
'service' => 's.object_name',
|
||||
'disabled' => 's.disabled',
|
||||
'object_type' => 's.object_type',
|
||||
]
|
||||
)->joinLeft(
|
||||
['ss' => 'icinga_service_set'],
|
||||
'ss.id = s.service_set_id',
|
||||
[]
|
||||
)->where(
|
||||
's.service_set_id = ?',
|
||||
$this->set->get('id')
|
||||
)->order('s.object_name');
|
||||
$connection = $this->connection();
|
||||
assert($connection instanceof Db);
|
||||
$builder = new ServiceSetQueryBuilder($connection, $this->branchUuid);
|
||||
return $builder->selectServicesForSet($this->set)->limit(100);
|
||||
}
|
||||
|
||||
if ($this->affectedHost) {
|
||||
$query->joinLeft(
|
||||
['hsb' => 'icinga_host_service_blacklist'],
|
||||
$db->quoteInto(
|
||||
's.id = hsb.service_id AND hsb.host_id = ?',
|
||||
$this->affectedHost->get('id')
|
||||
),
|
||||
[]
|
||||
)->columns([
|
||||
'blacklisted' => "CASE WHEN hsb.service_id IS NULL THEN 'n' ELSE 'y' END",
|
||||
]);
|
||||
} else {
|
||||
$query->columns(['blacklisted' => "('n')"]);
|
||||
}
|
||||
protected function createFakeRemoveLinkForReadonlyView()
|
||||
{
|
||||
return Html::tag('span', [
|
||||
'class' => 'icon-paste',
|
||||
'style' => 'float: right; font-weight: normal',
|
||||
], $this->host->getObjectName());
|
||||
}
|
||||
|
||||
return $query;
|
||||
protected function linkToHost(IcingaHost $host)
|
||||
{
|
||||
$hostname = $host->getObjectName();
|
||||
return Link::create($hostname, 'director/host/services', ['name' => $hostname], [
|
||||
'class' => 'icon-paste',
|
||||
'style' => 'float: right; font-weight: normal',
|
||||
'data-base-target' => '_next',
|
||||
'title' => sprintf(
|
||||
$this->translate('This set has been inherited from %s'),
|
||||
$hostname
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
protected function createRemoveLinkForm()
|
||||
{
|
||||
$deleteLink = new RemoveLinkForm(
|
||||
$this->translate('Remove'),
|
||||
sprintf(
|
||||
$this->translate('Remove "%s" from this host'),
|
||||
$this->getTitle()
|
||||
),
|
||||
Url::fromPath('director/host/services', [
|
||||
'name' => $this->host->getObjectName()
|
||||
]),
|
||||
['title' => $this->getTitle()]
|
||||
);
|
||||
$deleteLink->runOnSuccess(function () {
|
||||
$conn = $this->set->getConnection();
|
||||
$db = $conn->getDbAdapter();
|
||||
$query = $db->select()->from(['ss' => 'icinga_service_set'], 'ss.id')
|
||||
->join(['ssih' => 'icinga_service_set_inheritance'], 'ssih.service_set_id = ss.id', [])
|
||||
->where('ssih.parent_service_set_id = ?', $this->set->get('id'))
|
||||
->where('ss.host_id = ?', $this->host->get('id'));
|
||||
IcingaServiceSet::loadWithAutoIncId(
|
||||
$db->fetchOne($query),
|
||||
$conn
|
||||
)->delete();
|
||||
});
|
||||
$deleteLink->handleRequest();
|
||||
return $deleteLink;
|
||||
}
|
||||
}
|
||||
|
@ -7,11 +7,14 @@ use Icinga\Module\Director\Db;
|
||||
use gipfl\IcingaWeb2\Link;
|
||||
use gipfl\IcingaWeb2\Table\ZfQueryBasedTable;
|
||||
use gipfl\IcingaWeb2\Url;
|
||||
use Icinga\Module\Director\Db\DbSelectParenthesis;
|
||||
use Icinga\Module\Director\Restriction\FilterByNameRestriction;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
|
||||
class ObjectSetTable extends ZfQueryBasedTable
|
||||
{
|
||||
use TableWithBranchSupport;
|
||||
|
||||
protected $searchColumns = [
|
||||
'os.object_name',
|
||||
'os.description',
|
||||
@ -51,7 +54,8 @@ class ObjectSetTable extends ZfQueryBasedTable
|
||||
|
||||
$url = Url::fromPath("director/${type}set", $params);
|
||||
|
||||
return static::tr([
|
||||
$classes = $this->getRowClasses($row);
|
||||
$tr = static::tr([
|
||||
static::td([
|
||||
Link::create(sprintf(
|
||||
$this->translate('%s (%d members)'),
|
||||
@ -61,24 +65,44 @@ class ObjectSetTable extends ZfQueryBasedTable
|
||||
$row->description ? ': ' . $row->description : null
|
||||
])
|
||||
]);
|
||||
if (! empty($classes)) {
|
||||
$tr->getAttributes()->add('class', $classes);
|
||||
}
|
||||
|
||||
return $tr;
|
||||
}
|
||||
|
||||
protected function getRowClasses($row)
|
||||
{
|
||||
if ($row->branch_uuid !== null) {
|
||||
return ['branch_modified'];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function prepareQuery()
|
||||
{
|
||||
$type = $this->getType();
|
||||
|
||||
$table = "icinga_${type}_set";
|
||||
$columns = [
|
||||
'id' => 'os.id',
|
||||
'uuid' => 'os.uuid',
|
||||
'branch_uuid' => '(NULL)',
|
||||
'object_name' => 'os.object_name',
|
||||
'object_type' => 'os.object_type',
|
||||
'assign_filter' => 'os.assign_filter',
|
||||
'description' => 'os.description',
|
||||
'count_services' => 'COUNT(DISTINCT o.id)',
|
||||
'count_services' => 'COUNT(DISTINCT o.uuid)',
|
||||
];
|
||||
if ($this->branchUuid) {
|
||||
$columns['branch_uuid'] = 'bos.branch_uuid';
|
||||
$columns = $this->branchifyColumns($columns);
|
||||
$this->stripSearchColumnAliases();
|
||||
}
|
||||
|
||||
$query = $this->db()->select()->from(
|
||||
['os' => "icinga_${type}_set"],
|
||||
['os' => $table],
|
||||
$columns
|
||||
)->joinLeft(
|
||||
['o' => "icinga_${type}"],
|
||||
@ -92,22 +116,76 @@ class ObjectSetTable extends ZfQueryBasedTable
|
||||
"${type}_set"
|
||||
);
|
||||
$nameFilter->applyToQuery($query, 'os');
|
||||
// Disabled for now, check for correctness:
|
||||
// $query->joinLeft(
|
||||
// ['osi' => "icinga_${type}_set_inheritance"],
|
||||
// "osi.parent_${type}_set_id = os.id",
|
||||
// []
|
||||
// )->joinLeft(
|
||||
// ['oso' => "icinga_${type}_set"],
|
||||
// "oso.id = oso.${type}_set_id",
|
||||
// []
|
||||
// );
|
||||
// 'count_hosts' => 'COUNT(DISTINCT oso.id)',
|
||||
if ($this->branchUuid) {
|
||||
$right = clone($query);
|
||||
|
||||
$query
|
||||
->group('os.id')
|
||||
->where('os.object_type = ?', 'template')
|
||||
->order('os.object_name');
|
||||
/** @var Db $conn */
|
||||
$conn = $this->connection();
|
||||
$query->joinLeft(
|
||||
['bos' => "branched_$table"],
|
||||
// TODO: PgHexFunc
|
||||
$this->db()->quoteInto(
|
||||
'bos.uuid = os.uuid AND bos.branch_uuid = ?',
|
||||
$conn->quoteBinary($this->branchUuid->getBytes())
|
||||
),
|
||||
[]
|
||||
)->where("(bos.branch_deleted IS NULL OR bos.branch_deleted = 'n')");
|
||||
$right->joinRight(
|
||||
['bos' => "branched_$table"],
|
||||
'bos.uuid = os.uuid',
|
||||
[]
|
||||
)
|
||||
->where('os.uuid IS NULL')
|
||||
->where('bos.branch_uuid = ?', $conn->quoteBinary($this->branchUuid->getBytes()));
|
||||
$query->group('COALESCE(os.uuid, bos.uuid)');
|
||||
$right->group('COALESCE(os.uuid, bos.uuid)');
|
||||
if ($conn->isPgsql()) {
|
||||
// This is ugly, might want to modify the query - even a subselect looks better
|
||||
$query->group('bos.uuid')->group('os.uuid')->group('os.id')->group('bos.branch_uuid');
|
||||
$right->group('bos.uuid')->group('os.uuid')->group('os.id')->group('bos.branch_uuid');
|
||||
}
|
||||
|
||||
$query = $this->db()->select()->union([
|
||||
'l' => new DbSelectParenthesis($query),
|
||||
'r' => new DbSelectParenthesis($right),
|
||||
]);
|
||||
$query = $this->db()->select()->from(['u' => $query]);
|
||||
$query->order('object_name')->limit(100);
|
||||
|
||||
$query
|
||||
->group('uuid')
|
||||
->where('object_type = ?', 'template')
|
||||
->order('object_name');
|
||||
if ($conn->isPgsql()) {
|
||||
// BS. Drop count? Sub-select? Better query?
|
||||
$query
|
||||
->group('uuid')
|
||||
->group('id')
|
||||
->group('branch_uuid')
|
||||
->group('object_name')
|
||||
->group('object_type')
|
||||
->group('assign_filter')
|
||||
->group('description')
|
||||
->group('count_services');
|
||||
};
|
||||
} else {
|
||||
// Disabled for now, check for correctness:
|
||||
// $query->joinLeft(
|
||||
// ['osi' => "icinga_${type}_set_inheritance"],
|
||||
// "osi.parent_${type}_set_id = os.id",
|
||||
// []
|
||||
// )->joinLeft(
|
||||
// ['oso' => "icinga_${type}_set"],
|
||||
// "oso.id = oso.${type}_set_id",
|
||||
// []
|
||||
// );
|
||||
// 'count_hosts' => 'COUNT(DISTINCT oso.id)',
|
||||
|
||||
$query
|
||||
->group('os.uuid')
|
||||
->where('os.object_type = ?', 'template')
|
||||
->order('os.object_name');
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
@ -14,11 +14,12 @@ use gipfl\IcingaWeb2\Link;
|
||||
use gipfl\IcingaWeb2\Table\ZfQueryBasedTable;
|
||||
use gipfl\IcingaWeb2\Url;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Ramsey\Uuid\UuidInterface;
|
||||
use Zend_Db_Select as ZfSelect;
|
||||
|
||||
class ObjectsTable extends ZfQueryBasedTable
|
||||
{
|
||||
use TableWithBranchSupport;
|
||||
|
||||
/** @var ObjectRestriction[] */
|
||||
protected $objectRestrictions;
|
||||
|
||||
@ -37,9 +38,6 @@ class ObjectsTable extends ZfQueryBasedTable
|
||||
|
||||
protected $type;
|
||||
|
||||
/** @var UuidInterface|null */
|
||||
protected $branchUuid;
|
||||
|
||||
protected $baseObjectUrl;
|
||||
|
||||
/** @var IcingaObject */
|
||||
@ -112,13 +110,6 @@ class ObjectsTable extends ZfQueryBasedTable
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setBranchUuid(UuidInterface $uuid = null)
|
||||
{
|
||||
$this->branchUuid = $uuid;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getColumns()
|
||||
{
|
||||
return $this->columns;
|
||||
@ -256,36 +247,6 @@ class ObjectsTable extends ZfQueryBasedTable
|
||||
return $this->dummyObject;
|
||||
}
|
||||
|
||||
protected function branchifyColumns($columns)
|
||||
{
|
||||
$result = [
|
||||
'uuid' => 'COALESCE(o.uuid, bo.uuid)'
|
||||
];
|
||||
$ignore = ['o.id'];
|
||||
foreach ($columns as $alias => $column) {
|
||||
if (substr($column, 0, 2) === 'o.' && ! in_array($column, $ignore)) {
|
||||
// bo.column, o.column
|
||||
$column = "COALESCE(b$column, $column)";
|
||||
}
|
||||
|
||||
// Used in Service Tables:
|
||||
if ($column === 'h.object_name' && $alias = 'host') {
|
||||
$column = "COALESCE(bo.host, $column)";
|
||||
}
|
||||
|
||||
$result[$alias] = $column;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function stripSearchColumnAliases()
|
||||
{
|
||||
foreach ($this->searchColumns as &$column) {
|
||||
$column = preg_replace('/^[a-z]+\./', '', $column);
|
||||
}
|
||||
}
|
||||
|
||||
protected function prepareQuery()
|
||||
{
|
||||
$table = $this->getDummyObject()->getTableName();
|
||||
|
69
library/Director/Web/Table/TableWithBranchSupport.php
Normal file
69
library/Director/Web/Table/TableWithBranchSupport.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Icinga\Module\Director\Web\Table;
|
||||
|
||||
use Icinga\Module\Director\Db\Branch\Branch;
|
||||
use Ramsey\Uuid\UuidInterface;
|
||||
|
||||
trait TableWithBranchSupport
|
||||
{
|
||||
|
||||
/** @var UuidInterface|null */
|
||||
protected $branchUuid;
|
||||
|
||||
/**
|
||||
* Convenience method, only UUID is required
|
||||
*
|
||||
* @param Branch|null $branch
|
||||
* @return $this
|
||||
*/
|
||||
public function setBranch(Branch $branch = null)
|
||||
{
|
||||
if ($branch && $branch->isBranch()) {
|
||||
$this->setBranchUuid($branch->getUuid());
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setBranchUuid(UuidInterface $uuid = null)
|
||||
{
|
||||
$this->branchUuid = $uuid;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function branchifyColumns($columns)
|
||||
{
|
||||
$result = [
|
||||
'uuid' => 'COALESCE(o.uuid, bo.uuid)'
|
||||
];
|
||||
$ignore = ['o.id', 'os.id', 'o.service_set_id', 'os.host_id'];
|
||||
foreach ($columns as $alias => $column) {
|
||||
if (substr($column, 0, 2) === 'o.' && ! in_array($column, $ignore)) {
|
||||
// bo.column, o.column
|
||||
$column = "COALESCE(b$column, $column)";
|
||||
}
|
||||
if (substr($column, 0, 3) === 'os.' && ! in_array($column, $ignore)) {
|
||||
// bo.column, o.column
|
||||
$column = "COALESCE(b$column, $column)";
|
||||
}
|
||||
|
||||
// Used in Service Tables:
|
||||
if ($column === 'h.object_name' && $alias = 'host') {
|
||||
$column = "COALESCE(bo.host, $column)";
|
||||
}
|
||||
|
||||
$result[$alias] = $column;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function stripSearchColumnAliases()
|
||||
{
|
||||
foreach ($this->searchColumns as &$column) {
|
||||
$column = preg_replace('/^[a-z]+\./', '', $column);
|
||||
}
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@ class BranchedObjectHint extends HtmlDocument
|
||||
{
|
||||
use TranslationHelper;
|
||||
|
||||
public function __construct(Branch $branch, Auth $auth, BranchedObject $object)
|
||||
public function __construct(Branch $branch, Auth $auth, BranchedObject $object = null)
|
||||
{
|
||||
if (! $branch->isBranch()) {
|
||||
return;
|
||||
@ -29,6 +29,13 @@ class BranchedObjectHint extends HtmlDocument
|
||||
$label = $name;
|
||||
}
|
||||
$link = $hook->linkToBranch($branch, $auth, $label);
|
||||
if ($object === null) {
|
||||
$this->add(Hint::info(Html::sprintf($this->translate(
|
||||
'This object will be created in %s. It will not be part of any deployment'
|
||||
. ' unless being merged'
|
||||
), $link)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $object->hasBeenTouchedByBranch()) {
|
||||
$this->add(Hint::info(Html::sprintf($this->translate(
|
||||
|
26
schema/mysql-migrations/upgrade_180.sql
Normal file
26
schema/mysql-migrations/upgrade_180.sql
Normal file
@ -0,0 +1,26 @@
|
||||
CREATE TABLE branched_icinga_service_set (
|
||||
uuid VARBINARY(16) NOT NULL,
|
||||
branch_uuid VARBINARY(16) NOT NULL,
|
||||
branch_created ENUM('y', 'n') NOT NULL DEFAULT 'n',
|
||||
branch_deleted ENUM('y', 'n') NOT NULL DEFAULT 'n',
|
||||
|
||||
object_name VARCHAR(128) DEFAULT NULL,
|
||||
object_type ENUM('object', 'template', 'external_object') DEFAULT NULL,
|
||||
host VARCHAR(255) DEFAULT NULL,
|
||||
description TEXT DEFAULT NULL,
|
||||
assign_filter TEXT DEFAULT NULL,
|
||||
|
||||
imports TEXT DEFAULT NULL,
|
||||
set_null TEXT DEFAULT NULL,
|
||||
PRIMARY KEY (branch_uuid, uuid),
|
||||
INDEX search_object_name (object_name),
|
||||
CONSTRAINT icinga_service_set_branch
|
||||
FOREIGN KEY branch (branch_uuid)
|
||||
REFERENCES director_branch (uuid)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
INSERT INTO director_schema_migration
|
||||
(schema_version, migration_time)
|
||||
VALUES (180, NOW());
|
@ -2309,6 +2309,31 @@ CREATE TABLE branched_icinga_service (
|
||||
ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
CREATE TABLE branched_icinga_service_set (
|
||||
uuid VARBINARY(16) NOT NULL,
|
||||
branch_uuid VARBINARY(16) NOT NULL,
|
||||
branch_created ENUM('y', 'n') NOT NULL DEFAULT 'n',
|
||||
branch_deleted ENUM('y', 'n') NOT NULL DEFAULT 'n',
|
||||
|
||||
object_name VARCHAR(128) DEFAULT NULL,
|
||||
object_type ENUM('object', 'template', 'external_object') DEFAULT NULL,
|
||||
host VARCHAR(255) DEFAULT NULL,
|
||||
description TEXT DEFAULT NULL,
|
||||
assign_filter TEXT DEFAULT NULL,
|
||||
|
||||
|
||||
imports TEXT DEFAULT NULL,
|
||||
imports TEXT DEFAULT NULL,
|
||||
set_null TEXT DEFAULT NULL,
|
||||
PRIMARY KEY (branch_uuid, uuid),
|
||||
INDEX search_object_name (object_name),
|
||||
CONSTRAINT icinga_service_set_branch
|
||||
FOREIGN KEY branch (branch_uuid)
|
||||
REFERENCES director_branch (uuid)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
CREATE TABLE branched_icinga_notification (
|
||||
uuid VARBINARY(16) NOT NULL,
|
||||
branch_uuid VARBINARY(16) NOT NULL,
|
||||
@ -2415,4 +2440,4 @@ CREATE TABLE branched_icinga_dependency (
|
||||
|
||||
INSERT INTO director_schema_migration
|
||||
(schema_version, migration_time)
|
||||
VALUES (179, NOW());
|
||||
VALUES (180, NOW());
|
||||
|
32
schema/pgsql-migrations/upgrade_180.sql
Normal file
32
schema/pgsql-migrations/upgrade_180.sql
Normal file
@ -0,0 +1,32 @@
|
||||
CREATE TABLE branched_icinga_service_set (
|
||||
uuid bytea NOT NULL UNIQUE CHECK(LENGTH(uuid) = 16),
|
||||
branch_uuid bytea NOT NULL CHECK(LENGTH(branch_uuid) = 16),
|
||||
branch_created enum_boolean NOT NULL DEFAULT 'n',
|
||||
branch_deleted enum_boolean NOT NULL DEFAULT 'n',
|
||||
|
||||
object_name character varying(255) DEFAULT NULL,
|
||||
object_type enum_object_type_all DEFAULT NULL,
|
||||
disabled enum_boolean DEFAULT NULL,
|
||||
host character varying(255) DEFAULT NULL,
|
||||
description TEXT DEFAULT NULL,
|
||||
assign_filter text DEFAULT NULL,
|
||||
|
||||
|
||||
imports TEXT DEFAULT NULL,
|
||||
vars TEXT DEFAULT NULL,
|
||||
set_null TEXT DEFAULT NULL,
|
||||
PRIMARY KEY (branch_uuid, uuid),
|
||||
CONSTRAINT icinga_service_branch
|
||||
FOREIGN KEY (branch_uuid)
|
||||
REFERENCES director_branch (uuid)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX service_set_branch_object_name ON branched_icinga_service_set (branch_uuid, object_name);
|
||||
CREATE INDEX branched_service_set_search_object_name ON branched_icinga_service_set (object_name);
|
||||
|
||||
|
||||
INSERT INTO director_schema_migration
|
||||
(schema_version, migration_time)
|
||||
VALUES (180, NOW());
|
@ -2637,6 +2637,35 @@ CREATE INDEX branched_service_search_object_name ON branched_icinga_service (obj
|
||||
CREATE INDEX branched_service_search_display_name ON branched_icinga_service (display_name);
|
||||
|
||||
|
||||
CREATE TABLE branched_icinga_service_set (
|
||||
uuid bytea NOT NULL UNIQUE CHECK(LENGTH(uuid) = 16),
|
||||
branch_uuid bytea NOT NULL CHECK(LENGTH(branch_uuid) = 16),
|
||||
branch_created enum_boolean NOT NULL DEFAULT 'n',
|
||||
branch_deleted enum_boolean NOT NULL DEFAULT 'n',
|
||||
|
||||
object_name character varying(255) DEFAULT NULL,
|
||||
object_type enum_object_type_all DEFAULT NULL,
|
||||
disabled enum_boolean DEFAULT NULL,
|
||||
host character varying(255) DEFAULT NULL,
|
||||
description TEXT DEFAULT NULL,
|
||||
assign_filter text DEFAULT NULL,
|
||||
|
||||
|
||||
imports TEXT DEFAULT NULL,
|
||||
vars TEXT DEFAULT NULL,
|
||||
set_null TEXT DEFAULT NULL,
|
||||
PRIMARY KEY (branch_uuid, uuid),
|
||||
CONSTRAINT icinga_service_branch
|
||||
FOREIGN KEY (branch_uuid)
|
||||
REFERENCES director_branch (uuid)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX service_set_branch_object_name ON branched_icinga_service_set (branch_uuid, object_name);
|
||||
CREATE INDEX branched_service_set_search_object_name ON branched_icinga_service_set (object_name);
|
||||
|
||||
|
||||
CREATE TABLE branched_icinga_notification (
|
||||
uuid bytea NOT NULL UNIQUE CHECK(LENGTH(uuid) = 16),
|
||||
branch_uuid bytea NOT NULL CHECK(LENGTH(branch_uuid) = 16),
|
||||
@ -2749,4 +2778,4 @@ CREATE INDEX branched_dependency_search_object_name ON branched_icinga_dependenc
|
||||
|
||||
INSERT INTO director_schema_migration
|
||||
(schema_version, migration_time)
|
||||
VALUES (179, NOW());
|
||||
VALUES (180, NOW());
|
||||
|
Loading…
x
Reference in New Issue
Block a user