774 lines
25 KiB
PHP

<?php
namespace Icinga\Module\Director\Controllers;
use gipfl\Web\Widget\Hint;
use Icinga\Module\Director\Auth\Permission;
use Icinga\Module\Director\Exception\NestingError;
use Icinga\Module\Director\Forms\CustomPropertiesForm;
use Icinga\Module\Director\Integration\Icingadb\IcingadbBackend;
use Icinga\Module\Director\Integration\MonitoringModule\Monitoring;
use Icinga\Module\Director\Web\Table\ObjectsTableService;
use ipl\Html\Html;
use gipfl\IcingaWeb2\Link;
use gipfl\IcingaWeb2\Url;
use gipfl\IcingaWeb2\Widget\Tabs;
use Exception;
use Icinga\Module\Director\Db\AppliedServiceSetLoader;
use Icinga\Module\Director\DirectorObject\Lookup\ServiceFinder;
use Icinga\Module\Director\Forms\IcingaAddServiceForm;
use Icinga\Module\Director\Forms\IcingaServiceForm;
use Icinga\Module\Director\Forms\IcingaServiceSetForm;
use Icinga\Module\Director\Objects\IcingaHost;
use Icinga\Module\Director\Objects\IcingaService;
use Icinga\Module\Director\Objects\IcingaServiceSet;
use Icinga\Module\Director\Restriction\HostgroupRestriction;
use Icinga\Module\Director\Repository\IcingaTemplateRepository;
use Icinga\Module\Director\Web\Controller\ObjectController;
use Icinga\Module\Director\Web\SelfService;
use Icinga\Module\Director\Web\Table\IcingaHostAppliedServicesTable;
use Icinga\Module\Director\Web\Table\IcingaServiceSetServiceTable;
use ipl\Web\Widget\ButtonLink;
use PDO;
class HostController extends ObjectController
{
protected function checkDirectorPermissions()
{
$host = $this->getHostObject();
$auth = $this->Auth();
$backend = $this->backend();
if (
$this->isServiceAction()
&& $backend->canModifyService($host->getObjectName(), $this->getParam('service'))
) {
return;
}
if ($this->isServicesReadOnlyAction() && $auth->hasPermission($this->getServicesReadOnlyPermission())) {
return;
}
if ($auth->hasPermission(Permission::HOSTS)) { // faster
return;
}
if ($backend->canModifyHost($host->getObjectName())) {
return;
}
$this->assertPermission(Permission::HOSTS); // complain about default hosts permission
}
protected function isServicesReadOnlyAction()
{
return in_array($this->getRequest()->getActionName(), [
'servicesro',
'findservice',
'invalidservice',
]);
}
protected function isServiceAction()
{
return in_array($this->getRequest()->getActionName(), [
'servicesro',
'findservice',
'invalidservice',
'servicesetservice',
'appliedservice',
'inheritedservice',
]);
}
/**
* @return HostgroupRestriction
*/
protected function getHostgroupRestriction()
{
return new HostgroupRestriction($this->db(), $this->Auth());
}
public function editAction()
{
parent::editAction();
$this->addOptionalMonitoringLink();
}
public function variablesAction()
{
$this->assertPermission('director/admin');
$object = $this->requireObject();
$this->addTitle(
$this->translate('Custom Variables: %s'),
$object->getObjectName()
);
$objectProperties = $this->getObjectCustomProperties();
if ($this->object->isTemplate()) {
$this->actions()->add(
(new ButtonLink(
$this->translate('Add Property'),
Url::fromPath('director/host/add-property', ['uuid' => $this->getUuidFromUrl()])->getAbsoluteUrl(),
null,
['class' => 'control-button']
))->openInModal()
);
if ($objectProperties) {
$this->actions()->add(
(new ButtonLink(
$this->translate('Remove Property'),
Url::fromPath(
'director/host/remove-property',
['uuid' => $this->getUuidFromUrl()]
)->getAbsoluteUrl(),
null,
['class' => 'control-button']
))->openInModal()
);
}
}
$vars = json_decode(json_encode($this->object->getVars()), true);
if ($objectProperties) {
$form = (new CustomPropertiesForm($this->db(), $object, $objectProperties))
->populate($vars)
->on(CustomPropertiesForm::ON_SUCCESS, function () {
$this->redirectNow('__REFRESH__');
})
->handleRequest($this->getServerRequest());
try {
$this->content()->add($form);
} catch (NestingError $e) {
$this->content()->add(Hint::error($e->getMessage()));
}
}
$this->tabs()->activate('variables');
}
/**
* Get custom properties for the host.
*
* @return array
*/
protected function getObjectCustomProperties(): array
{
if ($this->object->uuid === null) {
return [];
}
$type = $this->object->getShortTableName();
$parents = $this->object->listAncestorIds();
$uuids = [];
$db = $this->db();
foreach ($parents as $parent) {
$uuids[] = IcingaHost::load($parent, $db)->get('uuid');
}
$uuids[] = $this->object->get('uuid');
$query = $db->getDbAdapter()
->select()
->from(
['dp' => 'director_property'],
[
'key_name' => 'dp.key_name',
'uuid' => 'dp.uuid',
'value_type' => 'dp.value_type',
'label' => 'dp.label',
'instantiable' => 'dp.instantiable',
'required' => 'iop.required',
'children' => 'COUNT(cdp.uuid)'
]
)
->join(['iop' => "icinga_$type" . '_property'], 'dp.uuid = iop.property_uuid', [])
->joinLeft(['cdp' => 'director_property'], 'cdp.parent_uuid = dp.uuid', [])
->where('iop.' . $type . '_uuid IN (?)', $uuids)
->group(['dp.uuid', 'dp.key_name', 'dp.value_type', 'dp.label', 'dp.instantiable', 'iop.required'])
->order('children')
->order('instantiable')
->order('key_name');
$result = [];
foreach ($db->getDbAdapter()->fetchAll($query, fetchMode: PDO::FETCH_ASSOC) as $row) {
$result[$row['key_name']] = $row;
}
return $result;
}
public function serviceAction()
{
$host = $this->getHostObject();
$this->addServicesHeader();
$this->addTitle($this->translate('Add Service to %s'), $host->getObjectName());
$this->content()->add(
IcingaAddServiceForm::load()
->setBranch($this->getBranch())
->setHost($host)
->setDb($this->db())
->handleRequest(),
);
}
public function servicesetAction()
{
$host = $this->getHostObject();
$this->addServicesHeader();
$this->addTitle($this->translate('Add Service Set to %s'), $host->getObjectName());
$this->content()->add(
IcingaServiceSetForm::load()
->setBranch($this->getBranch())
->setHost($host)
->setDb($this->db())
->handleRequest(),
);
}
protected function addServicesHeader()
{
$host = $this->getHostObject();
$hostname = $host->getObjectName();
$this->tabs()->activate('services');
$this->actions()->add(Link::create(
$this->translate('Add service'),
'director/host/service',
['name' => $hostname],
['class' => 'icon-plus'],
))->add(Link::create(
$this->translate('Add service set'),
'director/host/serviceset',
['name' => $hostname],
['class' => 'icon-plus'],
));
}
public function findserviceAction()
{
$auth = $this->Auth();
$host = $this->getHostObject();
$hostName = $host->getObjectName();
$serviceName = $this->params->get('service');
$info = ServiceFinder::find($host, $serviceName);
$backend = $this->backend();
if ($info && $auth->hasPermission(Permission::HOSTS)) {
$redirectUrl = $info->getUrl();
} elseif (
$info
&& (($backend instanceof Monitoring && $auth->hasPermission(Permission::MONITORING_HOSTS))
|| ($backend instanceof IcingadbBackend && $auth->hasPermission(Permission::ICINGADB_HOSTS))
)
&& $backend->canModifyService($hostName, $serviceName)
) {
$redirectUrl = $info->getUrl();
} elseif ($auth->hasPermission($this->getServicesReadOnlyPermission())) {
$redirectUrl = Url::fromPath('director/host/servicesro', [
'name' => $hostName,
'service' => $serviceName,
]);
} else {
$redirectUrl = Url::fromPath('director/host/invalidservice', [
'name' => $hostName,
'service' => $serviceName,
]);
}
$this->redirectNow($redirectUrl);
}
/**
* @throws \Icinga\Exception\NotFoundError
*/
public function invalidserviceAction()
{
if (! $this->showInfoForNonDirectorService()) {
$this->content()->add(Hint::error(sprintf(
$this->translate('No such service: %s'),
$this->params->get('service'),
)));
}
$this->servicesAction();
}
protected function showInfoForNonDirectorService()
{
try {
$api = $this->getApiIfAvailable();
if ($api) {
$name = $this->params->get('name') . '!' . $this->params->get('service');
$info = $api->getObject($name, 'Services');
if (isset($info->attrs->source_location)) {
$source = $info->attrs->source_location;
$this->content()->add(Hint::info(Html::sprintf(
'The configuration for this object has not been rendered by'
. ' Icinga Director. You can find it on line %s in %s.',
Html::tag('strong', null, $source->first_line),
Html::tag('strong', null, $source->path),
)));
}
}
return true;
} catch (Exception $e) {
return false;
}
}
/**
* @throws \Icinga\Exception\NotFoundError
*/
public function servicesAction()
{
$this->addServicesHeader();
$host = $this->getHostObject();
$this->addTitle($this->translate('Services: %s'), $host->getObjectName());
$branch = $this->getBranch();
$hostHasBeenCreatedInBranch = $branch->isBranch() && $host->get('id');
$content = $this->content();
$table = (new ObjectsTableService($this->db(), $this->Auth()))
->setHost($host)
->setBranch($branch)
->setTitle($this->translate('Individual Service objects'))
->removeQueryLimit();
if (count($table)) {
$content->add($table);
}
/** @var IcingaHost[] $parents */
$parents = IcingaTemplateRepository::instanceByObject($this->object)
->getTemplatesFor($this->object, true);
foreach ($parents as $parent) {
$table = (new ObjectsTableService($this->db(), $this->Auth()))
->setBranch($branch)
->setHost($parent)
->setInheritedBy($host)
->removeQueryLimit();
if (count($table)) {
$content->add(
$table->setTitle(sprintf(
$this->translate('Inherited from %s'),
$parent->getObjectName(),
)),
);
}
}
if (! $hostHasBeenCreatedInBranch) {
$this->addHostServiceSetTables($host);
}
foreach ($parents as $parent) {
$this->addHostServiceSetTables($parent, $host);
}
$appliedSets = AppliedServiceSetLoader::fetchForHost($host);
foreach ($appliedSets as $set) {
$title = sprintf($this->translate('%s (Applied Service set)'), $set->getObjectName());
$content->add(
IcingaServiceSetServiceTable::load($set)
// ->setHost($host)
->setBranch($branch)
->setAffectedHost($host)
->setTitle($title)
->removeQueryLimit(),
);
}
$table = IcingaHostAppliedServicesTable::load($host)
->setTitle($this->translate('Applied services'));
if (count($table)) {
$content->add($table);
}
}
/**
* Hint: this duplicates quite some logic from servicesAction. We might want
* to clean this up, but as soon as we store fully resolved Services this
* will be obsolete anyways
*
* @throws \Icinga\Exception\NotFoundError
* @throws \Icinga\Security\SecurityException
* @throws \Icinga\Exception\MissingParameterException
*/
public function servicesroAction()
{
$this->assertPermission($this->getServicesReadOnlyPermission());
$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());
$content = $this->content();
$table = (new ObjectsTableService($db, $this->Auth()))
->setHost($host)
->setBranch($branch)
->setReadonly()
->highlightService($service)
->setTitle($this->translate('Individual Service objects'));
if (count($table)) {
$content->add($table);
}
/** @var IcingaHost[] $parents */
$parents = IcingaTemplateRepository::instanceByObject($this->object)
->getTemplatesFor($this->object, true);
foreach ($parents as $parent) {
$table = (new ObjectsTableService($db, $this->Auth()))
->setReadonly()
->setBranch($branch)
->setHost($parent)
->highlightService($service)
->setInheritedBy($host);
if (count($table)) {
$content->add(
$table->setTitle(sprintf(
'Inherited from %s',
$parent->getObjectName(),
)),
);
}
}
$this->addHostServiceSetTables($host);
foreach ($parents as $parent) {
$this->addHostServiceSetTables($parent, $host, $service);
}
$appliedSets = AppliedServiceSetLoader::fetchForHost($host);
foreach ($appliedSets as $set) {
$title = sprintf($this->translate('%s (Applied Service set)'), $set->getObjectName());
$content->add(
IcingaServiceSetServiceTable::load($set)
// ->setHost($host)
->setBranch($branch)
->setAffectedHost($host)
->setReadonly()
->highlightService($service)
->setTitle($title),
);
}
$table = IcingaHostAppliedServicesTable::load($host)
->setReadonly()
->highlightService($service)
->setTitle($this->translate('Applied services'));
if (count($table)) {
$content->add($table);
}
}
/**
* @param IcingaHost $host
* @param IcingaHost|null $affectedHost
*/
protected function addHostServiceSetTables(IcingaHost $host, IcingaHost $affectedHost = null, $roService = null)
{
$db = $this->db();
if ($affectedHost === null) {
$affectedHost = $host;
}
if ($host->get('id') === null) {
return;
}
$query = $db->getDbAdapter()->select()
->from(
array('ss' => 'icinga_service_set'),
'ss.*',
)->join(
array('hsi' => 'icinga_service_set_inheritance'),
'hsi.parent_service_set_id = ss.id',
array(),
)->join(
array('hs' => 'icinga_service_set'),
'hs.id = hsi.service_set_id',
array(),
)->where('hs.host_id = ?', $host->get('id'));
$sets = IcingaServiceSet::loadAll($db, $query, 'object_name');
/** @var IcingaServiceSet $set*/
foreach ($sets as $name => $set) {
$title = sprintf($this->translate('%s (Service set)'), $name);
$table = IcingaServiceSetServiceTable::load($set)
->setHost($host)
->setBranch($this->getBranch())
->setAffectedHost($affectedHost)
->removeQueryLimit()
->setTitle($title);
if ($roService) {
$table->setReadonly()->highlightService($roService);
}
$this->content()->add($table);
}
}
/**
* @throws \Icinga\Exception\NotFoundError
*/
public function appliedserviceAction()
{
$db = $this->db();
$host = $this->getHostObject();
$serviceId = $this->params->get('service_id');
$parent = IcingaService::loadWithAutoIncId($serviceId, $db);
$serviceName = $parent->getObjectName();
$service = IcingaService::create([
'imports' => $parent,
'object_type' => 'apply',
'object_name' => $serviceName,
'host_id' => $host->get('id'),
'vars' => $host->getOverriddenServiceVars($serviceName),
], $db);
$this->addTitle(
$this->translate('Applied service: %s'),
$serviceName,
);
$this->content()->add(
IcingaServiceForm::load()
->setDb($db)
->setBranch($this->getBranch())
->setHost($host)
->setApplyGenerated($parent)
->setObject($service)
->handleRequest(),
);
$this->commonForServices();
}
/**
* @throws \Icinga\Exception\NotFoundError
*/
public function inheritedserviceAction()
{
$db = $this->db();
$host = $this->getHostObject();
$serviceName = $this->params->get('service');
$from = IcingaHost::load($this->params->get('inheritedFrom'), $this->db());
$parent = IcingaService::load([
'object_name' => $serviceName,
'host_id' => $from->get('id'),
], $this->db());
// TODO: we want to eventually show the host template name, doesn't work
// as template resolution would break.
// $parent->object_name = $from->object_name;
$service = IcingaService::create([
'object_type' => 'apply',
'object_name' => $serviceName,
'host_id' => $host->get('id'),
'imports' => [$parent],
'vars' => $host->getOverriddenServiceVars($serviceName),
], $db);
$this->addTitle($this->translate('Inherited service: %s'), $serviceName);
$form = IcingaServiceForm::load()
->setDb($db)
->setBranch($this->getBranch())
->setHost($host)
->setInheritedFrom($from->getObjectName())
->setObject($service)
->handleRequest();
$this->content()->add($form);
$this->commonForServices();
}
/**
* @throws \Icinga\Exception\NotFoundError
*/
public function removesetAction()
{
// TODO: clean this up, use POST
$db = $this->db()->getDbAdapter();
$query = $db->select()->from(
array('ss' => 'icinga_service_set'),
array('id' => 'ss.id'),
)->join(
array('si' => 'icinga_service_set_inheritance'),
'si.service_set_id = ss.id',
array(),
)->where(
'si.parent_service_set_id = ?',
$this->params->get('setId'),
)->where('ss.host_id = ?', $this->object->get('id'));
IcingaServiceSet::loadWithAutoIncId($db->fetchOne($query), $this->db())->delete();
$this->redirectNow(
Url::fromPath('director/host/services', array(
'name' => $this->object->getObjectName(),
)),
);
}
/**
* @throws \Icinga\Exception\NotFoundError
*/
public function servicesetserviceAction()
{
$db = $this->db();
$host = $this->getHostObject();
$serviceName = $this->params->get('service');
$setParams = [
'object_name' => $this->params->get('set'),
'host_id' => $host->get('id'),
];
$setTemplate = IcingaServiceSet::load($this->params->get('set'), $db);
if (IcingaServiceSet::exists($setParams, $db)) {
$set = IcingaServiceSet::load($setParams, $db);
} else {
$set = $setTemplate;
}
$service = IcingaService::load([
'object_name' => $serviceName,
'service_set_id' => $setTemplate->get('id'),
], $this->db());
$service = IcingaService::create([
'id' => $service->get('id'),
'object_type' => 'apply',
'object_name' => $serviceName,
'host_id' => $host->get('id'),
'imports' => $service->listImportNames(),
'vars' => $host->getOverriddenServiceVars($serviceName),
], $db);
// $set->copyVarsToService($service);
$this->addTitle(
$this->translate('%s on %s (from set: %s)'),
$serviceName,
$host->getObjectName(),
$set->getObjectName(),
);
$form = IcingaServiceForm::load()
->setDb($db)
->setBranch($this->getBranch())
->setHost($host)
->setServiceSet($set)
->setObject($service)
->handleRequest();
$this->tabs()->activate('services');
$this->content()->add($form);
$this->commonForServices();
}
protected function commonForServices()
{
$host = $this->object;
$this->actions()->add(Link::create(
$this->translate('back'),
'director/host/services',
['name' => $host->getObjectName()],
['class' => 'icon-left-big'],
));
$this->tabs()->activate('services');
}
/**
* @throws \Icinga\Exception\NotFoundError
*/
public function agentAction()
{
$selfService = new SelfService($this->getHostObject(), $this->api());
if ($os = $this->params->get('download')) {
$selfService->handleLegacyAgentDownloads($os);
return;
}
$selfService->renderTo($this);
$this->tabs()->activate('agent');
}
protected function addOptionalMonitoringLink()
{
$host = $this->object;
try {
$backend = $this->backend();
if (
$host instanceof IcingaHost
&& $host->isObject()
&& $backend->hasHost($host->getObjectName())
) {
$this->actions()->add(
Link::create(
$this->translate('Show'),
$backend->getHostUrl($host->getObjectName()),
null,
['class' => 'icon-globe critical', 'data-base-target' => '_next'],
),
);
// Intentionally placed here, show it only for deployed Hosts
$this->addOptionalInspectLink();
}
} catch (Exception $e) {
// Silently ignore errors in the monitoring module
}
}
protected function addOptionalInspectLink()
{
if (! $this->hasPermission(Permission::INSPECT)) {
return;
}
$this->actions()->add(Link::create(
$this->translate('Inspect'),
'director/inspect/object',
[
'type' => 'host',
'plural' => 'hosts',
'name' => $this->object->getObjectName(),
],
[
'class' => 'icon-zoom-in',
'data-base-target' => '_next',
],
));
}
/**
* @return ?IcingaHost
*/
protected function getHostObject()
{
if ($this->object !== null) {
assert($this->object instanceof IcingaHost);
}
return $this->object;
}
/**
* Get readOnly permission of the service for the current backend
*
* @return string permission
*/
protected function getServicesReadOnlyPermission(): string
{
return $this->backend() instanceof IcingadbBackend
? Permission::ICINGADB_SERVICES_RO
: Permission::MONITORING_SERVICES_RO;
}
}