summaryrefslogtreecommitdiffstats
path: root/library/Director
diff options
context:
space:
mode:
Diffstat (limited to 'library/Director')
-rw-r--r--library/Director/Acl.php2
-rw-r--r--library/Director/Auth/MonitoringRestriction.php36
-rw-r--r--library/Director/Auth/Permission.php31
-rw-r--r--library/Director/Auth/Restriction.php17
-rw-r--r--library/Director/Cli/Command.php4
-rw-r--r--library/Director/Cli/ObjectCommand.php2
-rw-r--r--library/Director/Core/CoreApi.php4
-rw-r--r--library/Director/Core/LegacyDeploymentApi.php10
-rw-r--r--library/Director/Core/RestApiClient.php14
-rw-r--r--library/Director/Core/RestApiResponse.php2
-rw-r--r--library/Director/CustomVariable/CustomVariable.php2
-rw-r--r--library/Director/CustomVariable/CustomVariableBoolean.php2
-rw-r--r--library/Director/CustomVariable/CustomVariableNull.php2
-rw-r--r--library/Director/CustomVariable/CustomVariableNumber.php2
-rw-r--r--library/Director/CustomVariable/CustomVariables.php9
-rw-r--r--library/Director/Daemon/BackgroundDaemon.php2
-rw-r--r--library/Director/Dashboard/BranchesDashboard.php10
-rw-r--r--library/Director/Dashboard/Dashboard.php1
-rw-r--r--library/Director/Dashboard/Dashlet/ActivityLogDashlet.php4
-rw-r--r--library/Director/Dashboard/Dashlet/ApiUserObjectDashlet.php6
-rw-r--r--library/Director/Dashboard/Dashlet/BasketDashlet.php4
-rw-r--r--library/Director/Dashboard/Dashlet/CheckCommandsDashlet.php4
-rw-r--r--library/Director/Dashboard/Dashlet/ChoicesDashlet.php4
-rw-r--r--library/Director/Dashboard/Dashlet/CommandObjectDashlet.php6
-rw-r--r--library/Director/Dashboard/Dashlet/CustomvarDashlet.php4
-rw-r--r--library/Director/Dashboard/Dashlet/DatafieldCategoryDashlet.php4
-rw-r--r--library/Director/Dashboard/Dashlet/DatafieldDashlet.php4
-rw-r--r--library/Director/Dashboard/Dashlet/DatalistDashlet.php4
-rw-r--r--library/Director/Dashboard/Dashlet/DependencyObjectDashlet.php2
-rw-r--r--library/Director/Dashboard/Dashlet/DeploymentDashlet.php3
-rw-r--r--library/Director/Dashboard/Dashlet/EndpointObjectDashlet.php5
-rw-r--r--library/Director/Dashboard/Dashlet/HostGroupsDashlet.php4
-rw-r--r--library/Director/Dashboard/Dashlet/HostObjectDashlet.php6
-rw-r--r--library/Director/Dashboard/Dashlet/HostTemplatesDashlet.php4
-rw-r--r--library/Director/Dashboard/Dashlet/HostsDashlet.php4
-rw-r--r--library/Director/Dashboard/Dashlet/ImportSourceDashlet.php3
-rw-r--r--library/Director/Dashboard/Dashlet/InfrastructureDashlet.php4
-rw-r--r--library/Director/Dashboard/Dashlet/JobDashlet.php3
-rw-r--r--library/Director/Dashboard/Dashlet/KickstartDashlet.php4
-rw-r--r--library/Director/Dashboard/Dashlet/NotificationApplyDashlet.php4
-rw-r--r--library/Director/Dashboard/Dashlet/NotificationTemplateDashlet.php6
-rw-r--r--library/Director/Dashboard/Dashlet/NotificationsDashlet.php6
-rw-r--r--library/Director/Dashboard/Dashlet/ScheduledDowntimeApplyDashlet.php4
-rw-r--r--library/Director/Dashboard/Dashlet/SelfServiceDashlet.php4
-rw-r--r--library/Director/Dashboard/Dashlet/ServiceApplyRulesDashlet.php4
-rw-r--r--library/Director/Dashboard/Dashlet/ServiceGroupsDashlet.php4
-rw-r--r--library/Director/Dashboard/Dashlet/ServiceObjectDashlet.php8
-rw-r--r--library/Director/Dashboard/Dashlet/ServiceSetsDashlet.php4
-rw-r--r--library/Director/Dashboard/Dashlet/ServiceTemplatesDashlet.php4
-rw-r--r--library/Director/Dashboard/Dashlet/SettingsDashlet.php4
-rw-r--r--library/Director/Dashboard/Dashlet/SingleServicesDashlet.php4
-rw-r--r--library/Director/Dashboard/Dashlet/SyncDashlet.php3
-rw-r--r--library/Director/Dashboard/Dashlet/TimeperiodObjectDashlet.php5
-rw-r--r--library/Director/Dashboard/Dashlet/TimeperiodTemplateDashlet.php6
-rw-r--r--library/Director/Dashboard/Dashlet/TimeperiodsDashlet.php6
-rw-r--r--library/Director/Dashboard/Dashlet/UserGroupsDashlet.php4
-rw-r--r--library/Director/Dashboard/Dashlet/UserObjectDashlet.php5
-rw-r--r--library/Director/Dashboard/Dashlet/UserTemplateDashlet.php6
-rw-r--r--library/Director/Dashboard/Dashlet/UsersDashlet.php6
-rw-r--r--library/Director/Dashboard/Dashlet/ZoneObjectDashlet.php6
-rw-r--r--library/Director/Data/AssignFilterHelper.php2
-rw-r--r--library/Director/Data/Db/DbDataFormatter.php16
-rw-r--r--library/Director/Data/Db/DbObject.php73
-rw-r--r--library/Director/Data/Db/ServiceSetQueryBuilder.php2
-rw-r--r--library/Director/Data/Exporter.php45
-rw-r--r--library/Director/Data/FieldReferenceLoader.php4
-rw-r--r--library/Director/Data/HostServiceLoader.php12
-rw-r--r--library/Director/Data/ObjectImporter.php168
-rw-r--r--library/Director/Data/PropertyMangler.php4
-rw-r--r--library/Director/Db/Branch/Branch.php4
-rw-r--r--library/Director/Db/Branch/BranchActivity.php8
-rw-r--r--library/Director/Db/Branch/BranchStore.php17
-rw-r--r--library/Director/Db/Branch/PreferredBranchSupport.php10
-rw-r--r--library/Director/Db/Branch/UuidLookup.php26
-rw-r--r--library/Director/Db/Cache/CustomVariableCache.php5
-rw-r--r--library/Director/Db/IcingaObjectFilterHelper.php58
-rw-r--r--library/Director/Db/Migrations.php2
-rw-r--r--library/Director/DirectorObject/Automation/Basket.php70
-rw-r--r--library/Director/DirectorObject/Automation/BasketDiff.php121
-rw-r--r--library/Director/DirectorObject/Automation/BasketSnapshot.php101
-rw-r--r--library/Director/DirectorObject/Automation/BasketSnapshotFieldResolver.php43
-rw-r--r--library/Director/DirectorObject/Automation/CompareBasketObject.php8
-rw-r--r--library/Director/DirectorObject/Automation/ExportInterface.php10
-rw-r--r--library/Director/DirectorObject/Automation/ImportExport.php7
-rw-r--r--library/Director/DirectorObject/Lookup/ServiceFinder.php29
-rw-r--r--library/Director/DirectorObject/ObjectPurgeHelper.php4
-rw-r--r--library/Director/Field/FormFieldSuggestion.php167
-rw-r--r--library/Director/Filter/CidrExpression.php89
-rw-r--r--library/Director/Filter/FilterEnrichment.php29
-rw-r--r--library/Director/Hook/BranchSupportHook.php2
-rw-r--r--library/Director/IcingaConfig/AgentWizard.php39
-rw-r--r--library/Director/IcingaConfig/AssignRenderer.php11
-rw-r--r--library/Director/IcingaConfig/ExtensibleSet.php2
-rw-r--r--library/Director/IcingaConfig/IcingaConfig.php31
-rw-r--r--library/Director/IcingaConfig/IcingaConfigHelper.php2
-rw-r--r--library/Director/Import/ImportSourceCoreApi.php2
-rw-r--r--library/Director/Import/ImportSourceLdap.php2
-rw-r--r--library/Director/Import/ImportSourceRestApi.php1
-rw-r--r--library/Director/Import/Sync.php120
-rw-r--r--library/Director/Import/SyncUtils.php2
-rw-r--r--library/Director/Integration/BackendInterface.php55
-rw-r--r--library/Director/Integration/Icingadb/IcingadbBackend.php127
-rw-r--r--library/Director/Integration/MonitoringModule/Monitoring.php200
-rw-r--r--library/Director/Monitoring.php149
-rw-r--r--library/Director/Objects/DirectorActivityLog.php15
-rw-r--r--library/Director/Objects/DirectorDatafield.php101
-rw-r--r--library/Director/Objects/DirectorDatalist.php118
-rw-r--r--library/Director/Objects/DirectorDatalistEntry.php2
-rw-r--r--library/Director/Objects/DirectorDeploymentLog.php6
-rw-r--r--library/Director/Objects/DirectorJob.php87
-rw-r--r--library/Director/Objects/GroupMembershipResolver.php32
-rw-r--r--library/Director/Objects/IcingaArguments.php8
-rw-r--r--library/Director/Objects/IcingaCommand.php91
-rw-r--r--library/Director/Objects/IcingaDependency.php90
-rw-r--r--library/Director/Objects/IcingaHost.php85
-rw-r--r--library/Director/Objects/IcingaHostGroup.php8
-rw-r--r--library/Director/Objects/IcingaNotification.php125
-rw-r--r--library/Director/Objects/IcingaObject.php94
-rw-r--r--library/Director/Objects/IcingaObjectGroup.php64
-rw-r--r--library/Director/Objects/IcingaObjectGroups.php6
-rw-r--r--library/Director/Objects/IcingaObjectLegacyAssignments.php2
-rw-r--r--library/Director/Objects/IcingaObjectMultiRelations.php2
-rw-r--r--library/Director/Objects/IcingaRelatedObject.php2
-rw-r--r--library/Director/Objects/IcingaService.php107
-rw-r--r--library/Director/Objects/IcingaServiceGroup.php8
-rw-r--r--library/Director/Objects/IcingaServiceSet.php280
-rw-r--r--library/Director/Objects/IcingaTemplateChoice.php63
-rw-r--r--library/Director/Objects/IcingaTemplateResolver.php1
-rw-r--r--library/Director/Objects/IcingaTimePeriod.php44
-rw-r--r--library/Director/Objects/IcingaUser.php37
-rw-r--r--library/Director/Objects/ImportExportHelper.php68
-rw-r--r--library/Director/Objects/ImportRowModifier.php15
-rw-r--r--library/Director/Objects/ImportSource.php79
-rw-r--r--library/Director/Objects/ObjectApplyMatches.php2
-rw-r--r--library/Director/Objects/SyncRule.php70
-rw-r--r--library/Director/PlainObjectRenderer.php2
-rw-r--r--library/Director/PropertyModifier/PropertyModifierArrayFilter.php2
-rw-r--r--library/Director/PropertyModifier/PropertyModifierExtractFromDN.php2
-rw-r--r--library/Director/PropertyModifier/PropertyModifierGetHostByName.php3
-rw-r--r--library/Director/PropertyModifier/PropertyModifierJsonDecode.php25
-rw-r--r--library/Director/PropertyModifier/PropertyModifierMap.php32
-rw-r--r--library/Director/PropertyModifier/PropertyModifierRegexReplace.php40
-rw-r--r--library/Director/PropertyModifier/PropertyModifierRejectOrSelect.php2
-rw-r--r--library/Director/PropertyModifier/PropertyModifierSplit.php2
-rw-r--r--library/Director/ProvidedHook/IcingaDbCubeLinks.php13
-rw-r--r--library/Director/ProvidedHook/Icingadb/HostActions.php78
-rw-r--r--library/Director/ProvidedHook/Icingadb/IcingadbSupport.php10
-rw-r--r--library/Director/ProvidedHook/Icingadb/ServiceActions.php87
-rw-r--r--library/Director/ProvidedHook/Monitoring/HostActions.php18
-rw-r--r--library/Director/ProvidedHook/Monitoring/ServiceActions.php16
-rw-r--r--library/Director/Resolver/CommandUsage.php4
-rw-r--r--library/Director/Resolver/IcingaObjectResolver.php10
-rw-r--r--library/Director/Resolver/TemplateTree.php12
-rw-r--r--library/Director/RestApi/RestApiClient.php9
-rw-r--r--library/Director/RestApi/RestApiParams.php3
-rw-r--r--library/Director/Restriction/FilterByNameRestriction.php2
-rw-r--r--library/Director/Restriction/HostgroupRestriction.php8
-rw-r--r--library/Director/StartupLogRenderer.php8
-rw-r--r--library/Director/Test/BaseTestCase.php19
-rw-r--r--library/Director/Test/IcingaObjectTestCase.php4
-rw-r--r--library/Director/Test/SyncTest.php6
-rw-r--r--library/Director/Test/TestSuiteLint.php2
-rw-r--r--library/Director/Test/TestSuiteUnit.php4
-rw-r--r--library/Director/Web/Controller/ActionController.php26
-rw-r--r--library/Director/Web/Controller/BranchHelper.php40
-rw-r--r--library/Director/Web/Controller/Extension/DirectorDb.php3
-rw-r--r--library/Director/Web/Controller/Extension/RestApi.php3
-rw-r--r--library/Director/Web/Controller/Extension/SingleObjectApiHandler.php236
-rw-r--r--library/Director/Web/Controller/ObjectController.php30
-rw-r--r--library/Director/Web/Controller/ObjectsController.php28
-rw-r--r--library/Director/Web/Controller/TemplateController.php35
-rw-r--r--library/Director/Web/Form/CloneImportSourceForm.php30
-rw-r--r--library/Director/Web/Form/CloneSyncRuleForm.php26
-rw-r--r--library/Director/Web/Form/CsrfToken.php2
-rw-r--r--library/Director/Web/Form/DbSelectorForm.php2
-rw-r--r--library/Director/Web/Form/DirectorForm.php2
-rw-r--r--library/Director/Web/Form/DirectorObjectForm.php14
-rw-r--r--library/Director/Web/Form/Element/DataFilter.php20
-rw-r--r--library/Director/Web/Form/Element/ExtensibleSet.php2
-rw-r--r--library/Director/Web/Form/IcingaObjectFieldLoader.php2
-rw-r--r--library/Director/Web/Form/IplElement/ExtensibleSetElement.php6
-rw-r--r--library/Director/Web/Form/QuickForm.php4
-rw-r--r--library/Director/Web/SelfService.php14
-rw-r--r--library/Director/Web/Table/ActivityLogTable.php19
-rw-r--r--library/Director/Web/Table/ApplyRulesTable.php107
-rw-r--r--library/Director/Web/Table/BasketSnapshotTable.php5
-rw-r--r--library/Director/Web/Table/BranchActivityTable.php13
-rw-r--r--library/Director/Web/Table/ChoicesTable.php4
-rw-r--r--library/Director/Web/Table/CustomvarTable.php6
-rw-r--r--library/Director/Web/Table/CustomvarVariantsTable.php6
-rw-r--r--library/Director/Web/Table/DatafieldTable.php17
-rw-r--r--library/Director/Web/Table/DependencyTemplateUsageTable.php12
-rw-r--r--library/Director/Web/Table/DeploymentLogTable.php3
-rw-r--r--library/Director/Web/Table/GroupMemberTable.php18
-rw-r--r--library/Director/Web/Table/HostTemplateUsageTable.php8
-rw-r--r--library/Director/Web/Table/IcingaServiceSetServiceTable.php31
-rw-r--r--library/Director/Web/Table/IntlZfQueryBasedTable.php51
-rw-r--r--library/Director/Web/Table/NotificationTemplateUsageTable.php12
-rw-r--r--library/Director/Web/Table/ObjectSetTable.php68
-rw-r--r--library/Director/Web/Table/ObjectsTable.php77
-rw-r--r--library/Director/Web/Table/ObjectsTableService.php6
-rw-r--r--library/Director/Web/Table/ObjectsTableSetMembers.php255
-rw-r--r--library/Director/Web/Table/ReadOnlyFormAvpTable.php113
-rw-r--r--library/Director/Web/Table/ServiceTemplateUsageTable.php26
-rw-r--r--library/Director/Web/Table/SyncRunTable.php9
-rw-r--r--library/Director/Web/Table/TableWithBranchSupport.php3
-rw-r--r--library/Director/Web/Table/TemplateUsageTable.php104
-rw-r--r--library/Director/Web/Table/TemplatesTable.php22
-rw-r--r--library/Director/Web/Tabs/InfraTabs.php7
-rw-r--r--library/Director/Web/Tabs/MainTabs.php5
-rw-r--r--library/Director/Web/Tabs/ObjectTabs.php35
-rw-r--r--library/Director/Web/Tabs/ObjectsTabs.php58
-rw-r--r--library/Director/Web/Tree/TemplateTreeRenderer.php2
-rw-r--r--library/Director/Web/Widget/ActivityLogInfo.php46
-rw-r--r--library/Director/Web/Widget/AdditionalTableActions.php5
-rw-r--r--library/Director/Web/Widget/BranchedObjectHint.php48
-rw-r--r--library/Director/Web/Widget/BranchedObjectsHint.php7
-rw-r--r--library/Director/Web/Widget/DeploymentInfo.php27
-rw-r--r--library/Director/Web/Widget/IcingaObjectInspection.php2
219 files changed, 3558 insertions, 2559 deletions
diff --git a/library/Director/Acl.php b/library/Director/Acl.php
index 4aa2bd2..44c23f5 100644
--- a/library/Director/Acl.php
+++ b/library/Director/Acl.php
@@ -56,7 +56,7 @@ class Acl
public function listRoleNames()
{
return array_map(
- array($this, 'getNameForRole'),
+ [$this, 'getNameForRole'],
$this->getUser()->getRoles()
);
}
diff --git a/library/Director/Auth/MonitoringRestriction.php b/library/Director/Auth/MonitoringRestriction.php
new file mode 100644
index 0000000..1fb6013
--- /dev/null
+++ b/library/Director/Auth/MonitoringRestriction.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Icinga\Module\Director\Auth;
+
+use Icinga\Authentication\Auth;
+use Icinga\Data\Filter\Filter;
+
+class MonitoringRestriction
+{
+ public static function getObjectsFilter(Auth $auth): Filter
+ {
+ $restriction = Filter::matchAny();
+ $restriction->setAllowedFilterColumns([
+ 'host_name',
+ 'hostgroup_name',
+ 'instance_name',
+ 'service_description',
+ 'servicegroup_name',
+ function ($c) {
+ return preg_match('/^_(?:host|service)_/i', $c);
+ }
+ ]);
+ foreach ($auth->getRestrictions(Restriction::MONITORING_RW_OBJECT_FILTER) as $filter) {
+ if ($filter === '*') {
+ return Filter::matchAll();
+ }
+ $restriction->addFilter(Filter::fromQueryString($filter));
+ }
+
+ if ($restriction->isEmpty()) {
+ return Filter::matchAll();
+ }
+
+ return $restriction;
+ }
+}
diff --git a/library/Director/Auth/Permission.php b/library/Director/Auth/Permission.php
new file mode 100644
index 0000000..c29d789
--- /dev/null
+++ b/library/Director/Auth/Permission.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Icinga\Module\Director\Auth;
+
+class Permission
+{
+ public const ALL_PERMISSIONS = 'director/*';
+ public const ADMIN = 'director/admin'; // internal, assign ALL_PERMISSONS
+ public const API = 'director/api';
+ public const AUDIT = 'director/audit';
+ public const DEPLOY = 'director/deploy';
+ public const DEPLOYMENTS = 'director/deployments'; // internal, assign ALL_PERMISSONS
+ public const GROUPS_FOR_RESTRICTED_HOSTS = 'director/groups-for-restricted-hosts';
+ public const HOSTS = 'director/hosts';
+ public const HOST_GROUPS = 'director/hostgroups'; // internal, assign ALL_PERMISSIONS
+ public const INSPECT = 'director/inspect';
+ public const MONITORING_SERVICES_RO = 'director/monitoring/services-ro';
+ public const MONITORING_SERVICES = 'director/monitoring/services';
+ public const MONITORING_HOSTS = 'director/monitoring/hosts';
+ public const ICINGADB_SERVICES_RO = 'director/icingadb/services-ro';
+ public const ICINGADB_SERVICES = 'director/icingadb/services';
+ public const ICINGADB_HOSTS = 'director/icingadb/hosts';
+ public const NOTIFICATIONS = 'director/notifications';
+ public const SCHEDULED_DOWNTIMES = 'director/scheduled-downtimes';
+ public const SERVICES = 'director/services';
+ public const SERVICE_SETS = 'director/servicesets';
+ public const SERVICE_SET_APPLY = 'director/service_set/apply';
+ public const SHOW_CONFIG = 'director/showconfig';
+ public const SHOW_SQL = 'director/showsql';
+ public const USERS = 'director/users';
+}
diff --git a/library/Director/Auth/Restriction.php b/library/Director/Auth/Restriction.php
new file mode 100644
index 0000000..3394dcc
--- /dev/null
+++ b/library/Director/Auth/Restriction.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Icinga\Module\Director\Auth;
+
+class Restriction
+{
+ public const MONITORING_RW_OBJECT_FILTER = 'director/monitoring/rw-object-filter';
+ public const ICINGADB_RW_OBJECT_FILTER = 'director/icingadb/rw-object-filter';
+ public const FILTER_HOSTGROUPS = 'director/filter/hostgroups';
+
+ // Hint: by-name-Filters are being fetched with variable names, like "director/$type/apply/filter-by-name"
+ public const NOTIFICATION_APPLY_FILTER_BY_NAME = 'director/notification/apply/filter-by-name';
+ public const SCHEDULED_DOWNTIME_APPLY_FILTER_BY_NAME = 'director/scheduled-downtime/apply/filter-by-name';
+ public const SERVICE_APPLY_FILTER_BY_NAME = 'director/service/apply/filter-by-name';
+ public const SERVICE_SET_FILTER_BY_NAME = 'director/service_set/filter-by-name';
+ const DB_RESOURCE = 'director/db_resource';
+}
diff --git a/library/Director/Cli/Command.php b/library/Director/Cli/Command.php
index 69d61b1..7268913 100644
--- a/library/Director/Cli/Command.php
+++ b/library/Director/Cli/Command.php
@@ -82,9 +82,9 @@ class Command extends CliCommand
{
MemoryLimit::raiseTo('1024M');
- ini_set('max_execution_time', 0);
+ ini_set('max_execution_time', '0');
if (version_compare(PHP_VERSION, '7.0.0') < 0) {
- ini_set('zend.enable_gc', 0);
+ ini_set('zend.enable_gc', '0');
}
return $this;
diff --git a/library/Director/Cli/ObjectCommand.php b/library/Director/Cli/ObjectCommand.php
index ca68213..ed99c14 100644
--- a/library/Director/Cli/ObjectCommand.php
+++ b/library/Director/Cli/ObjectCommand.php
@@ -428,7 +428,7 @@ class ObjectCommand extends Command
}
$stdin = file_get_contents('php://stdin');
- if (strlen($stdin) === 0) {
+ if (! $stdin) {
return null;
}
diff --git a/library/Director/Core/CoreApi.php b/library/Director/Core/CoreApi.php
index ea10916..73588c2 100644
--- a/library/Director/Core/CoreApi.php
+++ b/library/Director/Core/CoreApi.php
@@ -569,7 +569,7 @@ constants
'icon_image_alt' => 'icon_image_alt',
];
- if (version_compare($this->getVersion(), '2.8.0', '>=')) {
+ if (version_compare($this->getVersion() ?? '', '2.8.0', '>=')) {
$params['flapping_threshold_high'] = 'flapping_threshold_high';
$params['flapping_threshold_low'] = 'flapping_threshold_low';
}
@@ -622,7 +622,7 @@ constants
{
IcingaCommand::setPluginDir($this->getConstant('PluginDir'));
- $objects = $this->getDirectorObjects('Command', "${type}Commands", [
+ $objects = $this->getDirectorObjects('Command', "{$type}Commands", [
'arguments' => 'arguments',
// 'env' => 'env',
'timeout' => 'timeout',
diff --git a/library/Director/Core/LegacyDeploymentApi.php b/library/Director/Core/LegacyDeploymentApi.php
index 7287c4a..0ab77e0 100644
--- a/library/Director/Core/LegacyDeploymentApi.php
+++ b/library/Director/Core/LegacyDeploymentApi.php
@@ -128,6 +128,10 @@ class LegacyDeploymentApi implements DeploymentApiInterface
if (file_exists($path)) {
if (is_link($path)) {
$linkTarget = readlink($path);
+ if (! $linkTarget) {
+ throw new IcingaException('Failed to read symlink');
+ }
+
$linkTargetDir = dirname($linkTarget);
$linkTargetName = basename($linkTarget);
@@ -165,7 +169,7 @@ class LegacyDeploymentApi implements DeploymentApiInterface
$this->assertDeploymentPath();
$dh = @opendir($this->deploymentPath);
- if ($dh === null) {
+ if ($dh === false) {
throw new IcingaException('Can not list contents of %s', $this->deploymentPath);
}
@@ -279,7 +283,7 @@ class LegacyDeploymentApi implements DeploymentApiInterface
$this->mkdir(dirname($fullPath), true);
$fh = @fopen($fullPath, 'w');
- if ($fh === null) {
+ if ($fh === false) {
throw new IcingaException('Could not open file "%s" for writing.', $fullPath);
}
chmod($fullPath, $this->file_mode);
@@ -334,7 +338,7 @@ class LegacyDeploymentApi implements DeploymentApiInterface
protected function listDirectoryContents($path, $depth = 0)
{
$dh = @opendir($path);
- if ($dh === null) {
+ if ($dh === false) {
throw new IcingaException('Can not list contents of %s', $path);
}
diff --git a/library/Director/Core/RestApiClient.php b/library/Director/Core/RestApiClient.php
index b0854ff..19f6b68 100644
--- a/library/Director/Core/RestApiClient.php
+++ b/library/Director/Core/RestApiClient.php
@@ -206,14 +206,14 @@ class RestApiClient
}
/**
- * @return resource
+ * @throws RuntimeException
*/
protected function curl()
{
if ($this->curl === null) {
$this->curl = curl_init(sprintf('https://%s:%d', $this->peer, $this->port));
if (! $this->curl) {
- throw new RuntimeException('CURL INIT ERROR: ' . curl_error($this->curl));
+ throw new RuntimeException('CURL INIT FAILED');
}
}
@@ -260,13 +260,11 @@ class RestApiClient
public function disconnect()
{
- if ($this->curl !== null) {
- if (is_resource($this->curl)) {
- @curl_close($this->curl);
- }
-
- $this->curl = null;
+ if ($this->curl) {
+ @curl_close($this->curl);
}
+
+ $this->curl = null;
}
public function __destruct()
diff --git a/library/Director/Core/RestApiResponse.php b/library/Director/Core/RestApiResponse.php
index 523ed35..43516f7 100644
--- a/library/Director/Core/RestApiResponse.php
+++ b/library/Director/Core/RestApiResponse.php
@@ -113,7 +113,7 @@ class RestApiResponse
throw new IcingaException('API request failed: ' . $result->status);
}
} else {
- throw new IcingaException('API request failed: ' . var_export($result, 1));
+ throw new IcingaException('API request failed: ' . var_export($result, true));
}
}
diff --git a/library/Director/CustomVariable/CustomVariable.php b/library/Director/CustomVariable/CustomVariable.php
index 98eda84..4b5dd3e 100644
--- a/library/Director/CustomVariable/CustomVariable.php
+++ b/library/Director/CustomVariable/CustomVariable.php
@@ -236,7 +236,7 @@ abstract class CustomVariable implements IcingaConfigRenderer
// TODO: check for specific class/stdClass/interface?
return new CustomVariableDictionary($key, $value);
} else {
- throw new LogicException(sprintf('WTF (%s): %s', $key, var_export($value, 1)));
+ throw new LogicException(sprintf('WTF (%s): %s', $key, var_export($value, true)));
}
}
diff --git a/library/Director/CustomVariable/CustomVariableBoolean.php b/library/Director/CustomVariable/CustomVariableBoolean.php
index 9953fae..750f1d6 100644
--- a/library/Director/CustomVariable/CustomVariableBoolean.php
+++ b/library/Director/CustomVariable/CustomVariableBoolean.php
@@ -31,7 +31,7 @@ class CustomVariableBoolean extends CustomVariable
if (! is_bool($value)) {
throw new ProgrammingError(
'Expected a boolean, got %s',
- var_export($value, 1)
+ var_export($value, true)
);
}
diff --git a/library/Director/CustomVariable/CustomVariableNull.php b/library/Director/CustomVariable/CustomVariableNull.php
index f87ccfa..83e07f0 100644
--- a/library/Director/CustomVariable/CustomVariableNull.php
+++ b/library/Director/CustomVariable/CustomVariableNull.php
@@ -31,7 +31,7 @@ class CustomVariableNull extends CustomVariable
if (! is_null($value)) {
throw new ProgrammingError(
'Null can only be null, got %s',
- var_export($value, 1)
+ var_export($value, true)
);
}
diff --git a/library/Director/CustomVariable/CustomVariableNumber.php b/library/Director/CustomVariable/CustomVariableNumber.php
index 62838a9..7b0c3e9 100644
--- a/library/Director/CustomVariable/CustomVariableNumber.php
+++ b/library/Director/CustomVariable/CustomVariableNumber.php
@@ -47,7 +47,7 @@ class CustomVariableNumber extends CustomVariable
if (! is_int($value) && ! is_float($value)) {
throw new ProgrammingError(
'Expected a number, got %s',
- var_export($value, 1)
+ var_export($value, true)
);
}
diff --git a/library/Director/CustomVariable/CustomVariables.php b/library/Director/CustomVariable/CustomVariables.php
index cdcc4bd..01227c5 100644
--- a/library/Director/CustomVariable/CustomVariables.php
+++ b/library/Director/CustomVariable/CustomVariables.php
@@ -414,10 +414,15 @@ class CustomVariables implements Iterator, Countable, IcingaConfigRenderer
protected function renderKeyName($key)
{
+ return 'vars' . self::renderKeySuffix($key);
+ }
+
+ public static function renderKeySuffix($key)
+ {
if (preg_match('/^[a-z][a-z0-9_]*$/i', $key)) {
- return 'vars.' . c::escapeIfReserved($key);
+ return '.' . c::escapeIfReserved($key);
} else {
- return 'vars[' . c::renderString($key) . ']';
+ return '[' . c::renderString($key) . ']';
}
}
diff --git a/library/Director/Daemon/BackgroundDaemon.php b/library/Director/Daemon/BackgroundDaemon.php
index 34cc28b..2d8a29c 100644
--- a/library/Director/Daemon/BackgroundDaemon.php
+++ b/library/Director/Daemon/BackgroundDaemon.php
@@ -104,7 +104,7 @@ class BackgroundDaemon
try {
$uuid = \bin2hex(Uuid::uuid4()->getBytes());
} catch (Exception $e) {
- $uuid = 'deadc0de' . \substr(\md5(\getmypid()), 0, 24);
+ $uuid = 'deadc0de' . substr(md5((string) getmypid()), 0, 24);
}
}
$processDetails = new DaemonProcessDetails($uuid);
diff --git a/library/Director/Dashboard/BranchesDashboard.php b/library/Director/Dashboard/BranchesDashboard.php
index fe8b385..faeb8bf 100644
--- a/library/Director/Dashboard/BranchesDashboard.php
+++ b/library/Director/Dashboard/BranchesDashboard.php
@@ -4,8 +4,10 @@ namespace Icinga\Module\Director\Dashboard;
use gipfl\Web\Widget\Hint;
use Icinga\Application\Hook;
+use Icinga\Authentication\Auth;
use Icinga\Module\Director\Db\Branch\Branch;
use Icinga\Module\Director\Db\Branch\BranchStore;
+use Icinga\Module\Director\Db\Branch\PreferredBranchSupport;
use Icinga\Module\Director\Hook\BranchSupportHook;
use ipl\Html\Html;
@@ -19,6 +21,14 @@ class BranchesDashboard extends Dashboard
$this->translate('You\'re currently working in a Configuration Branch: %s'),
Branch::requireHook()->linkToBranch($branch, $this->getAuth(), $branch->getName())
)));
+ } else {
+ if (($implementation = Branch::optionalHook()) && $implementation instanceof PreferredBranchSupport) {
+ if ($implementation->hasPreferredBranch(Auth::getInstance())) {
+ $this->prepend(Hint::warning(
+ $this->translate('You\'re currently working in the main Configuration Branch')
+ ));
+ }
+ }
}
return $this->translate('Prepare your configuration in a safe Environment');
diff --git a/library/Director/Dashboard/Dashboard.php b/library/Director/Dashboard/Dashboard.php
index de8970c..3f1fc38 100644
--- a/library/Director/Dashboard/Dashboard.php
+++ b/library/Director/Dashboard/Dashboard.php
@@ -149,6 +149,7 @@ abstract class Dashboard extends HtmlDocument
]);
}
+ #[\ReturnTypeWillChange]
public function count()
{
return count($this->dashlets());
diff --git a/library/Director/Dashboard/Dashlet/ActivityLogDashlet.php b/library/Director/Dashboard/Dashlet/ActivityLogDashlet.php
index 9794986..7a9745b 100644
--- a/library/Director/Dashboard/Dashlet/ActivityLogDashlet.php
+++ b/library/Director/Dashboard/Dashlet/ActivityLogDashlet.php
@@ -2,6 +2,8 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
class ActivityLogDashlet extends Dashlet
{
protected $icon = 'book';
@@ -30,6 +32,6 @@ class ActivityLogDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/audit');
+ return [Permission::AUDIT];
}
}
diff --git a/library/Director/Dashboard/Dashlet/ApiUserObjectDashlet.php b/library/Director/Dashboard/Dashlet/ApiUserObjectDashlet.php
index 419859d..238059a 100644
--- a/library/Director/Dashboard/Dashlet/ApiUserObjectDashlet.php
+++ b/library/Director/Dashboard/Dashlet/ApiUserObjectDashlet.php
@@ -2,11 +2,13 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
class ApiUserObjectDashlet extends Dashlet
{
protected $icon = 'lock-open-alt';
- protected $requiredStats = array('apiuser');
+ protected $requiredStats = ['apiuser'];
public function getTitle()
{
@@ -20,6 +22,6 @@ class ApiUserObjectDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/admin');
+ return [Permission::ADMIN];
}
}
diff --git a/library/Director/Dashboard/Dashlet/BasketDashlet.php b/library/Director/Dashboard/Dashlet/BasketDashlet.php
index 10f2b81..8ac26ed 100644
--- a/library/Director/Dashboard/Dashlet/BasketDashlet.php
+++ b/library/Director/Dashboard/Dashlet/BasketDashlet.php
@@ -2,6 +2,8 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
class BasketDashlet extends Dashlet
{
protected $icon = 'tag';
@@ -25,6 +27,6 @@ class BasketDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/admin');
+ return [Permission::ADMIN];
}
}
diff --git a/library/Director/Dashboard/Dashlet/CheckCommandsDashlet.php b/library/Director/Dashboard/Dashlet/CheckCommandsDashlet.php
index 65d8c8c..458c700 100644
--- a/library/Director/Dashboard/Dashlet/CheckCommandsDashlet.php
+++ b/library/Director/Dashboard/Dashlet/CheckCommandsDashlet.php
@@ -2,6 +2,8 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
class CheckCommandsDashlet extends Dashlet
{
protected $icon = 'wrench';
@@ -21,7 +23,7 @@ class CheckCommandsDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/admin');
+ return [Permission::ADMIN];
}
public function getUrl()
diff --git a/library/Director/Dashboard/Dashlet/ChoicesDashlet.php b/library/Director/Dashboard/Dashlet/ChoicesDashlet.php
index efdbba5..105ebb9 100644
--- a/library/Director/Dashboard/Dashlet/ChoicesDashlet.php
+++ b/library/Director/Dashboard/Dashlet/ChoicesDashlet.php
@@ -2,6 +2,8 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
abstract class ChoicesDashlet extends Dashlet
{
protected $icon = 'flapping';
@@ -36,6 +38,6 @@ abstract class ChoicesDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/admin');
+ return [Permission::ADMIN];
}
}
diff --git a/library/Director/Dashboard/Dashlet/CommandObjectDashlet.php b/library/Director/Dashboard/Dashlet/CommandObjectDashlet.php
index 083172e..18a4731 100644
--- a/library/Director/Dashboard/Dashlet/CommandObjectDashlet.php
+++ b/library/Director/Dashboard/Dashlet/CommandObjectDashlet.php
@@ -2,11 +2,13 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
class CommandObjectDashlet extends Dashlet
{
protected $icon = 'wrench';
- protected $requiredStats = array('command');
+ protected $requiredStats = ['command'];
public function getTitle()
{
@@ -20,6 +22,6 @@ class CommandObjectDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/admin');
+ return [Permission::ADMIN];
}
}
diff --git a/library/Director/Dashboard/Dashlet/CustomvarDashlet.php b/library/Director/Dashboard/Dashlet/CustomvarDashlet.php
index 919c06b..a2acd54 100644
--- a/library/Director/Dashboard/Dashlet/CustomvarDashlet.php
+++ b/library/Director/Dashboard/Dashlet/CustomvarDashlet.php
@@ -2,6 +2,8 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
class CustomvarDashlet extends Dashlet
{
protected $icon = 'keyboard';
@@ -25,6 +27,6 @@ class CustomvarDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/admin');
+ return [Permission::ADMIN];
}
}
diff --git a/library/Director/Dashboard/Dashlet/DatafieldCategoryDashlet.php b/library/Director/Dashboard/Dashlet/DatafieldCategoryDashlet.php
index 6efb4ca..eb1cefc 100644
--- a/library/Director/Dashboard/Dashlet/DatafieldCategoryDashlet.php
+++ b/library/Director/Dashboard/Dashlet/DatafieldCategoryDashlet.php
@@ -2,6 +2,8 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
class DatafieldCategoryDashlet extends Dashlet
{
protected $icon = 'th-list';
@@ -25,6 +27,6 @@ class DatafieldCategoryDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/admin');
+ return [Permission::ADMIN];
}
}
diff --git a/library/Director/Dashboard/Dashlet/DatafieldDashlet.php b/library/Director/Dashboard/Dashlet/DatafieldDashlet.php
index 03f2d8d..a381a3f 100644
--- a/library/Director/Dashboard/Dashlet/DatafieldDashlet.php
+++ b/library/Director/Dashboard/Dashlet/DatafieldDashlet.php
@@ -2,6 +2,8 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
class DatafieldDashlet extends Dashlet
{
protected $icon = 'edit';
@@ -25,6 +27,6 @@ class DatafieldDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/admin');
+ return [Permission::ADMIN];
}
}
diff --git a/library/Director/Dashboard/Dashlet/DatalistDashlet.php b/library/Director/Dashboard/Dashlet/DatalistDashlet.php
index bdf179f..fe82e4b 100644
--- a/library/Director/Dashboard/Dashlet/DatalistDashlet.php
+++ b/library/Director/Dashboard/Dashlet/DatalistDashlet.php
@@ -2,6 +2,8 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
class DatalistDashlet extends Dashlet
{
protected $icon = 'sort-name-up';
@@ -25,6 +27,6 @@ class DatalistDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/admin');
+ return [Permission::ADMIN];
}
}
diff --git a/library/Director/Dashboard/Dashlet/DependencyObjectDashlet.php b/library/Director/Dashboard/Dashlet/DependencyObjectDashlet.php
index 47a18aa..b6455fe 100644
--- a/library/Director/Dashboard/Dashlet/DependencyObjectDashlet.php
+++ b/library/Director/Dashboard/Dashlet/DependencyObjectDashlet.php
@@ -6,7 +6,7 @@ class DependencyObjectDashlet extends Dashlet
{
protected $icon = 'sitemap';
- protected $requiredStats = array('dependency');
+ protected $requiredStats = ['dependency'];
public function getTitle()
{
diff --git a/library/Director/Dashboard/Dashlet/DeploymentDashlet.php b/library/Director/Dashboard/Dashlet/DeploymentDashlet.php
index 7a52793..83b4cea 100644
--- a/library/Director/Dashboard/Dashlet/DeploymentDashlet.php
+++ b/library/Director/Dashboard/Dashlet/DeploymentDashlet.php
@@ -3,6 +3,7 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
use Exception;
+use Icinga\Module\Director\Auth\Permission;
use Icinga\Module\Director\Objects\DirectorDeploymentLog;
class DeploymentDashlet extends Dashlet
@@ -109,6 +110,6 @@ class DeploymentDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/deploy');
+ return [Permission::DEPLOY];
}
}
diff --git a/library/Director/Dashboard/Dashlet/EndpointObjectDashlet.php b/library/Director/Dashboard/Dashlet/EndpointObjectDashlet.php
index 9dd9467..97ae746 100644
--- a/library/Director/Dashboard/Dashlet/EndpointObjectDashlet.php
+++ b/library/Director/Dashboard/Dashlet/EndpointObjectDashlet.php
@@ -3,12 +3,13 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
use Exception;
+use Icinga\Module\Director\Auth\Permission;
class EndpointObjectDashlet extends Dashlet
{
protected $icon = 'cloud';
- protected $requiredStats = array('endpoint');
+ protected $requiredStats = ['endpoint'];
protected $hasDeploymentEndpoint;
@@ -24,7 +25,7 @@ class EndpointObjectDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/admin');
+ return [Permission::ADMIN];
}
protected function hasDeploymentEndpoint()
diff --git a/library/Director/Dashboard/Dashlet/HostGroupsDashlet.php b/library/Director/Dashboard/Dashlet/HostGroupsDashlet.php
index 5d3b25f..249d4d6 100644
--- a/library/Director/Dashboard/Dashlet/HostGroupsDashlet.php
+++ b/library/Director/Dashboard/Dashlet/HostGroupsDashlet.php
@@ -2,6 +2,8 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
class HostGroupsDashlet extends Dashlet
{
protected $icon = 'tags';
@@ -26,6 +28,6 @@ class HostGroupsDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/hostgroups');
+ return [Permission::HOST_GROUPS];
}
}
diff --git a/library/Director/Dashboard/Dashlet/HostObjectDashlet.php b/library/Director/Dashboard/Dashlet/HostObjectDashlet.php
index 10cff94..e77f75b 100644
--- a/library/Director/Dashboard/Dashlet/HostObjectDashlet.php
+++ b/library/Director/Dashboard/Dashlet/HostObjectDashlet.php
@@ -2,11 +2,13 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
class HostObjectDashlet extends Dashlet
{
protected $icon = 'host';
- protected $requiredStats = array('host', 'hostgroup');
+ protected $requiredStats = ['host', 'hostgroup'];
public function getTitle()
{
@@ -15,7 +17,7 @@ class HostObjectDashlet extends Dashlet
public function listRequiredPermissions()
{
- return ['director/hosts'];
+ return [Permission::HOSTS];
}
public function getUrl()
diff --git a/library/Director/Dashboard/Dashlet/HostTemplatesDashlet.php b/library/Director/Dashboard/Dashlet/HostTemplatesDashlet.php
index 09bed17..eb4092a 100644
--- a/library/Director/Dashboard/Dashlet/HostTemplatesDashlet.php
+++ b/library/Director/Dashboard/Dashlet/HostTemplatesDashlet.php
@@ -2,6 +2,8 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
class HostTemplatesDashlet extends Dashlet
{
protected $icon = 'cubes';
@@ -26,6 +28,6 @@ class HostTemplatesDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/admin');
+ return [Permission::ADMIN];
}
}
diff --git a/library/Director/Dashboard/Dashlet/HostsDashlet.php b/library/Director/Dashboard/Dashlet/HostsDashlet.php
index 39c1421..55bebbd 100644
--- a/library/Director/Dashboard/Dashlet/HostsDashlet.php
+++ b/library/Director/Dashboard/Dashlet/HostsDashlet.php
@@ -2,6 +2,8 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
class HostsDashlet extends Dashlet
{
protected $icon = 'host';
@@ -27,6 +29,6 @@ class HostsDashlet extends Dashlet
public function listRequiredPermissions()
{
- return ['director/hosts'];
+ return [Permission::HOSTS];
}
}
diff --git a/library/Director/Dashboard/Dashlet/ImportSourceDashlet.php b/library/Director/Dashboard/Dashlet/ImportSourceDashlet.php
index 302c1ed..aa34613 100644
--- a/library/Director/Dashboard/Dashlet/ImportSourceDashlet.php
+++ b/library/Director/Dashboard/Dashlet/ImportSourceDashlet.php
@@ -3,6 +3,7 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
use Exception;
+use Icinga\Module\Director\Auth\Permission;
use Icinga\Module\Director\Objects\ImportSource;
class ImportSourceDashlet extends Dashlet
@@ -60,6 +61,6 @@ class ImportSourceDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/admin');
+ return [Permission::ADMIN];
}
}
diff --git a/library/Director/Dashboard/Dashlet/InfrastructureDashlet.php b/library/Director/Dashboard/Dashlet/InfrastructureDashlet.php
index 328df72..e1d5908 100644
--- a/library/Director/Dashboard/Dashlet/InfrastructureDashlet.php
+++ b/library/Director/Dashboard/Dashlet/InfrastructureDashlet.php
@@ -2,6 +2,8 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
class InfrastructureDashlet extends Dashlet
{
protected $icon = 'cloud';
@@ -25,6 +27,6 @@ class InfrastructureDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/admin');
+ return [Permission::ADMIN];
}
}
diff --git a/library/Director/Dashboard/Dashlet/JobDashlet.php b/library/Director/Dashboard/Dashlet/JobDashlet.php
index d7452e0..af5429d 100644
--- a/library/Director/Dashboard/Dashlet/JobDashlet.php
+++ b/library/Director/Dashboard/Dashlet/JobDashlet.php
@@ -3,6 +3,7 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
use Exception;
+use Icinga\Module\Director\Auth\Permission;
use Icinga\Module\Director\Objects\DirectorJob;
class JobDashlet extends Dashlet
@@ -60,6 +61,6 @@ class JobDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/admin');
+ return [Permission::ADMIN];
}
}
diff --git a/library/Director/Dashboard/Dashlet/KickstartDashlet.php b/library/Director/Dashboard/Dashlet/KickstartDashlet.php
index 09801f5..eb74371 100644
--- a/library/Director/Dashboard/Dashlet/KickstartDashlet.php
+++ b/library/Director/Dashboard/Dashlet/KickstartDashlet.php
@@ -2,6 +2,8 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
class KickstartDashlet extends Dashlet
{
protected $icon = 'gauge';
@@ -26,6 +28,6 @@ class KickstartDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/admin');
+ return [Permission::ADMIN];
}
}
diff --git a/library/Director/Dashboard/Dashlet/NotificationApplyDashlet.php b/library/Director/Dashboard/Dashlet/NotificationApplyDashlet.php
index e0b0443..d0dbb01 100644
--- a/library/Director/Dashboard/Dashlet/NotificationApplyDashlet.php
+++ b/library/Director/Dashboard/Dashlet/NotificationApplyDashlet.php
@@ -2,6 +2,8 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
class NotificationApplyDashlet extends Dashlet
{
protected $icon = 'bell';
@@ -27,7 +29,7 @@ class NotificationApplyDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/notifications');
+ return [Permission::NOTIFICATIONS];
}
public function getUrl()
diff --git a/library/Director/Dashboard/Dashlet/NotificationTemplateDashlet.php b/library/Director/Dashboard/Dashlet/NotificationTemplateDashlet.php
index a58b5d0..6f1fe64 100644
--- a/library/Director/Dashboard/Dashlet/NotificationTemplateDashlet.php
+++ b/library/Director/Dashboard/Dashlet/NotificationTemplateDashlet.php
@@ -2,11 +2,13 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
class NotificationTemplateDashlet extends Dashlet
{
protected $icon = 'cubes';
- protected $requiredStats = array('notification');
+ protected $requiredStats = ['notification'];
public function getTitle()
{
@@ -21,7 +23,7 @@ class NotificationTemplateDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/admin');
+ return [Permission::ADMIN];
}
public function getUrl()
diff --git a/library/Director/Dashboard/Dashlet/NotificationsDashlet.php b/library/Director/Dashboard/Dashlet/NotificationsDashlet.php
index 85610f0..a0b1e43 100644
--- a/library/Director/Dashboard/Dashlet/NotificationsDashlet.php
+++ b/library/Director/Dashboard/Dashlet/NotificationsDashlet.php
@@ -2,11 +2,13 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
class NotificationsDashlet extends Dashlet
{
protected $icon = 'bell';
- protected $requiredStats = array('notification');
+ protected $requiredStats = ['notification'];
public function getTitle()
{
@@ -23,7 +25,7 @@ class NotificationsDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/notifications');
+ return [Permission::NOTIFICATIONS];
}
public function getUrl()
diff --git a/library/Director/Dashboard/Dashlet/ScheduledDowntimeApplyDashlet.php b/library/Director/Dashboard/Dashlet/ScheduledDowntimeApplyDashlet.php
index 45bcfa2..c9fbb68 100644
--- a/library/Director/Dashboard/Dashlet/ScheduledDowntimeApplyDashlet.php
+++ b/library/Director/Dashboard/Dashlet/ScheduledDowntimeApplyDashlet.php
@@ -2,6 +2,8 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
class ScheduledDowntimeApplyDashlet extends Dashlet
{
protected $icon = 'plug';
@@ -15,7 +17,7 @@ class ScheduledDowntimeApplyDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/scheduled-downtimes');
+ return [Permission::SCHEDULED_DOWNTIMES];
}
public function getUrl()
diff --git a/library/Director/Dashboard/Dashlet/SelfServiceDashlet.php b/library/Director/Dashboard/Dashlet/SelfServiceDashlet.php
index 32b1cfa..b3d15fc 100644
--- a/library/Director/Dashboard/Dashlet/SelfServiceDashlet.php
+++ b/library/Director/Dashboard/Dashlet/SelfServiceDashlet.php
@@ -2,6 +2,8 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
class SelfServiceDashlet extends Dashlet
{
protected $icon = 'chat';
@@ -26,6 +28,6 @@ class SelfServiceDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/admin');
+ return [Permission::ADMIN];
}
}
diff --git a/library/Director/Dashboard/Dashlet/ServiceApplyRulesDashlet.php b/library/Director/Dashboard/Dashlet/ServiceApplyRulesDashlet.php
index b4bee04..487be02 100644
--- a/library/Director/Dashboard/Dashlet/ServiceApplyRulesDashlet.php
+++ b/library/Director/Dashboard/Dashlet/ServiceApplyRulesDashlet.php
@@ -2,6 +2,8 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
class ServiceApplyRulesDashlet extends Dashlet
{
protected $icon = 'resize-full-alt';
@@ -26,6 +28,6 @@ class ServiceApplyRulesDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/admin');
+ return [Permission::ADMIN];
}
}
diff --git a/library/Director/Dashboard/Dashlet/ServiceGroupsDashlet.php b/library/Director/Dashboard/Dashlet/ServiceGroupsDashlet.php
index ad47768..44162a9 100644
--- a/library/Director/Dashboard/Dashlet/ServiceGroupsDashlet.php
+++ b/library/Director/Dashboard/Dashlet/ServiceGroupsDashlet.php
@@ -2,6 +2,8 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
class ServiceGroupsDashlet extends Dashlet
{
protected $icon = 'tags';
@@ -26,6 +28,6 @@ class ServiceGroupsDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/admin');
+ return [Permission::ADMIN];
}
}
diff --git a/library/Director/Dashboard/Dashlet/ServiceObjectDashlet.php b/library/Director/Dashboard/Dashlet/ServiceObjectDashlet.php
index 01fb800..087590b 100644
--- a/library/Director/Dashboard/Dashlet/ServiceObjectDashlet.php
+++ b/library/Director/Dashboard/Dashlet/ServiceObjectDashlet.php
@@ -3,6 +3,8 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
use Icinga\Module\Director\Acl;
+use Icinga\Module\Director\Auth\Permission;
+use RuntimeException;
class ServiceObjectDashlet extends Dashlet
{
@@ -22,13 +24,13 @@ class ServiceObjectDashlet extends Dashlet
public function listRequiredPermissions()
{
- return ['director/services'];
+ throw new RuntimeException('This method should not be accessed, isAllowed() has been implemented');
}
public function isAllowed()
{
$acl = Acl::instance();
- return $acl->hasPermission('director/services')
- || $acl->hasPermission('director/service_sets');
+ return $acl->hasPermission(Permission::SERVICES)
+ || $acl->hasPermission(Permission::SERVICE_SETS);
}
}
diff --git a/library/Director/Dashboard/Dashlet/ServiceSetsDashlet.php b/library/Director/Dashboard/Dashlet/ServiceSetsDashlet.php
index f971d42..c8db0e9 100644
--- a/library/Director/Dashboard/Dashlet/ServiceSetsDashlet.php
+++ b/library/Director/Dashboard/Dashlet/ServiceSetsDashlet.php
@@ -2,6 +2,8 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
class ServiceSetsDashlet extends Dashlet
{
protected $icon = 'services';
@@ -26,6 +28,6 @@ class ServiceSetsDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/servicesets');
+ return [Permission::SERVICE_SETS];
}
}
diff --git a/library/Director/Dashboard/Dashlet/ServiceTemplatesDashlet.php b/library/Director/Dashboard/Dashlet/ServiceTemplatesDashlet.php
index 62d1b41..c2131d6 100644
--- a/library/Director/Dashboard/Dashlet/ServiceTemplatesDashlet.php
+++ b/library/Director/Dashboard/Dashlet/ServiceTemplatesDashlet.php
@@ -2,6 +2,8 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
class ServiceTemplatesDashlet extends Dashlet
{
protected $icon = 'cubes';
@@ -26,6 +28,6 @@ class ServiceTemplatesDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/admin');
+ return [Permission::ADMIN];
}
}
diff --git a/library/Director/Dashboard/Dashlet/SettingsDashlet.php b/library/Director/Dashboard/Dashlet/SettingsDashlet.php
index 716e565..0a3d680 100644
--- a/library/Director/Dashboard/Dashlet/SettingsDashlet.php
+++ b/library/Director/Dashboard/Dashlet/SettingsDashlet.php
@@ -2,6 +2,8 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
class SettingsDashlet extends Dashlet
{
protected $icon = 'edit';
@@ -25,6 +27,6 @@ class SettingsDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/admin');
+ return [Permission::ADMIN];
}
}
diff --git a/library/Director/Dashboard/Dashlet/SingleServicesDashlet.php b/library/Director/Dashboard/Dashlet/SingleServicesDashlet.php
index 297b3f8..a7d648a 100644
--- a/library/Director/Dashboard/Dashlet/SingleServicesDashlet.php
+++ b/library/Director/Dashboard/Dashlet/SingleServicesDashlet.php
@@ -2,6 +2,8 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
class SingleServicesDashlet extends Dashlet
{
protected $icon = 'service';
@@ -26,6 +28,6 @@ class SingleServicesDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/services');
+ return [Permission::SERVICES];
}
}
diff --git a/library/Director/Dashboard/Dashlet/SyncDashlet.php b/library/Director/Dashboard/Dashlet/SyncDashlet.php
index 4ac689a..d212bc2 100644
--- a/library/Director/Dashboard/Dashlet/SyncDashlet.php
+++ b/library/Director/Dashboard/Dashlet/SyncDashlet.php
@@ -3,6 +3,7 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
use Exception;
+use Icinga\Module\Director\Auth\Permission;
use Icinga\Module\Director\Objects\SyncRule;
class SyncDashlet extends Dashlet
@@ -60,6 +61,6 @@ class SyncDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/admin');
+ return [Permission::ADMIN];
}
}
diff --git a/library/Director/Dashboard/Dashlet/TimeperiodObjectDashlet.php b/library/Director/Dashboard/Dashlet/TimeperiodObjectDashlet.php
index ba4c1db..2aa4c9b 100644
--- a/library/Director/Dashboard/Dashlet/TimeperiodObjectDashlet.php
+++ b/library/Director/Dashboard/Dashlet/TimeperiodObjectDashlet.php
@@ -4,12 +4,13 @@ namespace Icinga\Module\Director\Dashboard\Dashlet;
use DirectoryIterator;
use Icinga\Exception\ProgrammingError;
+use Icinga\Module\Director\Auth\Permission;
class TimeperiodObjectDashlet extends Dashlet
{
protected $icon = 'calendar';
- protected $requiredStats = array('timeperiod');
+ protected $requiredStats = ['timeperiod'];
public function getTitle()
{
@@ -23,6 +24,6 @@ class TimeperiodObjectDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/admin');
+ return [Permission::ADMIN];
}
}
diff --git a/library/Director/Dashboard/Dashlet/TimeperiodTemplateDashlet.php b/library/Director/Dashboard/Dashlet/TimeperiodTemplateDashlet.php
index 26339e4..7aa3201 100644
--- a/library/Director/Dashboard/Dashlet/TimeperiodTemplateDashlet.php
+++ b/library/Director/Dashboard/Dashlet/TimeperiodTemplateDashlet.php
@@ -2,11 +2,13 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
class TimeperiodTemplateDashlet extends Dashlet
{
protected $icon = 'cubes';
- protected $requiredStats = array('timeperiod');
+ protected $requiredStats = ['timeperiod'];
public function getTitle()
{
@@ -21,7 +23,7 @@ class TimeperiodTemplateDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/admin');
+ return [Permission::ADMIN];
}
public function getUrl()
diff --git a/library/Director/Dashboard/Dashlet/TimeperiodsDashlet.php b/library/Director/Dashboard/Dashlet/TimeperiodsDashlet.php
index 5a54bec..827cc12 100644
--- a/library/Director/Dashboard/Dashlet/TimeperiodsDashlet.php
+++ b/library/Director/Dashboard/Dashlet/TimeperiodsDashlet.php
@@ -2,11 +2,13 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
class TimeperiodsDashlet extends Dashlet
{
protected $icon = 'calendar';
- protected $requiredStats = array('timeperiod');
+ protected $requiredStats = ['timeperiod'];
public function getTitle()
{
@@ -20,6 +22,6 @@ class TimeperiodsDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/admin');
+ return [Permission::ADMIN];
}
}
diff --git a/library/Director/Dashboard/Dashlet/UserGroupsDashlet.php b/library/Director/Dashboard/Dashlet/UserGroupsDashlet.php
index 3fba4ba..792e140 100644
--- a/library/Director/Dashboard/Dashlet/UserGroupsDashlet.php
+++ b/library/Director/Dashboard/Dashlet/UserGroupsDashlet.php
@@ -2,6 +2,8 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
class UserGroupsDashlet extends Dashlet
{
protected $icon = 'tags';
@@ -26,6 +28,6 @@ class UserGroupsDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/admin');
+ return [Permission::ADMIN];
}
}
diff --git a/library/Director/Dashboard/Dashlet/UserObjectDashlet.php b/library/Director/Dashboard/Dashlet/UserObjectDashlet.php
index 463b84c..7e4f511 100644
--- a/library/Director/Dashboard/Dashlet/UserObjectDashlet.php
+++ b/library/Director/Dashboard/Dashlet/UserObjectDashlet.php
@@ -2,14 +2,11 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
-use DirectoryIterator;
-use Icinga\Exception\ProgrammingError;
-
class UserObjectDashlet extends Dashlet
{
protected $icon = 'users';
- protected $requiredStats = array('user', 'usergroup');
+ protected $requiredStats = ['user', 'usergroup'];
public function getTitle()
{
diff --git a/library/Director/Dashboard/Dashlet/UserTemplateDashlet.php b/library/Director/Dashboard/Dashlet/UserTemplateDashlet.php
index 291ab05..c00215d 100644
--- a/library/Director/Dashboard/Dashlet/UserTemplateDashlet.php
+++ b/library/Director/Dashboard/Dashlet/UserTemplateDashlet.php
@@ -2,11 +2,13 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
class UserTemplateDashlet extends Dashlet
{
protected $icon = 'cubes';
- protected $requiredStats = array('user');
+ protected $requiredStats = ['user'];
public function getTitle()
{
@@ -21,7 +23,7 @@ class UserTemplateDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/admin');
+ return [Permission::ADMIN];
}
public function getUrl()
diff --git a/library/Director/Dashboard/Dashlet/UsersDashlet.php b/library/Director/Dashboard/Dashlet/UsersDashlet.php
index 43ddc26..4f0c7d7 100644
--- a/library/Director/Dashboard/Dashlet/UsersDashlet.php
+++ b/library/Director/Dashboard/Dashlet/UsersDashlet.php
@@ -2,11 +2,13 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
class UsersDashlet extends Dashlet
{
protected $icon = 'users';
- protected $requiredStats = array('user', 'usergroup');
+ protected $requiredStats = ['user', 'usergroup'];
public function getTitle()
{
@@ -15,7 +17,7 @@ class UsersDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/users');
+ return [Permission::USERS];
}
public function getUrl()
diff --git a/library/Director/Dashboard/Dashlet/ZoneObjectDashlet.php b/library/Director/Dashboard/Dashlet/ZoneObjectDashlet.php
index ee789f2..f2ff8c8 100644
--- a/library/Director/Dashboard/Dashlet/ZoneObjectDashlet.php
+++ b/library/Director/Dashboard/Dashlet/ZoneObjectDashlet.php
@@ -2,11 +2,13 @@
namespace Icinga\Module\Director\Dashboard\Dashlet;
+use Icinga\Module\Director\Auth\Permission;
+
class ZoneObjectDashlet extends Dashlet
{
protected $icon = 'globe';
- protected $requiredStats = array('zone');
+ protected $requiredStats = ['zone'];
public function getTitle()
{
@@ -20,6 +22,6 @@ class ZoneObjectDashlet extends Dashlet
public function listRequiredPermissions()
{
- return array('director/admin');
+ return [Permission::ADMIN];
}
}
diff --git a/library/Director/Data/AssignFilterHelper.php b/library/Director/Data/AssignFilterHelper.php
index b0253cf..7448c51 100644
--- a/library/Director/Data/AssignFilterHelper.php
+++ b/library/Director/Data/AssignFilterHelper.php
@@ -135,7 +135,7 @@ class AssignFilterHelper
$parts = array();
foreach (preg_split('~\*~', $expression) as $part) {
- $parts[] = preg_quote($part);
+ $parts[] = preg_quote($part, '/');
}
// match() is case insensitive
$pattern = '/^' . implode('.*', $parts) . '$/i';
diff --git a/library/Director/Data/Db/DbDataFormatter.php b/library/Director/Data/Db/DbDataFormatter.php
index d6e4eeb..91fc776 100644
--- a/library/Director/Data/Db/DbDataFormatter.php
+++ b/library/Director/Data/Db/DbDataFormatter.php
@@ -6,7 +6,7 @@ use InvalidArgumentException;
class DbDataFormatter
{
- public static function normalizeBoolean($value)
+ public static function normalizeBoolean($value): ?string
{
if ($value === 'y' || $value === '1' || $value === true || $value === 1) {
return 'y';
@@ -20,7 +20,19 @@ class DbDataFormatter
throw new InvalidArgumentException(sprintf(
'Got invalid boolean: %s',
- var_export($value, 1)
+ var_export($value, true)
));
}
+
+ public static function booleanForDbValue($value): ?bool
+ {
+ if ($value === 'y') {
+ return true;
+ }
+ if ($value === 'n') {
+ return false;
+ }
+
+ return $value; // let this fail elsewhere, if not null
+ }
}
diff --git a/library/Director/Data/Db/DbObject.php b/library/Director/Data/Db/DbObject.php
index 6ecae8b..114b61b 100644
--- a/library/Director/Data/Db/DbObject.php
+++ b/library/Director/Data/Db/DbObject.php
@@ -80,6 +80,9 @@ abstract class DbObject
protected $binaryProperties = [];
+ /* key/value!! */
+ protected $booleans = [];
+
/**
* Filled with object instances when prefetchAll is used
*/
@@ -346,6 +349,16 @@ abstract class DbObject
return $this->$func($value);
}
+ if ($this->getUuidColumn() === $key) {
+ if (strlen($value) > 16) {
+ $value = Uuid::fromString($value)->getBytes();
+ }
+ }
+
+ if ($this->propertyIsBoolean($key)) {
+ $value = DbDataFormatter::normalizeBoolean($value);
+ }
+
if (! $this->hasProperty($key)) {
throw new InvalidArgumentException(sprintf(
'Trying to set invalid key "%s"',
@@ -372,7 +385,10 @@ abstract class DbObject
return $this;
}
if ($key === 'id' || substr($key, -3) === '_id') {
- if ((int) $value === (int) $this->properties[$key]) {
+ if ($value !== null
+ && $this->properties[$key] !== null
+ && (int) $value === (int) $this->properties[$key]
+ ) {
return $this;
}
}
@@ -553,7 +569,7 @@ abstract class DbObject
/**
* Unique key name
*
- * @return string
+ * @return string|array
*/
public function getKeyName()
{
@@ -706,8 +722,7 @@ abstract class DbObject
*/
protected function loadFromDb()
{
- $select = $this->db->select()->from($this->table)->where($this->createWhere());
- $properties = $this->db->fetchRow($select);
+ $properties = $this->db->fetchRow($this->prepareObjectQuery());
if (empty($properties)) {
if (is_array($this->getKeyName())) {
@@ -728,6 +743,11 @@ abstract class DbObject
return $this->setDbProperties($properties);
}
+ public function prepareObjectQuery()
+ {
+ return $this->db->select()->from($this->table)->where($this->createWhere());
+ }
+
/**
* @param object|array $row
* @param Db $db
@@ -878,6 +898,11 @@ abstract class DbObject
return in_array($column, $this->binaryProperties) || $this->getUuidColumn() === $column;
}
+ public function propertyIsBoolean($property)
+ {
+ return array_key_exists($property, $this->booleans);
+ }
+
/**
* Store object to database
*
@@ -959,7 +984,7 @@ abstract class DbObject
$this->table,
$this->getLogId(),
$e->getMessage(),
- var_export($this->getProperties(), 1) // TODO: Remove properties
+ var_export($this->getProperties(), true) // TODO: Remove properties
));
}
@@ -1027,7 +1052,7 @@ abstract class DbObject
if ($this->hasUuidColumn() && $this->properties[$this->uuidColumn] !== null) {
return $this->db->quoteInto(
sprintf('%s = ?', $this->getUuidColumn()),
- $this->connection->quoteBinary($this->getUniqueId()->getBytes())
+ $this->connection->quoteBinary($this->getOriginalProperty($this->uuidColumn))
);
}
if ($id = $this->getAutoincId()) {
@@ -1302,6 +1327,40 @@ abstract class DbObject
}
/**
+ * @param $id
+ * @param DbConnection $connection
+ * @return static
+ */
+ public static function loadOptional($id, DbConnection $connection): ?DbObject
+ {
+ if ($prefetched = static::getPrefetched($id)) {
+ return $prefetched;
+ }
+ /** @var DbObject $obj */
+ $obj = new static();
+
+ if (self::$dbObjectStore !== null && $obj->hasUuidColumn()) {
+ $table = $obj->getTableName();
+ assert($connection instanceof Db);
+ $uuid = UuidLookup::findUuidForKey($id, $table, $connection, self::$dbObjectStore->getBranch());
+ if ($uuid) {
+ return self::$dbObjectStore->load($table, $uuid);
+ }
+
+ return null;
+ }
+
+ $obj->setConnection($connection)->setKey($id);
+ $properties = $connection->getDbAdapter()->fetchRow($obj->prepareObjectQuery());
+ if (empty($properties)) {
+ return null;
+ }
+
+ $obj->setDbProperties($properties);
+ return $obj;
+ }
+
+ /**
* @param DbConnection $connection
* @param \Zend_Db_Select $query
* @param string|null $keyColumn
@@ -1436,7 +1495,7 @@ abstract class DbObject
));
}
- public static function loadWithUniqueId(UuidInterface $uuid, DbConnection $connection)
+ public static function loadWithUniqueId(UuidInterface $uuid, DbConnection $connection): ?DbObject
{
$db = $connection->getDbAdapter();
$obj = new static;
diff --git a/library/Director/Data/Db/ServiceSetQueryBuilder.php b/library/Director/Data/Db/ServiceSetQueryBuilder.php
index 7841d1e..597fe0e 100644
--- a/library/Director/Data/Db/ServiceSetQueryBuilder.php
+++ b/library/Director/Data/Db/ServiceSetQueryBuilder.php
@@ -27,6 +27,8 @@ class ServiceSetQueryBuilder
/** @var \Zend_Db_Adapter_Abstract */
protected $db;
+ protected $searchColumns = [];
+
/**
* @param ?UuidInterface $uuid
*/
diff --git a/library/Director/Data/Exporter.php b/library/Director/Data/Exporter.php
index a2e3191..1a3cfcb 100644
--- a/library/Director/Data/Exporter.php
+++ b/library/Director/Data/Exporter.php
@@ -2,10 +2,14 @@
namespace Icinga\Module\Director\Data;
+use gipfl\Json\JsonString;
use gipfl\ZfDb\Adapter\Adapter;
+use Icinga\Authentication\Auth;
+use Icinga\Module\Director\Data\Db\DbDataFormatter;
use Icinga\Module\Director\Data\Db\DbObject;
use Icinga\Module\Director\Data\Db\DbObjectWithSettings;
use Icinga\Module\Director\Db;
+use Icinga\Module\Director\DirectorObject\Automation\Basket;
use Icinga\Module\Director\Objects\DirectorDatafield;
use Icinga\Module\Director\Objects\DirectorDatalist;
use Icinga\Module\Director\Objects\DirectorDatalistEntry;
@@ -18,6 +22,7 @@ use Icinga\Module\Director\Objects\IcingaTemplateChoice;
use Icinga\Module\Director\Objects\ImportSource;
use Icinga\Module\Director\Objects\InstantiatedViaHook;
use Icinga\Module\Director\Objects\SyncRule;
+use Ramsey\Uuid\Uuid;
use RuntimeException;
class Exporter
@@ -68,6 +73,11 @@ class Exporter
$props = $chosen;
}
+ if ($column = $object->getUuidColumn()) {
+ if ($uuid = $object->get($column)) {
+ $props[$column] = Uuid::fromBytes($uuid)->toString();
+ }
+ }
ksort($props);
return (object) $props;
@@ -152,10 +162,10 @@ class Exporter
throw new RuntimeException('Not yet');
}
$props['services'] = [];
- foreach ($object->getServiceObjects() as $serviceObject) {
- $props['services'][$serviceObject->getObjectName()] = $this->export($serviceObject);
+ foreach ($object->getServices() as $serviceObject) {
+ $props['services'][] = $this->export($serviceObject);
}
- ksort($props['services']);
+ usort($props['services'], [$this, 'sortByName']);
} elseif ($object instanceof IcingaHost) {
if ($this->exportHostServices) {
$services = [];
@@ -168,10 +178,15 @@ class Exporter
}
}
+ protected function sortByName($left, $right)
+ {
+ return $left->object_name < $right->object_name ? '-1' : '1';
+ }
+
public function serviceLoader()
{
if ($this->serviceLoader === null) {
- $this->serviceLoader = new HostServiceLoader($this->connection);
+ $this->serviceLoader = new HostServiceLoader($this->connection, Auth::getInstance());
$this->serviceLoader->resolveObjects($this->resolveObjects);
}
@@ -241,6 +256,12 @@ class Exporter
protected function exportDbObject(DbObject $object)
{
$props = $object->getProperties();
+ foreach ($props as $key => &$value) {
+ if ($object->propertyIsBoolean($key)) {
+ $value = DbDataFormatter::booleanForDbValue($value);
+ }
+ }
+ unset($value);
if ($object instanceof DbObjectWithSettings) {
if ($object instanceof InstantiatedViaHook) {
$props['settings'] = (object) $object->getInstance()->exportSettings();
@@ -248,6 +269,11 @@ class Exporter
$props['settings'] = (object) $object->getSettings(); // Already sorted
}
}
+ if ($object instanceof Basket) {
+ if (isset($props['objects']) && is_string($props['objects'])) {
+ $props['objects'] = JsonString::decode($props['objects']);
+ }
+ }
unset($props['uuid']); // Not yet
if (! $this->showDefaults) {
foreach ($props as $key => $value) {
@@ -279,16 +305,7 @@ class Exporter
protected function exportDatalistEntries(DirectorDatalist $list)
{
$entries = [];
- $id = $list->get('id');
- if ($id === null) {
- return $entries;
- }
-
- $dbEntries = DirectorDatalistEntry::loadAllForList($list);
- // Hint: they are loaded with entry_name key
- ksort($dbEntries);
-
- foreach ($dbEntries as $entry) {
+ foreach ($list->getEntries() as $name => $entry) {
if ($entry->shouldBeRemoved()) {
continue;
}
diff --git a/library/Director/Data/FieldReferenceLoader.php b/library/Director/Data/FieldReferenceLoader.php
index 1e3d92e..99a9925 100644
--- a/library/Director/Data/FieldReferenceLoader.php
+++ b/library/Director/Data/FieldReferenceLoader.php
@@ -29,12 +29,12 @@ class FieldReferenceLoader
}
$type = $object->getShortTableName();
$res = $db->fetchAll(
- $db->select()->from(['f' => "icinga_${type}_field"], [
+ $db->select()->from(['f' => "icinga_{$type}_field"], [
'f.datafield_id',
'f.is_required',
'f.var_filter',
])->join(['df' => 'director_datafield'], 'df.id = f.datafield_id', [])
- ->where("${type}_id = ?", (int) $id)
+ ->where("{$type}_id = ?", (int) $id)
->order('varname ASC')
);
diff --git a/library/Director/Data/HostServiceLoader.php b/library/Director/Data/HostServiceLoader.php
index 4cc4b96..c8bd8b9 100644
--- a/library/Director/Data/HostServiceLoader.php
+++ b/library/Director/Data/HostServiceLoader.php
@@ -4,6 +4,7 @@ namespace Icinga\Module\Director\Data;
use gipfl\IcingaWeb2\Table\QueryBasedTable;
use gipfl\ZfDb\Select;
+use Icinga\Authentication\Auth;
use Icinga\Data\SimpleQuery;
use Icinga\Module\Director\Db;
use Icinga\Module\Director\Db\AppliedServiceSetLoader;
@@ -26,21 +27,26 @@ class HostServiceLoader
/** @var \Zend_Db_Adapter_Abstract */
protected $db;
+ /** @var Auth */
+ protected $auth;
+
/** @var bool */
protected $resolveHostServices = false;
/** @var bool */
protected $resolveObjects = false;
- public function __construct(Db $connection)
+ public function __construct(Db $connection, Auth $auth)
{
$this->connection = $connection;
$this->db = $connection->getDbAdapter();
+ $this->auth = $auth;
}
public function fetchServicesForHost(IcingaHost $host)
{
- $table = (new ObjectsTableService($this->connection))->setHost($host);
+ $table = (new ObjectsTableService($this->connection, $this->auth))
+ ->setHost($host);
$services = $this->fetchServicesForTable($table);
if ($this->resolveHostServices) {
foreach ($this->fetchAllServicesForHost($host) as $service) {
@@ -69,7 +75,7 @@ class HostServiceLoader
/** @var IcingaHost[] $parents */
$parents = IcingaTemplateRepository::instanceByObject($host)->getTemplatesFor($host, true);
foreach ($parents as $parent) {
- $table = (new ObjectsTableService($this->connection))
+ $table = (new ObjectsTableService($this->connection, $this->auth))
->setHost($parent)
->setInheritedBy($host);
foreach ($this->fetchServicesForTable($table) as $service) {
diff --git a/library/Director/Data/ObjectImporter.php b/library/Director/Data/ObjectImporter.php
new file mode 100644
index 0000000..231ad1c
--- /dev/null
+++ b/library/Director/Data/ObjectImporter.php
@@ -0,0 +1,168 @@
+<?php
+
+namespace Icinga\Module\Director\Data;
+
+use gipfl\Json\JsonDecodeException;
+use gipfl\Json\JsonString;
+use Icinga\Module\Director\Data\Db\DbObject;
+use Icinga\Module\Director\Db;
+use Icinga\Module\Director\DirectorObject\Automation\Basket;
+use Icinga\Module\Director\Objects\DirectorJob;
+use Icinga\Module\Director\Objects\IcingaHost;
+use Icinga\Module\Director\Objects\IcingaService;
+use Icinga\Module\Director\Objects\IcingaServiceSet;
+use Icinga\Module\Director\Objects\ImportSource;
+use Icinga\Module\Director\Objects\SyncRule;
+use InvalidArgumentException;
+use Ramsey\Uuid\Uuid;
+use stdClass;
+
+class ObjectImporter
+{
+ protected static $templatesOnly = [
+ IcingaHost::class,
+ IcingaService::class,
+ IcingaServiceSet::class,
+ ];
+
+ /** @var Db */
+ protected $db;
+
+ public function __construct(Db $db)
+ {
+ $this->db = $db;
+ }
+
+ /**
+ * @param class-string|DbObject $implementation
+ * @param stdClass $plain
+ * @return DbObject
+ * @throws JsonDecodeException
+ */
+ public function import(string $implementation, stdClass $plain): DbObject
+ {
+ $this->assertTemplate($implementation, $plain);
+ $this->fixRelations($implementation, $plain);
+ $this->applyOtherWorkarounds($implementation, $plain);
+ $this->fixLegacyBaskets($implementation, $plain);
+ $this->fixSubObjects($implementation, $plain);
+
+ $object = $this->loadExistingObject($implementation, $plain);
+ if ($object === null) {
+ $object = $implementation::create([], $this->db);
+ }
+
+ $properties = (array) $plain;
+ unset($properties['fields']);
+ unset($properties['originalId']);
+ if ($implementation === Basket::class) {
+ if (isset($properties['objects']) && is_string($properties['objects'])) {
+ $properties['objects'] = JsonString::decode($properties['objects']);
+ }
+ }
+ $object->setProperties($properties);
+
+ return $object;
+ }
+
+ protected function fixLegacyBaskets(string $implementation, stdClass $plain)
+ {
+ // TODO: Check, whether current export sets modifiers = [] in case there is none
+ if ($implementation == ImportSource::class) {
+ if (!isset($plain->modifiers)) {
+ $plain->modifiers = [];
+ }
+ }
+ }
+
+ protected function applyOtherWorkarounds(string $implementation, stdClass $plain)
+ {
+ if ($implementation === SyncRule::class) {
+ if (isset($plain->properties)) {
+ $plain->syncProperties = $plain->properties;
+ unset($plain->properties);
+ }
+ }
+ }
+
+ protected function fixSubObjects(string $implementation, stdClass $plain)
+ {
+ if ($implementation === IcingaServiceSet::class) {
+ foreach ($plain->services as $service) {
+ unset($service->fields);
+ }
+ // Hint: legacy baskets are carrying service names as object keys, new baskets have arrays
+ $plain->services = array_values((array) $plain->services);
+ }
+ }
+
+ protected function fixRelations(string $implementation, stdClass $plain)
+ {
+ if ($implementation === DirectorJob::class) {
+ $settings = $plain->settings;
+ $source = $settings->source ?? null;
+ if ($source && !isset($settings->source_id)) {
+ $settings->source_id = ImportSource::load($source, $this->db)->get('id');
+ unset($settings->source);
+ }
+ $rule = $settings->rule ?? null;
+ if ($rule && !isset($settings->rule_id)) {
+ $settings->rule_id = SyncRule::load($rule, $this->db)->get('id');
+ unset($settings->rule);
+ }
+ }
+ }
+
+ /**
+ * @param class-string<DbObject> $implementation
+ * @param stdClass $plain
+ * @return DbObject|null
+ */
+ protected function loadExistingObject(string $implementation, stdClass $plain): ?DbObject
+ {
+ if (isset($plain->uuid)
+ && $instance = $implementation::loadWithUniqueId(Uuid::fromString($plain->uuid), $this->db)
+ ) {
+ return $instance;
+ }
+
+ if ($implementation === IcingaService::class) {
+ $key = [
+ 'object_type' => 'template',
+ 'object_name' => $plain->object_name
+ ];
+ } else {
+ $dummy = $implementation::create();
+ $keyColumn = $dummy->getKeyName();
+ if (is_array($keyColumn)) {
+ if (empty($keyColumn)) {
+ throw new \RuntimeException("$implementation has an empty keyColumn array");
+ }
+ $key = [];
+ foreach ($keyColumn as $column) {
+ if (isset($plain->$column)) {
+ $key[$column] = $plain->$column;
+ }
+ }
+ } else {
+ $key = $plain->$keyColumn;
+ }
+ }
+
+ return $implementation::loadOptional($key, $this->db);
+ }
+
+ protected function assertTemplate(string $implementation, stdClass $plain)
+ {
+ if (! in_array($implementation, self::$templatesOnly)) {
+ return;
+ }
+ if ($plain->object_type !== 'template') {
+ throw new InvalidArgumentException(sprintf(
+ 'Can import only Templates, got "%s" for "%s"',
+ $plain->object_type,
+ $plain->name
+ ));
+ }
+ }
+}
diff --git a/library/Director/Data/PropertyMangler.php b/library/Director/Data/PropertyMangler.php
index a457f1d..40b2570 100644
--- a/library/Director/Data/PropertyMangler.php
+++ b/library/Director/Data/PropertyMangler.php
@@ -19,7 +19,7 @@ class PropertyMangler
throw new InvalidArgumentException(sprintf(
'I can only append to arrays, %s is %s',
$key,
- var_export($current, 1)
+ var_export($current, true)
));
}
@@ -52,7 +52,7 @@ class PropertyMangler
throw new InvalidArgumentException(sprintf(
'I can only remove strings or from arrays, %s is %s',
$key,
- var_export($current, 1)
+ var_export($current, true)
));
}
}
diff --git a/library/Director/Db/Branch/Branch.php b/library/Director/Db/Branch/Branch.php
index cd68ff0..c99b1bd 100644
--- a/library/Director/Db/Branch/Branch.php
+++ b/library/Director/Db/Branch/Branch.php
@@ -2,11 +2,11 @@
namespace Icinga\Module\Director\Db\Branch;
+use Icinga\Application\Hook;
use Icinga\Application\Icinga;
use Icinga\Authentication\Auth;
use Icinga\Module\Director\Db;
use Icinga\Module\Director\Hook\BranchSupportHook;
-use Icinga\Web\Hook;
use Icinga\Web\Request;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
@@ -45,7 +45,7 @@ class Branch
$row->uuid = stream_get_contents($row->uuid);
}
if (strlen($row->uuid) !== 16) {
- throw new RuntimeException('Valid UUID expected, got ' . var_export($row->uuid, 1));
+ throw new RuntimeException('Valid UUID expected, got ' . var_export($row->uuid, true));
}
$self->branchUuid = Uuid::fromBytes(Db\DbUtil::binaryResult($row->uuid));
$self->name = $row->branch_name;
diff --git a/library/Director/Db/Branch/BranchActivity.php b/library/Director/Db/Branch/BranchActivity.php
index 3812e75..e95ac7d 100644
--- a/library/Director/Db/Branch/BranchActivity.php
+++ b/library/Director/Db/Branch/BranchActivity.php
@@ -294,7 +294,13 @@ class BranchActivity
*/
public function getObjectName()
{
- return $this->getProperty('object_name', 'unknown object name');
+ if ($this->objectTable === BranchSupport::TABLE_ICINGA_SERVICE && $host = $this->getProperty('host')) {
+ $suffix = " ($host)";
+ } else {
+ $suffix = '';
+ }
+
+ return $this->getProperty('object_name', 'unknown object name') . $suffix;
}
/**
diff --git a/library/Director/Db/Branch/BranchStore.php b/library/Director/Db/Branch/BranchStore.php
index 196d079..13971b9 100644
--- a/library/Director/Db/Branch/BranchStore.php
+++ b/library/Director/Db/Branch/BranchStore.php
@@ -56,6 +56,7 @@ class BranchStore
$rows = $db->fetchAll($db->select()->from($table)->where('branch_uuid = ?', $oldQuotedUuid));
foreach ($rows as $row) {
$modified = (array)$row;
+ $this->quoteBinaryProperties($modified);
$modified['branch_uuid'] = $quotedUuid;
if ($table === self::TABLE_ACTIVITY) {
$modified['timestamp_ns'] = round($modified['timestamp_ns'] / 1000000);
@@ -68,6 +69,21 @@ class BranchStore
return $this->fetchBranchByName($newName);
}
+ protected function quoteBinaryProperties(&$properties)
+ {
+ foreach ($properties as $key => $value) {
+ if ($this->isBinaryColumn($key)) {
+ $properties[$key] = $this->connection->quoteBinary($value);
+ }
+ }
+ }
+
+ protected function isBinaryColumn($key)
+ {
+ return (strpos($key, 'uuid') !== false || strpos($key, 'checksum') !== false)
+ && strpos($key, 'hex') === false;
+ }
+
protected function runTransaction($callback)
{
$db = $this->db;
@@ -100,7 +116,6 @@ class BranchStore
}
}
});
-
}
protected function newFromDbResult($query)
diff --git a/library/Director/Db/Branch/PreferredBranchSupport.php b/library/Director/Db/Branch/PreferredBranchSupport.php
new file mode 100644
index 0000000..3463bfe
--- /dev/null
+++ b/library/Director/Db/Branch/PreferredBranchSupport.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace Icinga\Module\Director\Db\Branch;
+
+use Icinga\Authentication\Auth;
+
+interface PreferredBranchSupport
+{
+ public function hasPreferredBranch(Auth $auth): bool;
+}
diff --git a/library/Director/Db/Branch/UuidLookup.php b/library/Director/Db/Branch/UuidLookup.php
index b340e07..4db7866 100644
--- a/library/Director/Db/Branch/UuidLookup.php
+++ b/library/Director/Db/Branch/UuidLookup.php
@@ -15,18 +15,13 @@ use function is_string;
class UuidLookup
{
/**
- * @param Db $connection
- * @param Branch $branch
- * @param string $objectType
* @param int|string $key
- * @param IcingaHost|null $host
- * @param IcingaServiceSet $set
* @return ?UuidInterface
*/
public static function findServiceUuid(
Db $connection,
Branch $branch,
- $objectType = null,
+ ?string $objectType = null,
$key = null,
IcingaHost $host = null,
IcingaServiceSet $set = null
@@ -37,11 +32,20 @@ class UuidLookup
$query->where('object_type = ?', $objectType);
}
$query = self::addKeyToQuery($connection, $query, $key);
- if ($host) {
- $query->where('host_id = ?', $host->get('id'));
- }
if ($set) {
- $query->where('service_set_id = ?', $set->get('id'));
+ $setId = $set->get('id');
+ if ($setId === null) {
+ $query->where('1 = 0');
+ } else {
+ $query->where('service_set_id = ?', $setId);
+ }
+ } elseif ($host) {
+ $hostId = $host->get('id');
+ if ($hostId === null) {
+ $query->where('1 = 0');
+ } else {
+ $query->where('host_id = ?', $hostId);
+ }
}
$uuid = self::fetchOptionalUuid($connection, $query);
@@ -100,7 +104,7 @@ class UuidLookup
$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();
+ $key['host'] = IcingaHost::loadWithAutoIncId((int) $key['host_id'], $connection)->getObjectName();
unset($key['host_id']);
}
$query = self::addKeyToQuery($connection, $db->select()->from("branched_$table", 'uuid'), $key);
diff --git a/library/Director/Db/Cache/CustomVariableCache.php b/library/Director/Db/Cache/CustomVariableCache.php
index 243ecae..ee2b9ef 100644
--- a/library/Director/Db/Cache/CustomVariableCache.php
+++ b/library/Director/Db/Cache/CustomVariableCache.php
@@ -76,9 +76,4 @@ class CustomVariableCache
return new CustomVariables();
}
}
-
- public function __destruct()
- {
- unset($this->db);
- }
}
diff --git a/library/Director/Db/IcingaObjectFilterHelper.php b/library/Director/Db/IcingaObjectFilterHelper.php
index 2eef406..2d9f8f3 100644
--- a/library/Director/Db/IcingaObjectFilterHelper.php
+++ b/library/Director/Db/IcingaObjectFilterHelper.php
@@ -5,6 +5,7 @@ namespace Icinga\Module\Director\Db;
use Icinga\Module\Director\Objects\IcingaObject;
use Icinga\Module\Director\Resolver\TemplateTree;
use InvalidArgumentException;
+use Ramsey\Uuid\UuidInterface;
use RuntimeException;
use Zend_Db_Select as ZfSelect;
@@ -30,7 +31,7 @@ class IcingaObjectFilterHelper
throw new InvalidArgumentException(sprintf(
'Numeric ID or IcingaObject expected, got %s',
// TODO: just type/class info?
- var_export($id, 1)
+ var_export($id, true)
));
}
}
@@ -46,20 +47,49 @@ class IcingaObjectFilterHelper
ZfSelect $query,
$template,
$tableAlias = 'o',
- $inheritanceType = self::INHERIT_DIRECT
+ $inheritanceType = self::INHERIT_DIRECT,
+ UuidInterface $branchuuid = null
) {
$i = $tableAlias . 'i';
$o = $tableAlias;
$type = $template->getShortTableName();
$db = $template->getDb();
$id = static::wantId($template);
+
+ if ($branchuuid) {
+ if ($inheritanceType === self::INHERIT_DIRECT) {
+ return $query->where('imports LIKE \'%"' . $template->getObjectName() . '"%\'');
+ } elseif ($inheritanceType === self::INHERIT_INDIRECT
+ || $inheritanceType === self::INHERIT_DIRECT_OR_INDIRECT
+ ) {
+ $tree = new TemplateTree($type, $template->getConnection());
+ $templateNames = $tree->getDescendantsFor($template);
+
+ if ($inheritanceType === self::INHERIT_DIRECT_OR_INDIRECT) {
+ $templateNames[] = $template->getObjectName();
+ }
+
+ if (empty($templateNames)) {
+ $condition = '(1 = 0)';
+ } else {
+ $condition = 'imports LIKE \'%"' . array_pop($templateNames) . '"%\'';
+
+ foreach ($templateNames as $templateName) {
+ $condition .= " OR imports LIKE '%\"$templateName\"%'";
+ }
+ }
+
+ return $query->where($condition);
+ }
+ }
+
$sub = $db->select()->from(
- array($i => "icinga_${type}_inheritance"),
+ array($i => "icinga_{$type}_inheritance"),
array('e' => '(1)')
- )->where("$i.${type}_id = $o.id");
+ )->where("$i.{$type}_id = $o.id");
if ($inheritanceType === self::INHERIT_DIRECT) {
- $sub->where("$i.parent_${type}_id = ?", $id);
+ $sub->where("$i.parent_{$type}_id = ?", $id);
} elseif ($inheritanceType === self::INHERIT_INDIRECT
|| $inheritanceType === self::INHERIT_DIRECT_OR_INDIRECT
) {
@@ -72,7 +102,7 @@ class IcingaObjectFilterHelper
if (empty($ids)) {
$sub->where('(1 = 0)');
} else {
- $sub->where("$i.parent_${type}_id IN (?)", $ids);
+ $sub->where("$i.parent_{$type}_id IN (?)", $ids);
}
} else {
throw new RuntimeException(sprintf(
@@ -95,12 +125,12 @@ class IcingaObjectFilterHelper
$query->where('(1 = 0)');
} else {
$sub = $query->getAdapter()->select()->from(
- array('go' => "icinga_${type}group_${type}"),
+ array('go' => "icinga_{$type}group_{$type}"),
array('e' => '(1)')
)->join(
- array('g' => "icinga_${type}group"),
- "go.${type}group_id = g.id"
- )->where("go.${type}_id = ${tableAlias}.id")
+ array('g' => "icinga_{$type}group"),
+ "go.{$type}group_id = g.id"
+ )->where("go.{$type}_id = {$tableAlias}.id")
->where('g.object_name IN (?)', $groups);
$query->where('EXISTS ?', $sub);
@@ -118,13 +148,13 @@ class IcingaObjectFilterHelper
$query->where('(1 = 0)');
} else {
$sub = $query->getAdapter()->select()->from(
- array('go' => "icinga_${type}group_${type}_resolved"),
+ array('go' => "icinga_{$type}group_{$type}_resolved"),
array('e' => '(1)')
)->join(
- array('g' => "icinga_${type}group"),
- "go.${type}group_id = g.id",
+ array('g' => "icinga_{$type}group"),
+ "go.{$type}group_id = g.id",
[]
- )->where("go.${type}_id = ${tableAlias}.id")
+ )->where("go.{$type}_id = {$tableAlias}.id")
->where('g.object_name IN (?)', $groups);
$query->where('EXISTS ?', $sub);
diff --git a/library/Director/Db/Migrations.php b/library/Director/Db/Migrations.php
index 2310408..ad59329 100644
--- a/library/Director/Db/Migrations.php
+++ b/library/Director/Db/Migrations.php
@@ -90,7 +90,7 @@ class Migrations
public function applyPendingMigrations()
{
// Ensure we have enough time to migrate
- ini_set('max_execution_time', 0);
+ ini_set('max_execution_time', '0');
foreach ($this->getPendingMigrations() as $migration) {
$migration->apply($this->connection);
diff --git a/library/Director/DirectorObject/Automation/Basket.php b/library/Director/DirectorObject/Automation/Basket.php
index f7eb8e5..81ae107 100644
--- a/library/Director/DirectorObject/Automation/Basket.php
+++ b/library/Director/DirectorObject/Automation/Basket.php
@@ -4,8 +4,6 @@ namespace Icinga\Module\Director\DirectorObject\Automation;
use Icinga\Module\Director\Core\Json;
use Icinga\Module\Director\Data\Db\DbObject;
-use Icinga\Module\Director\Db;
-use Icinga\Module\Director\Exception\DuplicateKeyException;
/**
* Class Basket
@@ -15,16 +13,17 @@ use Icinga\Module\Director\Exception\DuplicateKeyException;
*/
class Basket extends DbObject implements ExportInterface
{
- const SELECTION_ALL = true;
- const SELECTION_NONE = false;
+ const SELECTION_ALL = 'ALL';
+ const SELECTION_NONE = 'IGNORE';
+ const SELECTION_CUSTOM = '[]';
protected $table = 'director_basket';
protected $keyName = 'basket_name';
- protected $chosenObjects = [];
+ protected $uuidColumn = 'uuid';
- protected $protectedFormerChosenObjects;
+ protected $chosenObjects = [];
protected $defaultProperties = [
'uuid' => null,
@@ -69,44 +68,6 @@ class Basket extends DbObject implements ExportInterface
return $this->get('basket_name');
}
- public function export()
- {
- $result = $this->getProperties();
- unset($result['uuid']);
- $result['objects'] = Json::decode($result['objects']);
- ksort($result);
-
- return (object) $result;
- }
-
- /**
- * @param $plain
- * @param Db $db
- * @param bool $replace
- * @return static
- * @throws DuplicateKeyException
- * @throws \Icinga\Exception\NotFoundError
- */
- public static function import($plain, Db $db, $replace = false)
- {
- $properties = (array) $plain;
- $name = $properties['basket_name'];
-
- if ($replace && static::exists($name, $db)) {
- $object = static::load($name, $db);
- } elseif (static::exists($name, $db)) {
- throw new DuplicateKeyException(
- 'Basket "%s" already exists',
- $name
- );
- } else {
- $object = static::create([], $db);
- }
- $object->setProperties($properties);
-
- return $object;
- }
-
public function supportsCustomSelectionFor($type)
{
if (! array_key_exists($type, $this->chosenObjects)) {
@@ -121,7 +82,6 @@ class Basket extends DbObject implements ExportInterface
if (empty($objects)) {
$this->chosenObjects = [];
} else {
- $this->protectedFormerChosenObjects = $this->chosenObjects;
$this->chosenObjects = [];
foreach ((array) $objects as $type => $object) {
$this->addObjects($type, $object);
@@ -141,32 +101,22 @@ class Basket extends DbObject implements ExportInterface
{
BasketSnapshot::assertValidType($type);
// '1' -> from Form!
- if ($objects === 'ALL') {
+ if ($objects === self::SELECTION_ALL) {
$objects = true;
- } elseif ($objects === null || $objects === 'IGNORE') {
+ } elseif ($objects === null || $objects === self::SELECTION_NONE) {
return;
- } elseif ($objects === '[]' || is_array($objects)) {
+ } elseif ($objects === self::SELECTION_CUSTOM || is_array($objects)) {
if (! isset($this->chosenObjects[$type]) || ! is_array($this->chosenObjects[$type])) {
$this->chosenObjects[$type] = [];
}
- if (isset($this->protectedFormerChosenObjects[$type])) {
- if (is_array($this->protectedFormerChosenObjects[$type])) {
- $this->chosenObjects[$type] = $this->protectedFormerChosenObjects[$type];
- } else {
- $this->chosenObjects[$type] = [];
- }
- }
-
- if ($objects === '[]') {
+ if ($objects === self::SELECTION_CUSTOM) {
$objects = [];
}
}
if ($objects === true) {
$this->chosenObjects[$type] = true;
- } elseif ($objects === '0') {
- // nothing
- } else {
+ } elseif ($objects !== '0') { // TODO: what would generate '0'?
foreach ($objects as $object) {
$this->addObject($type, $object);
}
diff --git a/library/Director/DirectorObject/Automation/BasketDiff.php b/library/Director/DirectorObject/Automation/BasketDiff.php
new file mode 100644
index 0000000..8dbb423
--- /dev/null
+++ b/library/Director/DirectorObject/Automation/BasketDiff.php
@@ -0,0 +1,121 @@
+<?php
+
+namespace Icinga\Module\Director\DirectorObject\Automation;
+
+use gipfl\Json\JsonString;
+use Icinga\Module\Director\Data\Exporter;
+use Icinga\Module\Director\Data\ObjectImporter;
+use Icinga\Module\Director\Db;
+use Icinga\Module\Director\Objects\DirectorDatalist;
+use Ramsey\Uuid\UuidInterface;
+use stdClass;
+
+class BasketDiff
+{
+ /** @var Db */
+ protected $db;
+ /** @var ObjectImporter */
+ protected $importer;
+ /** @var Exporter */
+ protected $exporter;
+ /** @var BasketSnapshot */
+ protected $snapshot;
+ /** @var ?stdClass */
+ protected $objects = null;
+ /** @var BasketSnapshotFieldResolver */
+ protected $fieldResolver;
+
+ public function __construct(BasketSnapshot $snapshot, Db $db)
+ {
+ $this->db = $db;
+ $this->importer = new ObjectImporter($db);
+ $this->exporter = new Exporter($db);
+ $this->snapshot = $snapshot;
+ }
+
+ public function hasChangedFor(string $type, string $key, ?UuidInterface $uuid = null): bool
+ {
+ return $this->getCurrentString($type, $key, $uuid) !== $this->getBasketString($type, $key);
+ }
+
+ public function getCurrentString(string $type, string $key, ?UuidInterface $uuid = null): string
+ {
+ $current = $this->getCurrent($type, $key, $uuid);
+ return $current ? JsonString::encode($current, JSON_PRETTY_PRINT) : '';
+ }
+
+ public function getBasketString(string $type, string $key): string
+ {
+ return JsonString::encode($this->getBasket($type, $key), JSON_PRETTY_PRINT);
+ }
+
+ protected function getFieldResolver(): BasketSnapshotFieldResolver
+ {
+ if ($this->fieldResolver === null) {
+ $this->fieldResolver = new BasketSnapshotFieldResolver($this->getBasketObjects(), $this->db);
+ }
+
+ return $this->fieldResolver;
+ }
+
+ protected function getCurrent(string $type, string $key, ?UuidInterface $uuid = null): ?stdClass
+ {
+ if ($uuid && $current = BasketSnapshot::instanceByUuid($type, $uuid, $this->db)) {
+ $exported = $this->exporter->export($current);
+ $this->getFieldResolver()->tweakTargetIds($exported);
+ } elseif ($current = BasketSnapshot::instanceByIdentifier($type, $key, $this->db)) {
+ $exported = $this->exporter->export($current);
+ $this->getFieldResolver()->tweakTargetIds($exported);
+ } else {
+ $exported = null;
+ }
+ CompareBasketObject::normalize($exported);
+
+ return $exported;
+ }
+
+ protected function getBasket($type, $key): stdClass
+ {
+ $object = $this->getBasketObject($type, $key);
+ $fields = $object->fields ?? null;
+ $reExport = $this->exporter->export(
+ $this->importer->import(BasketSnapshot::getClassForType($type), $object)
+ );
+ if ($fields === null) {
+ unset($reExport->fields);
+ } else {
+ $reExport->fields = $fields;
+ }
+ CompareBasketObject::normalize($reExport);
+
+ return $reExport;
+ }
+
+ public function hasCurrentInstance(string $type, string $key, ?UuidInterface $uuid = null): bool
+ {
+ return $this->getCurrentInstance($type, $key, $uuid) !== null;
+ }
+
+ public function getCurrentInstance(string $type, string $key, ?UuidInterface $uuid = null)
+ {
+ if ($uuid && $instance = BasketSnapshot::instanceByUuid($type, $uuid, $this->db)) {
+ return $instance;
+ } else {
+ return BasketSnapshot::instanceByIdentifier($type, $key, $this->db);
+ }
+ }
+
+ public function getBasketObjects(): stdClass
+ {
+ if ($this->objects === null) {
+ $this->objects = JsonString::decode($this->snapshot->getJsonDump());
+ }
+
+ return $this->objects;
+ }
+
+ public function getBasketObject(string $type, string $key): stdClass
+ {
+ return $this->getBasketObjects()->$type->$key;
+ }
+}
diff --git a/library/Director/DirectorObject/Automation/BasketSnapshot.php b/library/Director/DirectorObject/Automation/BasketSnapshot.php
index 4ddf2ce..9638e49 100644
--- a/library/Director/DirectorObject/Automation/BasketSnapshot.php
+++ b/library/Director/DirectorObject/Automation/BasketSnapshot.php
@@ -2,10 +2,11 @@
namespace Icinga\Module\Director\DirectorObject\Automation;
+use gipfl\Json\JsonDecodeException;
use gipfl\Json\JsonEncodeException;
use gipfl\Json\JsonString;
-use Icinga\Module\Director\Core\Json;
use Icinga\Module\Director\Data\Exporter;
+use Icinga\Module\Director\Data\ObjectImporter;
use Icinga\Module\Director\Db;
use Icinga\Module\Director\Data\Db\DbObject;
use Icinga\Module\Director\Objects\DirectorDatafield;
@@ -29,7 +30,9 @@ use Icinga\Module\Director\Objects\IcingaUserGroup;
use Icinga\Module\Director\Objects\ImportSource;
use Icinga\Module\Director\Objects\SyncRule;
use InvalidArgumentException;
+use Ramsey\Uuid\UuidInterface;
use RuntimeException;
+use stdClass;
class BasketSnapshot extends DbObject
{
@@ -217,16 +220,11 @@ class BasketSnapshot extends DbObject
/**
* @param Db $connection
- * @param bool $replace
* @throws \Icinga\Exception\NotFoundError
*/
- public function restoreTo(Db $connection, $replace = true)
+ public function restoreTo(Db $connection)
{
- static::restoreJson(
- $this->getJsonDump(),
- $connection,
- $replace
- );
+ static::restoreJson($this->getJsonDump(), $connection);
}
/**
@@ -240,61 +238,49 @@ class BasketSnapshot extends DbObject
'basket_uuid' => $basket->get('uuid')
]);
$snapshot->objects = [];
- foreach ((array) Json::decode($string) as $type => $objects) {
+ foreach ((array) JsonString::decode($string) as $type => $objects) {
$snapshot->objects[$type] = (array) $objects;
}
return $snapshot;
}
- public static function restoreJson($string, Db $connection, $replace = true)
+ public static function restoreJson($string, Db $connection)
{
- $snapshot = new static();
- $snapshot->restoreObjects(
- Json::decode($string),
- $connection,
- $replace
- );
+ (new static())->restoreObjects(JsonString::decode($string), $connection);
}
/**
- * @param $all
- * @param Db $connection
- * @param bool $replace
* @throws \Icinga\Module\Director\Exception\DuplicateKeyException
* @throws \Zend_Db_Adapter_Exception
* @throws \Icinga\Exception\NotFoundError
+ * @throws JsonDecodeException
*/
- protected function restoreObjects($all, Db $connection, $replace = true)
+ protected function restoreObjects(stdClass $all, Db $connection)
{
$db = $connection->getDbAdapter();
$db->beginTransaction();
$fieldResolver = new BasketSnapshotFieldResolver($all, $connection);
- $this->restoreType($all, 'DataList', $fieldResolver, $connection, $replace);
- $this->restoreType($all, 'DatafieldCategory', $fieldResolver, $connection, $replace);
+ $this->restoreType($all, 'DataList', $fieldResolver, $connection);
+ $this->restoreType($all, 'DatafieldCategory', $fieldResolver, $connection);
$fieldResolver->storeNewFields();
foreach ($this->restoreOrder as $typeName) {
- $this->restoreType($all, $typeName, $fieldResolver, $connection, $replace);
+ $this->restoreType($all, $typeName, $fieldResolver, $connection);
}
$db->commit();
}
/**
- * @param $all
- * @param $typeName
- * @param BasketSnapshotFieldResolver $fieldResolver
- * @param Db $connection
- * @param $replace
* @throws \Icinga\Exception\NotFoundError
* @throws \Icinga\Module\Director\Exception\DuplicateKeyException
* @throws \Zend_Db_Adapter_Exception
+ * @throws JsonDecodeException
*/
public function restoreType(
- &$all,
- $typeName,
+ stdClass $all,
+ string $typeName,
BasketSnapshotFieldResolver $fieldResolver,
- Db $connection,
- $replace
+ Db $connection
) {
if (isset($all->$typeName)) {
$objects = (array) $all->$typeName;
@@ -302,11 +288,10 @@ class BasketSnapshot extends DbObject
return;
}
$class = static::getClassForType($typeName);
-
+ $importer = new ObjectImporter($connection);
$changed = [];
- foreach ($objects as $key => $object) {
- /** @var DbObject $new */
- $new = $class::import($object, $connection, $replace);
+ foreach ($objects as $object) {
+ $new = $importer->import($class, $object);
if ($new->hasBeenModified()) {
if ($new instanceof IcingaObject && $new->supportsImports()) {
/** @var ExportInterface $new */
@@ -325,7 +310,6 @@ class BasketSnapshot extends DbObject
$fieldResolver->relinkObjectFields($new, $object);
}
}
- $allObjects[spl_object_hash($new)] = $object;
}
/** @var IcingaObject $object */
@@ -334,7 +318,7 @@ class BasketSnapshot extends DbObject
}
foreach ($changed as $key => $new) {
// Store related fields. As objects might have formerly been
- // un-stored, let's to it right here
+ // un-stored, let's do it right here
if ($new instanceof IcingaObject) {
$fieldResolver->relinkObjectFields($new, $objects[$key]);
}
@@ -358,10 +342,9 @@ class BasketSnapshot extends DbObject
}
/**
- * @return BasketContent
* @throws \Icinga\Exception\NotFoundError
*/
- protected function getContent()
+ protected function getContent(): BasketContent
{
if ($this->content === null) {
$this->content = BasketContent::load($this->get('content_checksum'), $this->getConnection());
@@ -380,26 +363,25 @@ class BasketSnapshot extends DbObject
}
/**
- * @return string
- * @throws \Icinga\Exception\NotFoundError
+ * @throws \Icinga\Exception\NotFoundError|JsonEncodeException
*/
- public function getJsonSummary()
+ public function getJsonSummary(): string
{
if ($this->hasBeenLoadedFromDb()) {
return $this->getContent()->get('summary');
}
- return Json::encode($this->getSummary(), JSON_PRETTY_PRINT);
+ return JsonString::encode($this->getSummary(), JSON_PRETTY_PRINT);
}
/**
* @return array|mixed
- * @throws \Icinga\Exception\NotFoundError
+ * @throws \Icinga\Exception\NotFoundError|JsonDecodeException
*/
public function getSummary()
{
if ($this->hasBeenLoadedFromDb()) {
- return Json::decode($this->getContent()->get('summary'));
+ return JsonString::decode($this->getContent()->get('summary'));
}
$summary = [];
@@ -412,7 +394,7 @@ class BasketSnapshot extends DbObject
/**
* @return string
- * @throws \Icinga\Exception\NotFoundError
+ * @throws \Icinga\Exception\NotFoundError|JsonEncodeException
*/
public function getJsonDump()
{
@@ -428,7 +410,7 @@ class BasketSnapshot extends DbObject
try {
JsonString::encode($object);
} catch (JsonEncodeException $singleError) {
- $dump = var_export($object, 1);
+ $dump = var_export($object, true);
if (function_exists('iconv')) {
$dump = iconv('UTF-8', 'UTF-8//IGNORE', $dump);
}
@@ -486,6 +468,17 @@ class BasketSnapshot extends DbObject
}
/**
+ * @return ExportInterface|DbObject|null
+ */
+ public static function instanceByUuid(string $typeName, UuidInterface $uuid, Db $connection)
+ {
+ /** @var class-string<DbObject> $class */
+ $class = static::getClassForType($typeName);
+ /** @var ExportInterface $object */
+ return $class::loadWithUniqueId($uuid, $connection);
+ }
+
+ /**
* @param $typeName
* @param $identifier
* @param Db $connection
@@ -493,21 +486,17 @@ class BasketSnapshot extends DbObject
*/
public static function instanceByIdentifier($typeName, $identifier, Db $connection)
{
+ /** @var class-string<DbObject> $class */
$class = static::getClassForType($typeName);
- if (substr($class, -13) === 'IcingaService') {
+ if ($class === IcingaService::class) {
$identifier = [
'object_type' => 'template',
'object_name' => $identifier,
];
}
- /** @var ExportInterface $object */
- if ($class::exists($identifier, $connection)) {
- $object = $class::load($identifier, $connection);
- } else {
- $object = null;
- }
- return $object;
+ /** @var ExportInterface $object */
+ return $class::loadOptional($identifier, $connection);
}
/**
diff --git a/library/Director/DirectorObject/Automation/BasketSnapshotFieldResolver.php b/library/Director/DirectorObject/Automation/BasketSnapshotFieldResolver.php
index 4653255..e565f77 100644
--- a/library/Director/DirectorObject/Automation/BasketSnapshotFieldResolver.php
+++ b/library/Director/DirectorObject/Automation/BasketSnapshotFieldResolver.php
@@ -4,7 +4,10 @@ namespace Icinga\Module\Director\DirectorObject\Automation;
use Icinga\Module\Director\Db;
use Icinga\Module\Director\Objects\DirectorDatafield;
+use Icinga\Module\Director\Objects\DirectorDatalist;
use Icinga\Module\Director\Objects\IcingaObject;
+use InvalidArgumentException;
+use stdClass;
class BasketSnapshotFieldResolver
{
@@ -39,7 +42,7 @@ class BasketSnapshotFieldResolver
* @return DirectorDatafield[]
* @throws \Icinga\Exception\NotFoundError
*/
- public function loadCurrentFields(Db $db)
+ public function loadCurrentFields(Db $db): array
{
$fields = [];
foreach ($this->getRequiredIds() as $id) {
@@ -90,6 +93,11 @@ class BasketSnapshotFieldResolver
$existingFields[(int) $mapping->datafield_id] = $mapping;
}
foreach ($object->fields as $field) {
+ if (! isset($fieldMap[(int) $field->datafield_id])) {
+ throw new InvalidArgumentException(
+ 'Basket Snapshot contains invalid field reference: ' . $field->datafield_id
+ );
+ }
$id = $fieldMap[(int) $field->datafield_id];
if (isset($existingFields[$id])) {
unset($existingFields[$id]);
@@ -114,6 +122,8 @@ class BasketSnapshotFieldResolver
}
/**
+ * For diff purposes only, gives '(UNKNOWN)' for fields missing in our DB
+ *
* @param object $object
* @throws \Icinga\Exception\NotFoundError
*/
@@ -127,21 +137,34 @@ class BasketSnapshotFieldResolver
if (isset($map[$id])) {
$field->datafield_id = $map[$id];
} else {
- $field->datafield_id = "(NEW)";
+ $field->datafield_id = "(UNKNOWN)";
}
}
}
}
- /**
- * @return int
- */
- protected function getNextNewId()
+ public static function fixOptionalDatalistReference(stdClass $plain, Db $db)
+ {
+ if (isset($plain->settings->datalist_uuid)) {
+ unset($plain->settings->datalist);
+ return;
+ }
+ if (isset($plain->settings->datalist)) {
+ // Just try to load the list, final import will fail if missing
+ // No modification in case we do not find the list,
+ if ($list = DirectorDatalist::loadOptional($plain->settings->datalist, $db)) {
+ unset($plain->settings->datalist);
+ $plain->settings->datalist_id = $list->get('id');
+ }
+ }
+ }
+
+ protected function getNextNewId(): int
{
return $this->nextNewId++;
}
- protected function getRequiredIds()
+ protected function getRequiredIds(): array
{
if ($this->requiredIds === null) {
if (isset($this->objects['Datafield'])) {
@@ -169,7 +192,7 @@ class BasketSnapshotFieldResolver
* @param $type
* @return object[]
*/
- protected function getObjectsByType($type)
+ protected function getObjectsByType($type): array
{
if (isset($this->objects->$type)) {
return (array) $this->objects->$type;
@@ -182,7 +205,7 @@ class BasketSnapshotFieldResolver
* @return DirectorDatafield[]
* @throws \Icinga\Exception\NotFoundError
*/
- protected function getTargetFields()
+ protected function getTargetFields(): array
{
if ($this->targetFields === null) {
$this->calculateIdMap();
@@ -194,7 +217,7 @@ class BasketSnapshotFieldResolver
/**
* @throws \Icinga\Exception\NotFoundError
*/
- protected function getIdMap()
+ protected function getIdMap(): array
{
if ($this->idMap === null) {
$this->calculateIdMap();
diff --git a/library/Director/DirectorObject/Automation/CompareBasketObject.php b/library/Director/DirectorObject/Automation/CompareBasketObject.php
index ef2e9e2..f1ab6a9 100644
--- a/library/Director/DirectorObject/Automation/CompareBasketObject.php
+++ b/library/Director/DirectorObject/Automation/CompareBasketObject.php
@@ -31,7 +31,7 @@ class CompareBasketObject
static::normalize($v);
}
unset($v);
- $value = $sorted;
+ $value = (object) $sorted;
// foreign baskets might not sort those lists correctly:
if (isset($value->list_name) && isset($value->entries)) {
@@ -46,7 +46,11 @@ class CompareBasketObject
protected static function sortListBy($key, &$list)
{
usort($list, function ($a, $b) use ($key) {
- return $a->$key > $b->$key ? -1 : 1;
+ if (is_array($a)) {
+ return $a[$key] > $b[$key] ? -1 : 1;
+ } else {
+ return $a->$key > $b->$key ? -1 : 1;
+ }
});
}
diff --git a/library/Director/DirectorObject/Automation/ExportInterface.php b/library/Director/DirectorObject/Automation/ExportInterface.php
index 275dfed..271824f 100644
--- a/library/Director/DirectorObject/Automation/ExportInterface.php
+++ b/library/Director/DirectorObject/Automation/ExportInterface.php
@@ -2,18 +2,8 @@
namespace Icinga\Module\Director\DirectorObject\Automation;
-use Icinga\Module\Director\Db;
-
interface ExportInterface
{
- /**
- * @deprecated
- * @return \stdClass
- */
- public function export();
-
- public static function import($plain, Db $db, $replace = false);
-
// TODO:
// public function getXyzChecksum();
public function getUniqueIdentifier();
diff --git a/library/Director/DirectorObject/Automation/ImportExport.php b/library/Director/DirectorObject/Automation/ImportExport.php
index a5e72fa..1664f5d 100644
--- a/library/Director/DirectorObject/Automation/ImportExport.php
+++ b/library/Director/DirectorObject/Automation/ImportExport.php
@@ -3,6 +3,7 @@
namespace Icinga\Module\Director\DirectorObject\Automation;
use Icinga\Module\Director\Data\Exporter;
+use Icinga\Module\Director\Data\ObjectImporter;
use Icinga\Module\Director\Db;
use Icinga\Module\Director\Objects\DirectorDatafield;
use Icinga\Module\Director\Objects\DirectorDatalist;
@@ -125,8 +126,9 @@ class ImportExport
{
$count = 0;
$this->connection->runFailSafeTransaction(function () use ($objects, &$count) {
+ $importer = new ObjectImporter($this->connection);
foreach ($objects as $object) {
- ImportSource::import($object, $this->connection)->store();
+ $importer->import(ImportSource::class, $object)->store();
$count++;
}
});
@@ -138,8 +140,9 @@ class ImportExport
{
$count = 0;
$this->connection->runFailSafeTransaction(function () use ($objects, &$count) {
+ $importer = new ObjectImporter($this->connection);
foreach ($objects as $object) {
- SyncRule::import($object, $this->connection)->store();
+ $importer->import(SyncRule::class, $object)->store();
}
$count++;
});
diff --git a/library/Director/DirectorObject/Lookup/ServiceFinder.php b/library/Director/DirectorObject/Lookup/ServiceFinder.php
index fb8d74c..a14d853 100644
--- a/library/Director/DirectorObject/Lookup/ServiceFinder.php
+++ b/library/Director/DirectorObject/Lookup/ServiceFinder.php
@@ -4,6 +4,8 @@ namespace Icinga\Module\Director\DirectorObject\Lookup;
use gipfl\IcingaWeb2\Url;
use Icinga\Authentication\Auth;
+use Icinga\Module\Director\Auth\Permission;
+use Icinga\Module\Director\Integration\MonitoringModule\Monitoring;
use Icinga\Module\Director\Objects\HostApplyMatches;
use Icinga\Module\Director\Objects\IcingaHost;
use RuntimeException;
@@ -49,31 +51,4 @@ class ServiceFinder
return false;
}
-
- /**
- * @param $serviceName
- * @return Url
- */
- public function getRedirectionUrl($serviceName)
- {
- if ($this->auth === null) {
- throw new RuntimeException('Auth is required for ServiceFinder when dealing when asking for URLs');
- }
- if ($this->auth->hasPermission('director/host')) {
- if ($info = $this::find($this->host, $serviceName)) {
- return $info->getUrl();
- }
- }
- if ($this->auth->hasPermission('director/monitoring/services-ro')) {
- return Url::fromPath('director/host/servicesro', [
- 'name' => $this->host->getObjectName(),
- 'service' => $serviceName
- ]);
- }
-
- return Url::fromPath('director/host/invalidservice', [
- 'name' => $this->host->getObjectName(),
- 'service' => $serviceName,
- ]);
- }
}
diff --git a/library/Director/DirectorObject/ObjectPurgeHelper.php b/library/Director/DirectorObject/ObjectPurgeHelper.php
index a043965..5e50727 100644
--- a/library/Director/DirectorObject/ObjectPurgeHelper.php
+++ b/library/Director/DirectorObject/ObjectPurgeHelper.php
@@ -44,11 +44,11 @@ class ObjectPurgeHelper
// TODO: this is object-specific and to be found in the ::import() function!
unset($properties['fields']);
$object = $class::fromPlainObject($properties);
- } elseif (\get_class($object) !== $class) {
+ } elseif (get_class($object) !== $class) {
throw new InvalidArgumentException(
'Can keep only matching objects, expected "%s", got "%s',
$class,
- \get_class($keep)
+ get_class($object)
);
}
$key = [];
diff --git a/library/Director/Field/FormFieldSuggestion.php b/library/Director/Field/FormFieldSuggestion.php
new file mode 100644
index 0000000..2f7f875
--- /dev/null
+++ b/library/Director/Field/FormFieldSuggestion.php
@@ -0,0 +1,167 @@
+<?php
+
+namespace Icinga\Module\Director\Field;
+
+use gipfl\Translation\TranslationHelper;
+use Icinga\Module\Director\Objects\IcingaCommand;
+
+class FormFieldSuggestion
+{
+ use TranslationHelper;
+
+ /**
+ * Macro/Argument names used in command argument values
+ *
+ * @var array
+ */
+ protected $argumentVars = [];
+ protected $suggestedFields = [];
+ protected $blacklistedVars = [];
+ protected $descriptions = [];
+ protected $booleans = [];
+
+ /** @var ?IcingaCommand */
+ protected $command;
+
+ /** @var array */
+ protected $existingFields;
+
+ protected $fields = null;
+
+ public function __construct(
+ ?IcingaCommand $command,
+ array $existingFields
+ ) {
+ $this->command = $command;
+ $this->existingFields = $existingFields;
+ }
+
+ public function getCommandFields(): array
+ {
+ if ($this->fields === null) {
+ $this->fields = $this->prepareFields();
+ }
+
+ return $this->fields;
+ }
+
+ protected function prepareFields(): array
+ {
+ // TODO: remove assigned ones!
+
+ foreach ($this->existingFields as $id => $field) {
+ if (preg_match('/ \(([^)]+)\)$/', $field, $m)) {
+ $this->blacklistedVars['$' . $m[1] . '$'] = $id;
+ }
+ }
+
+ if ($this->command) {
+ foreach ($this->command->arguments() as $arg) {
+ if ($arg->argument_format === 'string') {
+ foreach (self::extractMacroNamesFromString($arg->argument_value) as $val) {
+ $this->addSuggestion($val, $arg->description, $this->argumentVars);
+ }
+ }
+
+ if (($arg->set_if_format === 'string' || $arg->set_if_format === null)
+ && $val = self::getMacroIfStringIsSingleMacro($arg->set_if)
+ ) {
+ $this->addSuggestion($val, $arg->description, $this->booleans);
+ }
+ }
+ }
+
+ asort($this->suggestedFields, SORT_NATURAL | SORT_FLAG_CASE);
+ ksort($this->argumentVars);
+ ksort($this->booleans);
+ asort($this->existingFields, SORT_NATURAL | SORT_FLAG_CASE);
+
+ // Prepare combined fields array
+ $fields = [];
+ if (! empty($this->suggestedFields)) {
+ $fields[$this->translate('Suggested fields')] = $this->suggestedFields;
+ }
+
+ if (! empty($this->argumentVars)) {
+ $fields[$this->translate('Argument macros')] = $this->argumentVars;
+ }
+
+ if (! empty($this->booleans)) {
+ $fields[$this->translate('Toggles (boolean arguments)')] = $this->booleans;
+ }
+
+ if (! empty($this->existingFields)) {
+ $fields[$this->translate('Other available fields')] = $this->existingFields;
+ }
+
+ return $fields;
+ }
+
+ public function getDescription($id)
+ {
+ if (array_key_exists($id, $this->descriptions)) {
+ return $this->descriptions[$id];
+ }
+
+ return null;
+ }
+
+ public function isBoolean(string $macro): bool
+ {
+ return isset($this->booleans[$macro]);
+ }
+
+ protected function addSuggestion(
+ string $val,
+ ?string $description,
+ array &$targetList
+ ) {
+ if (array_key_exists($val, $this->blacklistedVars)) {
+ $id = $this->blacklistedVars[$val];
+
+ // Hint: if not set it might already have been
+ // removed in this loop
+ if (array_key_exists($id, $this->existingFields)) {
+ $this->suggestedFields[$id] = $this->existingFields[$id];
+ unset($this->existingFields[$id]);
+ }
+ } else {
+ $targetList[$val] = $val;
+ $this->descriptions[$val] = $description;
+ }
+ }
+
+ /**
+ * Returns a macro name string ($macro_name$), if the given string is such, null otherwise
+ *
+ * @param ?string $string
+ * @return ?string
+ */
+ protected static function getMacroIfStringIsSingleMacro(?string $string): ?string
+ {
+ if ($string === null) {
+ return null;
+ }
+
+ if (preg_match('/^(\$[a-z0-9_]+\$)$/i', $string, $matches)) {
+ return $matches[1];
+ }
+
+ return null;
+ }
+
+ /**
+ * Extracts all macro names ($macro_name$) from a given string
+ *
+ * @param ?string $string
+ * @return array
+ */
+ protected static function extractMacroNamesFromString(?string $string): array
+ {
+ if ($string !== null && preg_match_all('/(\$[a-z0-9_]+\$)/i', $string, $matches, PREG_PATTERN_ORDER)) {
+ return $matches[1];
+ }
+
+ return [];
+ }
+}
diff --git a/library/Director/Filter/CidrExpression.php b/library/Director/Filter/CidrExpression.php
new file mode 100644
index 0000000..169ddce
--- /dev/null
+++ b/library/Director/Filter/CidrExpression.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace Icinga\Module\Director\Filter;
+
+use Icinga\Data\Filter\FilterExpression;
+use InvalidArgumentException;
+
+use function array_map;
+use function filter_var;
+use function inet_pton;
+use function pack;
+use function preg_match;
+use function str_pad;
+use function str_split;
+
+class CidrExpression extends FilterExpression
+{
+ protected $networkAddress;
+ protected $broadcastAddress;
+
+ public function __construct($column, $sign, $expression)
+ {
+ if ($parts = static::splitOptionalCidrString($expression)) {
+ list($this->networkAddress, $this->broadcastAddress) = $parts;
+ } else {
+ throw new InvalidArgumentException("'$expression' isn't valid CIDR notation");
+ }
+
+ parent::__construct($column, $sign, $expression);
+ }
+
+ public static function isCidrFormat(string $string): bool
+ {
+ return static::splitOptionalCidrString($string) !== null;
+ }
+
+ protected static function splitOptionalCidrString(string $string): ?array
+ {
+ if (preg_match('#^(.+?)/(\d{1,3})$#', $string, $match)) {
+ $address = $match[1];
+ $mask = (int) $match[2];
+
+ if (filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) && $mask <= 32) {
+ $bits = 32;
+ } elseif (filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) && $mask <= 128) {
+ $bits = 128;
+ } else {
+ return null;
+ }
+
+ $binaryAddress = inet_pton($address);
+ $broadcast = $binaryAddress | static::bitmaskToInverseBinaryMask($mask, $bits);
+
+ return [$binaryAddress, $broadcast];
+ }
+
+ return null;
+ }
+
+ public function matches($row): bool
+ {
+ if (! isset($row->{$this->column})) {
+ return false;
+ }
+ $value = inet_pton((string) $row->{$this->column});
+
+ return $value >= $this->networkAddress && $value <= $this->broadcastAddress;
+ }
+
+ public static function fromExpression(FilterExpression $filter): CidrExpression
+ {
+ $sign = $filter->getSign();
+ if ($sign !== '=') {
+ throw new InvalidArgumentException("'$sign' cannot be applied to CIDR notation");
+ }
+ return new CidrExpression($filter->getColumn(), $sign, $filter->getExpression());
+ }
+
+ protected static function bitmaskToInverseBinaryMask($mask, $maxLen): string
+ {
+ $binary = str_pad(str_pad('', $mask, '0'), $maxLen, '1');
+ $address = '';
+ foreach (array_map('bindec', str_split($binary, 8)) as $char) {
+ $address .= pack('C*', $char);
+ }
+
+ return $address;
+ }
+}
diff --git a/library/Director/Filter/FilterEnrichment.php b/library/Director/Filter/FilterEnrichment.php
new file mode 100644
index 0000000..c726f76
--- /dev/null
+++ b/library/Director/Filter/FilterEnrichment.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Icinga\Module\Director\Filter;
+
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterChain;
+use Icinga\Data\Filter\FilterExpression;
+
+class FilterEnrichment
+{
+ public static function enrichFilter(Filter $filter): Filter
+ {
+ if ($filter instanceof FilterExpression) {
+ if (CidrExpression::isCidrFormat($filter->getExpression())) {
+ return CidrExpression::fromExpression($filter);
+ }
+ } elseif ($filter instanceof FilterChain) {
+ foreach ($filter->filters() as $subFilter) {
+ if ($subFilter instanceof FilterExpression
+ && CidrExpression::isCidrFormat($subFilter->getExpression())
+ ) {
+ $filter->replaceById($subFilter->getId(), CidrExpression::fromExpression($subFilter));
+ }
+ }
+ }
+
+ return $filter;
+ }
+}
diff --git a/library/Director/Hook/BranchSupportHook.php b/library/Director/Hook/BranchSupportHook.php
index 6615cbe..72ce096 100644
--- a/library/Director/Hook/BranchSupportHook.php
+++ b/library/Director/Hook/BranchSupportHook.php
@@ -14,7 +14,7 @@ abstract class BranchSupportHook
{
/**
* @param Request $request
- * @param BranchSTore $store
+ * @param BranchStore $store
* @param Auth $auth
* @return Branch
*/
diff --git a/library/Director/IcingaConfig/AgentWizard.php b/library/Director/IcingaConfig/AgentWizard.php
index aceddb1..93527f5 100644
--- a/library/Director/IcingaConfig/AgentWizard.php
+++ b/library/Director/IcingaConfig/AgentWizard.php
@@ -148,15 +148,34 @@ class AgentWizard
public function renderIcinga4WindowsWizardCommand($token)
{
- $script = "Use-Icinga;\n"
- . 'Start-IcingaAgentInstallWizard `' . "\n "
+ $ifwParams = [
+ "IfW-DirectorSelfServiceKey" => [
+ "Values" => [$token],
+ ],
+ "IfW-DirectorUrl" => [
+ "Values" => [$this->getDirectorUrl()],
+ ],
+ "IfW-StableRepository" => [
+ "Values" => ["https://packages.icinga.com/IcingaForWindows/stable"],
+ ]
+ ];
+
+ $script = "[Net.ServicePointManager]::SecurityProtocol = 'tls12, tls11';\n"
+ . "\$ProgressPreference = 'SilentlyContinue';" . "\n"
+ . "[string]\$ScriptFile = 'C:\Users\Public\IcingaForWindows.ps1';\n"
+ . "\n"
+ . "Invoke-WebRequest `\n "
. $this->renderPowershellParameters([
- 'DirectorUrl' => $this->getDirectorUrl(),
- 'SelfServiceAPIKey' => $token,
- 'UseDirectorSelfService' => 1,
- 'OverrideDirectorVars' => 0,
- 'Reconfigure',
- 'RunInstaller'
+ 'UseBasicParsing',
+ 'Uri' => "https://packages.icinga.com/IcingaForWindows/IcingaForWindows.ps1",
+ 'OutFile' => '$ScriptFile;',
+ ]) . "\n"
+ . "\n"
+ . "& \$ScriptFile `\n "
+ . $this->renderPowershellParameters([
+ 'ModuleDirectory' => "C:\Program Files\WindowsPowerShell\Modules\\",
+ 'InstallCommand' => json_encode($ifwParams, JSON_UNESCAPED_SLASHES),
+ 'IcingaRepository' => "https://packages.icinga.com/IcingaForWindows/stable/ifw.repo.json"
]);
return $script;
@@ -235,6 +254,8 @@ class AgentWizard
$ret .= implode(', ', $vals);
} elseif (is_int($value)) {
$ret .= $value;
+ } elseif (is_string($value) && $value[0] === '$') {
+ $ret .= $value;
} else {
$ret .= $this->renderPowershellString($value);
}
@@ -297,7 +318,7 @@ class AgentWizard
} else {
$value = escapeshellarg($value);
}
- $script = preg_replace("~^#?$quotedKey='@$quotedKey@'$~m", "${key}=${value}", $script);
+ $script = preg_replace("~^#?$quotedKey='@$quotedKey@'$~m", "{$key}={$value}", $script);
}
return $script;
diff --git a/library/Director/IcingaConfig/AssignRenderer.php b/library/Director/IcingaConfig/AssignRenderer.php
index 6acbfee..495ad1e 100644
--- a/library/Director/IcingaConfig/AssignRenderer.php
+++ b/library/Director/IcingaConfig/AssignRenderer.php
@@ -2,6 +2,7 @@
namespace Icinga\Module\Director\IcingaConfig;
+use gipfl\Json\JsonDecodeException;
use gipfl\Json\JsonString;
use Icinga\Data\Filter\Filter;
use Icinga\Data\Filter\FilterAnd;
@@ -128,8 +129,14 @@ class AssignRenderer
}
$column = $filter->getColumn();
- $rawExpression = Json::decode($filter->getExpression());
- $expression = $this->renderExpressionValue($rawExpression);
+ try {
+ $rawExpression = JsonString::decode($filter->getExpression());
+ $expression = $this->renderExpressionValue($rawExpression);
+ } catch (JsonDecodeException $e) {
+ throw new InvalidArgumentException(
+ "Got invalid JSON in filter string: $column" . $filter->getSign() . $filter->getExpression()
+ );
+ }
if (is_array($rawExpression) && $filter instanceof FilterMatch) {
return $this->renderInArray($column, $expression);
diff --git a/library/Director/IcingaConfig/ExtensibleSet.php b/library/Director/IcingaConfig/ExtensibleSet.php
index 9120816..d52a30e 100644
--- a/library/Director/IcingaConfig/ExtensibleSet.php
+++ b/library/Director/IcingaConfig/ExtensibleSet.php
@@ -50,7 +50,7 @@ class ExtensibleSet
$set->object = $object;
$set->propertyName = $propertyName;
- if ($object->hasBeenLoadedFromDb()) {
+ if ($object->hasBeenLoadedFromDb() && $id = $object->get('id')) {
$set->loadFromDb();
}
diff --git a/library/Director/IcingaConfig/IcingaConfig.php b/library/Director/IcingaConfig/IcingaConfig.php
index 72edd7e..a79bf3c 100644
--- a/library/Director/IcingaConfig/IcingaConfig.php
+++ b/library/Director/IcingaConfig/IcingaConfig.php
@@ -25,6 +25,9 @@ class IcingaConfig
protected $zoneMap = array();
+ /** @var ?array Exists for caching reasons at rendering time */
+ protected $nonGlobalZones = null;
+
protected $lastActivityChecksum;
/** @var \Zend_Db_Adapter_Abstract */
@@ -349,6 +352,15 @@ class IcingaConfig
return $this->zoneMap[$id];
}
+ public function listNonGlobalZones(): array
+ {
+ if ($this->nonGlobalZones === null) {
+ $this->nonGlobalZones = array_values($this->connection->enumNonglobalZones());
+ }
+
+ return $this->nonGlobalZones;
+ }
+
/**
* @return self
*/
@@ -436,9 +448,9 @@ class IcingaConfig
$start = microtime(true);
MemoryLimit::raiseTo('1024M');
- ini_set('max_execution_time', 0);
+ ini_set('max_execution_time', '0');
// Workaround for https://bugs.php.net/bug.php?id=68606 or similar
- ini_set('zend.enable_gc', 0);
+ ini_set('zend.enable_gc', '0');
if (! $this->connection->isPgsql() && $this->db->quote("1\0") !== '\'1\\0\'') {
throw new RuntimeException(
@@ -501,6 +513,7 @@ class IcingaConfig
"\nconst DirectorStageDir = dirname(dirname(current_filename))\n"
. $this->renderFlappingLogHelper()
. $this->renderHostOverridableVars()
+ . $this->renderIfwFallbackTemplate()
);
return $this;
@@ -566,6 +579,20 @@ if (! globals.contains(DirectorOverrideTemplate)) {
);
}
+
+ protected function renderIfwFallbackTemplate(): string
+ {
+ return '
+// Make sure config validates for Icinga < 2.14 with IfW 1.11 configuration. This might look weird,
+// but is intentional. get_object() does\'t work as expected at parse time.
+if (! globals.System || ! System.get_template || ! get_template(CheckCommand, "ifw-api-check-command")) {
+ object CheckCommand "ifw-api" {
+ import "plugin-check-command"
+ }
+}
+';
+ }
+
/**
* @param string $checksum
*
diff --git a/library/Director/IcingaConfig/IcingaConfigHelper.php b/library/Director/IcingaConfig/IcingaConfigHelper.php
index 03c017e..634337f 100644
--- a/library/Director/IcingaConfig/IcingaConfigHelper.php
+++ b/library/Director/IcingaConfig/IcingaConfigHelper.php
@@ -177,7 +177,7 @@ class IcingaConfigHelper
throw new InvalidArgumentException(sprintf(
'Unexpected type %s',
- var_export($value, 1)
+ var_export($value, true)
));
}
diff --git a/library/Director/Import/ImportSourceCoreApi.php b/library/Director/Import/ImportSourceCoreApi.php
index 6d590ec..3c54d48 100644
--- a/library/Director/Import/ImportSourceCoreApi.php
+++ b/library/Director/Import/ImportSourceCoreApi.php
@@ -30,7 +30,7 @@ class ImportSourceCoreApi extends ImportSourceHook
public function listColumns()
{
$res = $this->fetchData();
- if (empty($data)) {
+ if (empty($res)) {
return array('object_name');
}
diff --git a/library/Director/Import/ImportSourceLdap.php b/library/Director/Import/ImportSourceLdap.php
index 4518565..559669d 100644
--- a/library/Director/Import/ImportSourceLdap.php
+++ b/library/Director/Import/ImportSourceLdap.php
@@ -45,7 +45,7 @@ class ImportSourceLdap extends ImportSourceHook
public static function addSettingsFormFields(QuickForm $form)
{
- Util::addLDAPResourceFormElement($form, 'resource');
+ Util::addLdapResourceFormElement($form, 'resource');
$form->addElement('text', 'base', array(
'label' => $form->translate('LDAP Search Base'),
'description' => $form->translate(
diff --git a/library/Director/Import/ImportSourceRestApi.php b/library/Director/Import/ImportSourceRestApi.php
index dc772e1..45f7351 100644
--- a/library/Director/Import/ImportSourceRestApi.php
+++ b/library/Director/Import/ImportSourceRestApi.php
@@ -69,6 +69,7 @@ class ImportSourceRestApi extends ImportSourceHook
$data = $result;
foreach ($parts as $part) {
// un-escape any dots
+ /** @var string $part */
$part = preg_replace('~\\\\.~', '.', $part);
if (property_exists($data, $part)) {
diff --git a/library/Director/Import/Sync.php b/library/Director/Import/Sync.php
index 8fea46c..2957433 100644
--- a/library/Director/Import/Sync.php
+++ b/library/Director/Import/Sync.php
@@ -16,6 +16,7 @@ use Icinga\Module\Director\Objects\HostGroupMembershipResolver;
use Icinga\Module\Director\Objects\IcingaHost;
use Icinga\Module\Director\Objects\IcingaHostGroup;
use Icinga\Module\Director\Objects\IcingaObject;
+use Icinga\Module\Director\Objects\IcingaObjectGroup;
use Icinga\Module\Director\Objects\ImportSource;
use Icinga\Module\Director\Objects\IcingaService;
use Icinga\Module\Director\Objects\SyncProperty;
@@ -49,6 +50,9 @@ class Sync
/** @var array<mixed, array<int, string>> key => [property, property]*/
protected $setNull = [];
+ /** @var array<mixed, array<string, mixed>> key => [propertyName, newValue]*/
+ protected $newProperties = [];
+
/** @var bool Whether we already prepared your sync */
protected $isPrepared = false;
@@ -88,6 +92,12 @@ class Sync
/** @var ?DbObjectStore */
protected $store;
+ /** @var IcingaObjectGroup[] */
+ protected $modifiedGroups = [];
+
+ /** @var IcingaObject[] */
+ protected $modifiedGroupObjects = [];
+
/**
* @param SyncRule $rule
* @param ?DbObjectStore $store
@@ -137,24 +147,6 @@ class Sync
}
/**
- * Transform the given value to an array
- *
- * @param array|string|null $value
- *
- * @return array
- */
- protected function wantArray($value)
- {
- if (is_array($value)) {
- return $value;
- } elseif ($value === null) {
- return [];
- } else {
- return [$value];
- }
- }
-
- /**
* Raise PHP resource limits
*
* @return self;
@@ -162,7 +154,7 @@ class Sync
protected function raiseLimits()
{
MemoryLimit::raiseTo('1024M');
- ini_set('max_execution_time', 0);
+ ini_set('max_execution_time', '0');
return $this;
}
@@ -452,7 +444,28 @@ class Sync
if ($this->store) {
$objects = $this->store->loadAll(DbObjectTypeRegistry::tableNameByType($ruleObjectType), 'object_name');
} else {
- $objects = IcingaObject::loadAllByType($ruleObjectType, $this->db);
+ $keyColumn = null;
+ $query = null;
+ // We enforce named index for combined-key templates (Services and Sets) and applied Sets
+ if ($ruleObjectType === 'service' || $ruleObjectType === 'serviceSet') {
+ foreach ($this->syncProperties as $prop) {
+ $configuredObjectType = $prop->get('source_expression');
+ if ($prop->get('destination_field') === 'object_type'
+ && (
+ $configuredObjectType === 'template'
+ || ($configuredObjectType === 'apply' && $ruleObjectType === 'serviceSet')
+ )
+ ) {
+ $keyColumn = 'object_name';
+ $table = $ruleObjectType === 'service'
+ ? BranchSupport::TABLE_ICINGA_SERVICE
+ : BranchSupport::TABLE_ICINGA_SERVICE_SET;
+ $query = $this->db->getDbAdapter()->select()
+ ->from($table)->where('object_type = ?', $configuredObjectType);
+ }
+ }
+ }
+ $objects = IcingaObject::loadAllByType($ruleObjectType, $this->db, $query, $keyColumn);
}
if ($useLowerCaseKeys) {
@@ -532,6 +545,15 @@ class Sync
*/
protected function prepareNewObject($row, DbObject $object, $objectKey, $sourceId)
{
+ if (!isset($this->newProperties[$objectKey])) {
+ $this->newProperties[$objectKey] = [];
+ }
+ // TODO: some more improvements are possible here. First, no need to instantiate
+ // all new objects, we could stick with the newProperties array. Next, we
+ // should be more correct when respecting sync property order. Right now,
+ // a property from another Import Source might win, even if property order
+ // tells something different. This is a very rare case, but still incorrect.
+ $properties = &$this->newProperties[$objectKey];
foreach ($this->syncProperties as $propertyKey => $p) {
if ($p->get('source_id') !== $sourceId) {
continue;
@@ -557,38 +579,35 @@ class Sync
$varName = substr($prop, 5);
if (substr($varName, -2) === '[]') {
$varName = substr($varName, 0, -2);
- $current = $this->wantArray($object->vars()->$varName);
$object->vars()->$varName = array_merge(
- $current,
- $this->wantArray($val)
+ (array) ($object->vars()->$varName),
+ (array) $val
);
} else {
- if ($val === null) {
- $this->setNull[$objectKey][$prop] = $prop;
- } else {
- unset($this->setNull[$objectKey][$prop]);
- $object->vars()->$varName = $val;
- }
+ $this->setPropertyWithNullLogic($object, $objectKey, $prop, $val, $properties);
}
} else {
- if ($val === null) {
- $this->setNull[$objectKey][$prop] = $prop;
- } else {
- unset($this->setNull[$objectKey][$prop]);
- $object->set($prop, $val);
- }
+ $this->setPropertyWithNullLogic($object, $objectKey, $prop, $val, $properties);
}
} else {
- if ($val === null) {
- $this->setNull[$objectKey][$prop] = $prop;
- } else {
- unset($this->setNull[$objectKey][$prop]);
- $object->set($prop, $val);
- }
+ $this->setPropertyWithNullLogic($object, $objectKey, $prop, $val, $properties);
}
}
}
+ protected function setPropertyWithNullLogic(DbObject $object, $objectKey, $property, $value, &$allProps)
+ {
+ if ($value === null) {
+ if (! array_key_exists($property, $allProps) || $allProps[$property] === null) {
+ $this->setNull[$objectKey][$property] = $property;
+ }
+ } else {
+ unset($this->setNull[$objectKey][$property]);
+ $object->set($property, $value);
+ }
+ $allProps[$property] = $value;
+ }
+
/**
* @return $this
*/
@@ -625,6 +644,9 @@ class Sync
protected function notifyResolvers()
{
if ($resolver = $this->getHostGroupMembershipResolver()) {
+ if ($this->rule->get('object_type') === 'hostgroup') {
+ $resolver->setGroups($this->modifiedGroups);
+ }
$resolver->refreshDb(true);
}
@@ -748,6 +770,13 @@ class Sync
}
}
+ protected function optionallyTellResolverAboutModifiedGroup(IcingaObjectGroup $group)
+ {
+ if (in_array('assign_filter', $group->getModifiedProperties())) {
+ $this->modifiedGroups[] = $group;
+ }
+ }
+
/**
* @param $key
* @param DbObject|IcingaObject $object
@@ -791,7 +820,11 @@ class Sync
}
}
- if (isset($this->setNull[$key])) {
+ // Hint: in theory, NULL should be set on new objects, but this has no effect
+ // anyway, and we also do not store vars.something = null, this would
+ // instead delete the variable. So here we do not need to check for new
+ // objects, and skip all null values with update policy = 'ignore'
+ if ($policy !== 'ignore' && isset($this->setNull[$key])) {
foreach ($this->setNull[$key] as $property) {
$this->objects[$key]->set($property, null);
}
@@ -850,6 +883,9 @@ class Sync
}
if ($object->hasBeenModified()) {
+ if ($object instanceof IcingaObjectGroup) {
+ $this->optionallyTellResolverAboutModifiedGroup($object);
+ }
$existing = $object->hasBeenLoadedFromDb();
if ($existing) {
if ($this->store) {
diff --git a/library/Director/Import/SyncUtils.php b/library/Director/Import/SyncUtils.php
index 5528b2d..c106c20 100644
--- a/library/Director/Import/SyncUtils.php
+++ b/library/Director/Import/SyncUtils.php
@@ -99,7 +99,7 @@ class SyncUtils
throw new InvalidArgumentException(sprintf(
'Data is not nested, cannot access %s: %s',
$var,
- var_export($row, 1)
+ var_export($row, true)
));
}
diff --git a/library/Director/Integration/BackendInterface.php b/library/Director/Integration/BackendInterface.php
new file mode 100644
index 0000000..7b2b88c
--- /dev/null
+++ b/library/Director/Integration/BackendInterface.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace Icinga\Module\Director\Integration;
+
+use Icinga\Web\Url;
+
+interface BackendInterface
+{
+ /**
+ * Whether the backend has the given host
+ *
+ * @param ?string $hostName
+ *
+ * @return bool
+ */
+ public function hasHost(?string $hostName): bool;
+
+ /**
+ * Whether the backend has the given service of the specified host
+ *
+ * @param ?string $hostName
+ * @param ?string $serviceName
+ *
+ * @return bool
+ */
+ public function hasService(?string $hostName, ?string $serviceName): bool;
+
+ /**
+ * Whether an authenticated user has the permission (is not restricted) to modify given host
+ *
+ * @param ?string $hostName
+ *
+ * @return bool
+ */
+ public function canModifyHost(?string $hostName): bool;
+
+ /**
+ * Whether an authenticated user has the permission (is not restricted) to modify given service of specified host
+ *
+ * @param ?string $hostName
+ * @param ?string $serviceName
+ *
+ * @return bool
+ */
+ public function canModifyService(?string $hostName, ?string $serviceName): bool;
+
+ /**
+ * Get the url of given host
+ *
+ * @param ?string $hostName
+ *
+ * @return Url
+ */
+ public function getHostUrl(?string $hostName): ?Url;
+}
diff --git a/library/Director/Integration/Icingadb/IcingadbBackend.php b/library/Director/Integration/Icingadb/IcingadbBackend.php
new file mode 100644
index 0000000..874cddd
--- /dev/null
+++ b/library/Director/Integration/Icingadb/IcingadbBackend.php
@@ -0,0 +1,127 @@
+<?php
+
+namespace Icinga\Module\Director\Integration\Icingadb;
+
+use Icinga\Module\Director\Auth\Permission;
+use Icinga\Module\Director\Auth\Restriction;
+use Icinga\Module\Director\Integration\BackendInterface;
+use Icinga\Module\Icingadb\Common\Auth;
+use Icinga\Module\Icingadb\Common\Database;
+use Icinga\Module\Icingadb\Model\Host;
+use Icinga\Module\Icingadb\Model\Service;
+use Icinga\Web\Url;
+use ipl\Orm\Query;
+use ipl\Stdlib\Filter;
+
+class IcingadbBackend implements BackendInterface
+{
+ use Database;
+ use Auth;
+
+ public function hasHost(?string $hostName): bool
+ {
+ if ($hostName === null) {
+ return false;
+ }
+
+ return $this->getHostQuery($hostName)->first() !== null;
+ }
+
+ public function hasService(?string $hostName, ?string $serviceName): bool
+ {
+ if ($hostName === null || $serviceName === null) {
+ return false;
+ }
+
+ return $this->getServiceQuery($hostName, $serviceName)->first() !== null;
+ }
+
+ public function getHostUrl(?string $hostName): ?Url
+ {
+ if ($hostName === null) {
+ return null;
+ }
+
+ return Url::fromPath('icingadb/host', ['name' => $hostName]);
+ }
+
+ public function canModifyHost(?string $hostName): bool
+ {
+ if ($hostName === null
+ || ! $this->getAuth()->hasPermission(Permission::ICINGADB_HOSTS)
+ ) {
+ return false;
+ }
+
+ $query = $this->getHostQuery($hostName);
+
+ return $query->first() !== null;
+ }
+
+ public function canModifyService(?string $hostName, ?string $serviceName): bool
+ {
+ if ($hostName === null
+ || $serviceName === null
+ || ! $this->getAuth()->hasPermission(Permission::ICINGADB_SERVICES)
+ ) {
+ return false;
+ }
+
+ $query = $this->getServiceQuery($hostName, $serviceName);
+
+ return $query->first() !== null;
+ }
+
+ /**
+ * Get the query for given host
+ *
+ * @param string $hostName
+ *
+ * @return Query
+ */
+ protected function getHostQuery(string $hostName): Query
+ {
+ $query = Host::on($this->getDb())
+ ->filter(Filter::equal('host.name', $hostName));
+
+ $this->applyDirectorRestrictions($query);
+
+ return $query;
+ }
+
+ /**
+ * Get the query for given host and service
+ *
+ * @param string $hostName
+ * @param string $serviceName
+ *
+ * @return Query
+ */
+ protected function getServiceQuery(string $hostName, string $serviceName): Query
+ {
+ $query = Service::on($this->getDb())
+ ->filter(Filter::all(
+ Filter::equal('service.name', $serviceName),
+ Filter::equal('host.name', $hostName)
+ ));
+
+ $this->applyDirectorRestrictions($query);
+
+ return $query;
+ }
+
+ /**
+ * Apply director restrictions on the given query
+ *
+ * @param Query $query
+ */
+ protected function applyDirectorRestrictions(Query $query): void
+ {
+ $queryFilter = Filter::any();
+ foreach ($this->getAuth()->getRestrictions(Restriction::ICINGADB_RW_OBJECT_FILTER) as $restriction) {
+ $queryFilter->add($this->parseRestriction($restriction, Restriction::ICINGADB_RW_OBJECT_FILTER));
+ }
+
+ $query->filter($queryFilter);
+ }
+}
diff --git a/library/Director/Integration/MonitoringModule/Monitoring.php b/library/Director/Integration/MonitoringModule/Monitoring.php
new file mode 100644
index 0000000..5a2dfde
--- /dev/null
+++ b/library/Director/Integration/MonitoringModule/Monitoring.php
@@ -0,0 +1,200 @@
+<?php
+
+namespace Icinga\Module\Director\Integration\MonitoringModule;
+
+use Exception;
+use Icinga\Application\Icinga;
+use Icinga\Authentication\Auth;
+use Icinga\Data\Filter\Filter;
+use Icinga\Exception\ConfigurationError;
+use Icinga\Module\Director\Auth\MonitoringRestriction;
+use Icinga\Module\Director\Auth\Permission;
+use Icinga\Module\Director\Auth\Restriction;
+use Icinga\Module\Director\Integration\BackendInterface;
+use Icinga\Module\Monitoring\Backend\MonitoringBackend;
+use Icinga\Web\Url;
+
+class Monitoring implements BackendInterface
+{
+ /** @var ?MonitoringBackend */
+ protected $backend;
+
+ /** @var Auth */
+ protected $auth;
+
+ public function __construct(Auth $auth)
+ {
+ $this->auth = $auth;
+ $this->initializeMonitoringBackend();
+ }
+
+ public function getHostUrl(?string $hostName): ?Url
+ {
+ if ($hostName === null) {
+ return null;
+ }
+
+ return Url::fromPath('monitoring/host/show', ['host' => $hostName]);
+ }
+
+ public function hasHost(?string $hostName): bool
+ {
+ if ($hostName === null || ! $this->isAvailable()) {
+ return false;
+ }
+
+ try {
+ return $this->selectHost($hostName)->fetchOne() === $hostName;
+ } catch (Exception $_) {
+ return false;
+ }
+ }
+
+ public function hasService(?string $hostName, ?string $serviceName): bool
+ {
+ if ($hostName === null || $serviceName === null || ! $this->isAvailable()) {
+ return false;
+ }
+
+ try {
+ return $this->rowIsService(
+ $this->selectService($hostName, $serviceName)->fetchRow(),
+ $hostName,
+ $serviceName
+ );
+ } catch (Exception $_) {
+ return false;
+ }
+ }
+
+ public function canModifyService(?string $hostName, ?string $serviceName): bool
+ {
+ if (! $this->isAvailable() || $hostName === null || $serviceName === null) {
+ return false;
+ }
+ if ($this->auth->hasPermission(Permission::MONITORING_SERVICES)) {
+ $restriction = null;
+ foreach ($this->auth->getRestrictions(Restriction::MONITORING_RW_OBJECT_FILTER) as $restriction) {
+ if ($this->hasServiceWithFilter($hostName, $serviceName, Filter::fromQueryString($restriction))) {
+ return true;
+ }
+ }
+ if ($restriction === null) {
+ return $this->hasService($hostName, $serviceName);
+ }
+ }
+
+ return false;
+ }
+
+ public function canModifyHost(?string $hostName): bool
+ {
+ if ($hostName !== null && $this->isAvailable() && $this->auth->hasPermission(Permission::MONITORING_HOSTS)) {
+ $restriction = null;
+ foreach ($this->auth->getRestrictions(Restriction::MONITORING_RW_OBJECT_FILTER) as $restriction) {
+ if ($this->hasHostWithFilter($hostName, Filter::fromQueryString($restriction))) {
+ return true;
+ }
+ }
+ if ($restriction === null) {
+ return $this->hasHost($hostName);
+ }
+ }
+
+ return false;
+ }
+
+ protected function hasHostWithFilter($hostname, Filter $filter): bool
+ {
+ try {
+ return $this->selectHost($hostname)->applyFilter($filter)->fetchOne() === $hostname;
+ } catch (Exception $e) {
+ return false;
+ }
+ }
+
+ protected function hasServiceWithFilter($hostname, $service, Filter $filter): bool
+ {
+ try {
+ return $this->rowIsService(
+ $this->selectService($hostname, $service)->applyFilter($filter)->fetchRow(),
+ $hostname,
+ $service
+ );
+ } catch (Exception $e) {
+ return false;
+ }
+ }
+
+ protected function selectHost($hostname)
+ {
+ return $this->selectHostStatus($hostname, [
+ 'hostname' => 'host_name',
+ ]);
+ }
+
+ protected function selectHostStatus($hostname, $columns)
+ {
+ return $this->restrictQuery(
+ $this->backend
+ ->select()
+ ->from('hostStatus', $columns)
+ ->where('host_name', $hostname)
+ );
+ }
+
+ protected function selectService($hostname, $service)
+ {
+ return $this->selectServiceStatus($hostname, $service, [
+ 'hostname' => 'host_name',
+ 'service' => 'service_description',
+ ]);
+ }
+
+ protected function selectServiceStatus($hostname, $service, $columns)
+ {
+ return $this->restrictQuery(
+ $this->backend
+ ->select()
+ ->from('serviceStatus', $columns)
+ ->where('host_name', $hostname)
+ ->where('service_description', $service)
+ );
+ }
+
+ protected function restrictQuery($query)
+ {
+ $query->applyFilter(MonitoringRestriction::getObjectsFilter($this->auth));
+ return $query;
+ }
+
+ protected function rowIsService($row, $hostname, $service): bool
+ {
+ return (array) $row === [
+ 'hostname' => $hostname,
+ 'service' => $service,
+ ];
+ }
+
+ protected function initializeMonitoringBackend()
+ {
+ $app = Icinga::app();
+ $modules = $app->getModuleManager();
+ if (!$modules->hasLoaded('monitoring') && $app->isCli()) {
+ $modules->loadEnabledModules();
+ }
+
+ if ($modules->hasLoaded('monitoring')) {
+ try {
+ $this->backend = MonitoringBackend::instance();
+ } catch (ConfigurationError $e) {
+ $this->backend = null;
+ }
+ }
+ }
+
+ protected function isAvailable(): bool
+ {
+ return $this->backend !== null;
+ }
+}
diff --git a/library/Director/Monitoring.php b/library/Director/Monitoring.php
deleted file mode 100644
index f5d4108..0000000
--- a/library/Director/Monitoring.php
+++ /dev/null
@@ -1,149 +0,0 @@
-<?php
-
-namespace Icinga\Module\Director;
-
-use Icinga\Application\Icinga;
-use Icinga\Authentication\Auth;
-use Icinga\Data\Filter\Filter;
-use Icinga\Module\Monitoring\Backend\MonitoringBackend;
-
-class Monitoring
-{
- protected $backend;
-
- public function __construct()
- {
- $app = Icinga::app();
- $modules = $app->getModuleManager();
- if (!$modules->hasLoaded('monitoring') && $app->isCli()) {
- $app->getModuleManager()->loadEnabledModules();
- }
-
- if ($modules->hasLoaded('monitoring')) {
- $this->backend = MonitoringBackend::instance();
- }
- }
-
- public function isAvailable()
- {
- return $this->backend !== null;
- }
-
- public function hasHost($hostname)
- {
- return $this->backend->select()->from('hostStatus', [
- 'hostname' => 'host_name',
- ])->where('host_name', $hostname)->fetchOne() === $hostname;
- }
-
- public function hasService($hostname, $service)
- {
- return (array) $this->prepareServiceKeyColumnQuery($hostname, $service)->fetchRow() === [
- 'hostname' => $hostname,
- 'service' => $service,
- ];
- }
-
- public function authCanEditHost(Auth $auth, $hostname)
- {
- if ($auth->hasPermission('director/monitoring/hosts')) {
- $restriction = null;
- foreach ($auth->getRestrictions('director/monitoring/rw-object-filter') as $restriction) {
- if ($this->hasHostWithExtraFilter($hostname, Filter::fromQueryString($restriction))) {
- return true;
- }
- }
- if ($restriction === null) {
- return $this->hasHost($hostname);
- }
- }
-
- return false;
- }
-
- public function authCanEditService(Auth $auth, $hostname, $service)
- {
- if ($hostname === null || $service === null) {
- // TODO: UUID support!
- return false;
- }
- if ($auth->hasPermission('director/monitoring/services')) {
- $restriction = null;
- foreach ($auth->getRestrictions('director/monitoring/rw-object-filter') as $restriction) {
- if ($this->hasServiceWithExtraFilter($hostname, $service, Filter::fromQueryString($restriction))) {
- return true;
- }
- }
- if ($restriction === null) {
- return $this->hasService($hostname, $service);
- }
- }
-
- return false;
- }
-
- public function hasHostWithExtraFilter($hostname, Filter $filter)
- {
- return $this->backend->select()->from('hostStatus', [
- 'hostname' => 'host_name',
- ])->where('host_name', $hostname)->applyFilter($filter)->fetchOne() === $hostname;
- }
-
- public function hasServiceWithExtraFilter($hostname, $service, Filter $filter)
- {
- return (array) $this
- ->prepareServiceKeyColumnQuery($hostname, $service)
- ->applyFilter($filter)
- ->fetchRow() === [
- 'hostname' => $hostname,
- 'service' => $service,
- ];
- }
-
- public function getHostState($hostname)
- {
- $hostStates = [
- '0' => 'up',
- '1' => 'down',
- '2' => 'unreachable',
- '99' => 'pending',
- ];
-
- $query = $this->backend->select()->from('hostStatus', [
- 'hostname' => 'host_name',
- 'state' => 'host_state',
- 'problem' => 'host_problem',
- 'acknowledged' => 'host_acknowledged',
- 'in_downtime' => 'host_in_downtime',
- 'output' => 'host_output',
- ])->where('host_name', $hostname);
-
- $res = $query->fetchRow();
- if ($res === false) {
- $res = (object) [
- 'hostname' => $hostname,
- 'state' => '99',
- 'problem' => '0',
- 'acknowledged' => '0',
- 'in_downtime' => '0',
- 'output' => null,
- ];
- }
-
- $res->state = $hostStates[$res->state];
-
- return $res;
- }
-
- protected function prepareServiceKeyColumnQuery($hostname, $service)
- {
- return $this->backend
- ->select()
- ->from('serviceStatus', [
- 'hostname' => 'host_name',
- 'service' => 'service_description',
- ])
- ->where('host_name', $hostname)
- ->where('service_description', $service);
- }
-}
diff --git a/library/Director/Objects/DirectorActivityLog.php b/library/Director/Objects/DirectorActivityLog.php
index cb041b6..2cecc2e 100644
--- a/library/Director/Objects/DirectorActivityLog.php
+++ b/library/Director/Objects/DirectorActivityLog.php
@@ -7,6 +7,7 @@ use Icinga\Module\Director\Db;
use Icinga\Authentication\Auth;
use Icinga\Application\Icinga;
use Icinga\Application\Logger;
+use stdClass;
class DirectorActivityLog extends DbObject
{
@@ -176,7 +177,19 @@ class DirectorActivityLog extends DbObject
{
$name = $object->getObjectName();
$type = $object->getTableName();
- $oldProps = json_encode($object->getPlainUnmodifiedObject());
+ /** @var stdClass $plainUnmodifiedObject */
+ $plainUnmodifiedObject = $object->getPlainUnmodifiedObject();
+
+ if ($object instanceof IcingaServiceSet) {
+ $services = [];
+ foreach ($object->getCachedServices() as $service) {
+ $services[$service->getObjectName()] = $service->toPlainObject();
+ }
+
+ $plainUnmodifiedObject->services = $services;
+ }
+
+ $oldProps = json_encode($plainUnmodifiedObject);
$data = [
'object_name' => $name,
diff --git a/library/Director/Objects/DirectorDatafield.php b/library/Director/Objects/DirectorDatafield.php
index 84db068..ced6218 100644
--- a/library/Director/Objects/DirectorDatafield.php
+++ b/library/Director/Objects/DirectorDatafield.php
@@ -2,28 +2,30 @@
namespace Icinga\Module\Director\Objects;
-use Icinga\Module\Director\Core\Json;
use Icinga\Module\Director\Data\Db\DbObjectWithSettings;
use Icinga\Module\Director\Db;
+use Icinga\Module\Director\DirectorObject\Automation\BasketSnapshotFieldResolver;
use Icinga\Module\Director\DirectorObject\Automation\CompareBasketObject;
-use Icinga\Module\Director\Exception\DuplicateKeyException;
use Icinga\Module\Director\Forms\IcingaServiceForm;
use Icinga\Module\Director\Hook\DataTypeHook;
use Icinga\Module\Director\Resolver\OverriddenVarsResolver;
use Icinga\Module\Director\Web\Form\DirectorObjectForm;
-use InvalidArgumentException;
+use Ramsey\Uuid\Uuid;
+use stdClass;
use Zend_Form_Element as ZfElement;
class DirectorDatafield extends DbObjectWithSettings
{
protected $table = 'director_datafield';
-
protected $keyName = 'id';
-
protected $autoincKeyName = 'id';
+ protected $uuidColumn = 'uuid';
+ protected $settingsTable = 'director_datafield_setting';
+ protected $settingsRemoteId = 'datafield_id';
protected $defaultProperties = [
'id' => null,
+ 'uuid' => null,
'category_id' => null,
'varname' => null,
'caption' => null,
@@ -33,16 +35,11 @@ class DirectorDatafield extends DbObjectWithSettings
];
protected $relations = [
- 'category' => 'DirectorDatafieldCategory'
+ 'category' => 'DirectorDatafieldCategory'
];
- protected $settingsTable = 'director_datafield_setting';
-
- protected $settingsRemoteId = 'datafield_id';
-
- /** @var DirectorDatafieldCategory|null */
+ /** @var ?DirectorDatafieldCategory */
private $category;
-
private $object;
public static function fromDbRow($row, Db $connection)
@@ -70,10 +67,9 @@ class DirectorDatafield extends DbObjectWithSettings
}
/**
- * @return DirectorDatafieldCategory|null
* @throws \Icinga\Exception\NotFoundError
*/
- public function getCategory()
+ public function getCategory(): ?DirectorDatafieldCategory
{
if ($this->category) {
return $this->category;
@@ -84,7 +80,7 @@ class DirectorDatafield extends DbObjectWithSettings
}
}
- public function getCategoryName()
+ public function getCategoryName(): ?string
{
$category = $this->getCategory();
if ($category === null) {
@@ -105,27 +101,26 @@ class DirectorDatafield extends DbObjectWithSettings
}
$this->category = $category;
} else {
- if (DirectorDatafieldCategory::exists($category, $this->getConnection())) {
- $this->setCategory(DirectorDatafieldCategory::load($category, $this->getConnection()));
+ if ($category = DirectorDatafieldCategory::loadOptional($category, $this->getConnection())) {
+ $this->setCategory($category);
} else {
$this->setCategory(DirectorDatafieldCategory::create([
'category_name' => $category
], $this->getConnection()));
}
}
-
- return $this;
}
/**
- * @return object
* @throws \Icinga\Exception\NotFoundError
*/
- public function export()
+ public function export(): stdClass
{
$plain = (object) $this->getProperties();
- $plain->originalId = $plain->id;
unset($plain->id);
+ if ($uuid = $this->get('uuid')) {
+ $plain->uuid = Uuid::fromBytes($uuid)->toString();
+ }
$plain->settings = (object) $this->getSettings();
if (property_exists($plain->settings, 'datalist_id')) {
@@ -144,63 +139,35 @@ class DirectorDatafield extends DbObjectWithSettings
}
/**
- * @param $plain
- * @param Db $db
- * @param bool $replace
- * @return DirectorDatafield
* @throws \Icinga\Exception\NotFoundError
*/
- public static function import($plain, Db $db, $replace = false)
+ public static function import(stdClass $plain, Db $db): DirectorDatafield
{
- $properties = (array) $plain;
- if (isset($properties['originalId'])) {
- $id = $properties['originalId'];
- unset($properties['originalId']);
- } else {
- $id = null;
- }
-
- if (isset($properties['settings']->datalist)) {
- // Just try to load the list, import should fail if missing
- $list = DirectorDatalist::load(
- $properties['settings']->datalist,
- $db
- );
- } else {
- $list = null;
- }
-
- $compare = Json::decode(Json::encode($properties));
- if ($id && static::exists($id, $db)) {
- $existing = static::loadWithAutoIncId($id, $db);
- $existingProperties = (array) $existing->export();
- unset($existingProperties['originalId']);
- if (CompareBasketObject::equals((object) $compare, (object) $existingProperties)) {
- return $existing;
+ $dba = $db->getDbAdapter();
+ if ($uuid = $plain->uuid ?? null) {
+ $uuid = Uuid::fromString($uuid);
+ if ($candidate = DirectorDatafield::loadWithUniqueId($uuid, $db)) {
+ BasketSnapshotFieldResolver::fixOptionalDatalistReference($plain, $db);
+ assert($candidate instanceof DirectorDatafield);
+ $candidate->setProperties((array) $plain);
+ return $candidate;
}
}
-
- if ($list) {
- unset($properties['settings']->datalist);
- $properties['settings']->datalist_id = $list->get('id');
- }
-
- $dba = $db->getDbAdapter();
- $query = $dba->select()
- ->from('director_datafield')
- ->where('varname = ?', $plain->varname);
+ $query = $dba->select()->from('director_datafield')->where('varname = ?', $plain->varname);
$candidates = DirectorDatafield::loadAll($db, $query);
foreach ($candidates as $candidate) {
$export = $candidate->export();
- unset($export->originalId);
CompareBasketObject::normalize($export);
- if (CompareBasketObject::equals($export, $compare)) {
+ unset($export->uuid);
+ unset($plain->originalId);
+ if (CompareBasketObject::equals($export, $plain)) {
return $candidate;
}
}
+ BasketSnapshotFieldResolver::fixOptionalDatalistReference($plain, $db);
- return static::create($properties, $db);
+ return static::create((array) $plain, $db);
}
protected function beforeStore()
@@ -223,7 +190,7 @@ class DirectorDatafield extends DbObjectWithSettings
return $this->object;
}
- public function getFormElement(DirectorObjectForm $form, $name = null)
+ public function getFormElement(DirectorObjectForm $form, $name = null): ?ZfElement
{
$className = $this->get('datatype');
@@ -305,7 +272,7 @@ class DirectorDatafield extends DbObjectWithSettings
}
}
- protected function eventuallyGetResolvedCommandVar(IcingaObject $object, $varName)
+ protected function eventuallyGetResolvedCommandVar(IcingaObject $object, $varName): ?array
{
if (! $object->hasRelation('check_command')) {
return null;
diff --git a/library/Director/Objects/DirectorDatalist.php b/library/Director/Objects/DirectorDatalist.php
index ae5c983..1bb821b 100644
--- a/library/Director/Objects/DirectorDatalist.php
+++ b/library/Director/Objects/DirectorDatalist.php
@@ -4,68 +4,35 @@ namespace Icinga\Module\Director\Objects;
use Exception;
use Icinga\Module\Director\Data\Db\DbObject;
-use Icinga\Module\Director\Db;
+use Icinga\Module\Director\DataType\DataTypeDatalist;
use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
use Icinga\Module\Director\Exception\DuplicateKeyException;
class DirectorDatalist extends DbObject implements ExportInterface
{
protected $table = 'director_datalist';
-
protected $keyName = 'list_name';
-
protected $autoincKeyName = 'id';
+ protected $uuidColumn = 'uuid';
- protected $defaultProperties = array(
+ protected $defaultProperties = [
'id' => null,
+ 'uuid' => null,
'list_name' => null,
'owner' => null
- );
+ ];
/** @var DirectorDatalistEntry[] */
- protected $storedEntries;
+ protected $entries;
public function getUniqueIdentifier()
{
return $this->get('list_name');
}
- /**
- * @param $plain
- * @param Db $db
- * @param bool $replace
- * @return static
- * @throws \Icinga\Exception\NotFoundError
- * @throws DuplicateKeyException
- */
- public static function import($plain, Db $db, $replace = false)
- {
- $properties = (array) $plain;
- if (isset($properties['originalId'])) {
- unset($properties['originalId']);
- } else {
- $id = null;
- }
- $name = $properties['list_name'];
-
- if ($replace && static::exists($name, $db)) {
- $object = static::load($name, $db);
- } elseif (static::exists($name, $db)) {
- throw new DuplicateKeyException(
- 'Data List %s already exists',
- $name
- );
- } else {
- $object = static::create([], $db);
- }
- $object->setProperties($properties);
-
- return $object;
- }
-
- public function setEntries($entries)
+ public function setEntries($entries): void
{
- $existing = $this->getStoredEntries();
+ $existing = $this->getEntries();
$new = [];
$seen = [];
@@ -101,15 +68,13 @@ class DirectorDatalist extends DbObject implements ExportInterface
$this->hasBeenModified = true;
}
- $this->storedEntries = $existing;
- ksort($this->storedEntries);
-
- return $this;
+ $this->entries = $existing;
+ ksort($this->entries);
}
protected function beforeDelete()
{
- if ($this->hasBeenUsed()) {
+ if ($this->isInUse()) {
throw new Exception(
sprintf(
"Cannot delete '%s', as the datalist '%s' is currently being used.",
@@ -120,9 +85,13 @@ class DirectorDatalist extends DbObject implements ExportInterface
}
}
- protected function hasBeenUsed()
+ protected function isInUse(): bool
{
- $datalistType = 'Icinga\\Module\\Director\\DataType\\DataTypeDatalist';
+ $id = $this->get('id');
+ if ($id === null) {
+ return false;
+ }
+
$db = $this->getDb();
$dataFieldsCheck = $db->select()
@@ -137,8 +106,8 @@ class DirectorDatalist extends DbObject implements ExportInterface
'l.id = dfs.setting_value',
[]
)
- ->where('datatype = ?', $datalistType)
- ->where('setting_value = ?', $this->get('id'));
+ ->where('datatype = ?', DataTypeDatalist::class)
+ ->where('setting_value = ?', $id);
if ($db->fetchOne($dataFieldsCheck)) {
return true;
@@ -147,7 +116,7 @@ class DirectorDatalist extends DbObject implements ExportInterface
$syncCheck = $db->select()
->from(['sp' =>'sync_property'], ['source_expression'])
->where('sp.destination_field = ?', 'list_id')
- ->where('sp.source_expression = ?', $this->get('id'));
+ ->where('sp.source_expression = ?', $id);
if ($db->fetchOne($syncCheck)) {
return true;
@@ -161,65 +130,38 @@ class DirectorDatalist extends DbObject implements ExportInterface
*/
public function onStore()
{
- if ($this->storedEntries) {
+ if ($this->entries) {
$db = $this->getConnection();
$removedKeys = [];
$myId = $this->get('id');
- foreach ($this->storedEntries as $key => $entry) {
+ foreach ($this->entries as $key => $entry) {
if ($entry->shouldBeRemoved()) {
$entry->delete();
$removedKeys[] = $key;
} else {
- if (! $entry->hasBeenLoadedFromDb()) {
- $entry->set('list_id', $myId);
- }
$entry->set('list_id', $myId);
$entry->store($db);
}
}
foreach ($removedKeys as $key) {
- unset($this->storedEntries[$key]);
- }
- }
- }
-
- /**
- * @deprecated please use \Icinga\Module\Director\Data\Exporter
- * @return object
- */
- public function export()
- {
- $plain = (object) $this->getProperties();
- $plain->originalId = $plain->id;
- unset($plain->id);
-
- $plain->entries = [];
- foreach ($this->getStoredEntries() as $key => $entry) {
- if ($entry->shouldBeRemoved()) {
- continue;
+ unset($this->entries[$key]);
}
- $plainEntry = (object) $entry->getProperties();
- unset($plainEntry->list_id);
-
- $plain->entries[] = $plainEntry;
}
-
- return $plain;
}
- protected function getStoredEntries()
+ public function getEntries(): array
{
- if ($this->storedEntries === null) {
- if ($id = $this->get('id')) {
- $this->storedEntries = DirectorDatalistEntry::loadAllForList($this);
- ksort($this->storedEntries);
+ if ($this->entries === null) {
+ if ($this->get('id')) {
+ $this->entries = DirectorDatalistEntry::loadAllForList($this);
+ ksort($this->entries);
} else {
- $this->storedEntries = [];
+ $this->entries = [];
}
}
- return $this->storedEntries;
+ return $this->entries;
}
}
diff --git a/library/Director/Objects/DirectorDatalistEntry.php b/library/Director/Objects/DirectorDatalistEntry.php
index 086686a..278de97 100644
--- a/library/Director/Objects/DirectorDatalistEntry.php
+++ b/library/Director/Objects/DirectorDatalistEntry.php
@@ -51,7 +51,7 @@ class DirectorDatalistEntry extends DbObject
} else {
throw new RuntimeException(
'Expected array or null for allowed_roles, got %s',
- var_export($roles, 1)
+ var_export($roles, true)
);
}
}
diff --git a/library/Director/Objects/DirectorDeploymentLog.php b/library/Director/Objects/DirectorDeploymentLog.php
index 0794a3c..e9b07ce 100644
--- a/library/Director/Objects/DirectorDeploymentLog.php
+++ b/library/Director/Objects/DirectorDeploymentLog.php
@@ -46,7 +46,11 @@ class DirectorDeploymentLog extends DbObject
public function getConfigHexChecksum()
{
- return bin2hex($this->config_checksum);
+ $checksum = $this->get('config_checksum');
+ if ($checksum === null) {
+ return null;
+ }
+ return bin2hex($checksum);
}
public function getConfig()
diff --git a/library/Director/Objects/DirectorJob.php b/library/Director/Objects/DirectorJob.php
index 361f764..2a18d52 100644
--- a/library/Director/Objects/DirectorJob.php
+++ b/library/Director/Objects/DirectorJob.php
@@ -122,8 +122,12 @@ class DirectorJob extends DbObjectWithSettings implements ExportInterface, Insta
return false;
}
+ if ($this->get('ts_last_attempt') === null) {
+ return true;
+ }
+
return (
- strtotime((int) $this->get('ts_last_attempt')) + $this->get('run_interval') * 2
+ strtotime($this->get('ts_last_attempt')) + $this->get('run_interval') * 2
) < time();
}
@@ -194,87 +198,6 @@ class DirectorJob extends DbObjectWithSettings implements ExportInterface, Insta
}
/**
- * @return object
- * @deprecated please use \Icinga\Module\Director\Data\Exporter
- * @throws \Icinga\Exception\NotFoundError
- */
- public function export()
- {
- $plain = (object) $this->getProperties();
- $plain->originalId = $plain->id;
- unset($plain->id);
- unset($plain->timeperiod_id);
- if ($this->hasTimeperiod()) {
- $plain->timeperiod = $this->timeperiod()->getObjectName();
- }
-
- foreach ($this->stateProperties as $key) {
- unset($plain->$key);
- }
- $plain->settings = $this->getInstance()->exportSettings();
-
- return $plain;
- }
-
- /**
- * @param $plain
- * @param Db $db
- * @param bool $replace
- * @return DirectorJob
- * @throws DuplicateKeyException
- * @throws NotFoundError
- */
- public static function import($plain, Db $db, $replace = false)
- {
- $dummy = new static;
- $idCol = $dummy->autoincKeyName;
- $keyCol = $dummy->keyName;
- $properties = (array) $plain;
- if (isset($properties['originalId'])) {
- $id = $properties['originalId'];
- unset($properties['originalId']);
- } else {
- $id = null;
- }
- $name = $properties[$keyCol];
-
- if ($replace && $id && static::existsWithNameAndId($name, $id, $db)) {
- $object = static::loadWithAutoIncId($id, $db);
- } elseif ($replace && static::exists($name, $db)) {
- $object = static::load($name, $db);
- } elseif (static::exists($name, $db)) {
- throw new DuplicateKeyException(
- 'Director Job "%s" already exists',
- $name
- );
- } else {
- $object = static::create([], $db);
- }
-
- $settings = (array) $properties['settings'];
-
- if (array_key_exists('source', $settings) && ! (array_key_exists('source_id', $settings))) {
- $val = ImportSource::load($settings['source'], $db)->get('id');
- $settings['source_id'] = $val;
- unset($settings['source']);
- }
-
- if (array_key_exists('rule', $settings) && ! (array_key_exists('rule_id', $settings))) {
- $val = SyncRule::load($settings['rule'], $db)->get('id');
- $settings['rule_id'] = $val;
- unset($settings['rule']);
- }
-
- $properties['settings'] = (object) $settings;
- $object->setProperties($properties);
- if ($id !== null) {
- $object->reallySet($idCol, $id);
- }
-
- return $object;
- }
-
- /**
* @param string $name
* @param int $id
* @param Db $connection
diff --git a/library/Director/Objects/GroupMembershipResolver.php b/library/Director/Objects/GroupMembershipResolver.php
index f5ef418..7266de9 100644
--- a/library/Director/Objects/GroupMembershipResolver.php
+++ b/library/Director/Objects/GroupMembershipResolver.php
@@ -211,7 +211,7 @@ abstract class GroupMembershipResolver
$query,
$object,
'o',
- Db\IcingaObjectFilterHelper::INHERIT_DIRECT_OR_INDIRECT
+ IcingaObjectFilterHelper::INHERIT_DIRECT_OR_INDIRECT
);
foreach ($object::loadAll($this->connection, $query) as $child) {
@@ -352,7 +352,7 @@ abstract class GroupMembershipResolver
$type = $this->type;
if ($this->groupMap === null) {
$this->groupMap = $this->db->fetchPairs(
- $this->db->select()->from("icinga_${type}group", ['object_name', 'id'])
+ $this->db->select()->from("icinga_{$type}group", ['object_name', 'id'])
);
}
@@ -390,9 +390,9 @@ abstract class GroupMembershipResolver
$db->delete(
$this->getResolvedTableName(),
sprintf(
- "(${type}group_id = %d AND ${type}_id = %d)",
- $row["${type}group_id"],
- $row["${type}_id"]
+ "({$type}group_id = %d AND {$type}_id = %d)",
+ $row["{$type}group_id"],
+ $row["{$type}_id"]
)
);
}
@@ -416,16 +416,16 @@ abstract class GroupMembershipResolver
foreach ($objectIds as $objectId) {
if (! array_key_exists($objectId, $right[$groupId])) {
$diff[] = array(
- "${type}group_id" => $groupId,
- "${type}_id" => $objectId,
+ "{$type}group_id" => $groupId,
+ "{$type}_id" => $objectId,
);
}
}
} else {
foreach ($objectIds as $objectId) {
$diff[] = array(
- "${type}group_id" => $groupId,
- "${type}_id" => $objectId,
+ "{$type}group_id" => $groupId,
+ "{$type}_id" => $objectId,
);
}
}
@@ -445,16 +445,16 @@ abstract class GroupMembershipResolver
$query = $this->db->select()->from(
array('hgh' => $this->getResolvedTableName()),
array(
- 'group_id' => "${type}group_id",
- 'object_id' => "${type}_id",
+ 'group_id' => "{$type}group_id",
+ 'object_id' => "{$type}_id",
)
);
- $this->addMembershipWhere($query, "${type}_id", $this->objects);
- $this->addMembershipWhere($query, "${type}group_id", $this->groups);
+ $this->addMembershipWhere($query, "{$type}_id", $this->objects);
+ $this->addMembershipWhere($query, "{$type}group_id", $this->groups);
if (! empty($this->groups)) {
// load staticGroups (we touched here) additionally, so we can compare changes
- $this->addMembershipWhere($query, "${type}group_id", $this->staticGroups);
+ $this->addMembershipWhere($query, "{$type}group_id", $this->staticGroups);
}
foreach ($this->db->fetchAll($query) as $row) {
@@ -602,7 +602,7 @@ abstract class GroupMembershipResolver
{
$type = $this->getType();
$query = $this->db->select()->from(
- array('hg' => "icinga_${type}group"),
+ array('hg' => "icinga_{$type}group"),
array(
'id',
'assign_filter',
@@ -629,7 +629,7 @@ abstract class GroupMembershipResolver
protected function getTableName()
{
$type = $this->getType();
- return "icinga_${type}group_${type}";
+ return "icinga_{$type}group_{$type}";
}
protected function getResolvedTableName()
diff --git a/library/Director/Objects/IcingaArguments.php b/library/Director/Objects/IcingaArguments.php
index e788da8..22bf914 100644
--- a/library/Director/Objects/IcingaArguments.php
+++ b/library/Director/Objects/IcingaArguments.php
@@ -155,7 +155,9 @@ class IcingaArguments implements Iterator, Countable, IcingaConfigRenderer
if (property_exists($value, 'type')) {
// argument is directly set as function, no further properties
if ($value->type === 'Function') {
- $attrs['argument_value'] = self::COMMENT_DSL_UNSUPPORTED;
+ $attrs['argument_value'] = property_exists($value, 'body')
+ ? $value->body
+ : self::COMMENT_DSL_UNSUPPORTED;
$attrs['argument_format'] = 'expression';
}
} elseif (property_exists($value, 'value')) {
@@ -296,6 +298,7 @@ class IcingaArguments implements Iterator, Countable, IcingaConfigRenderer
$this->arguments = IcingaCommandArgument::loadAll($connection, $query, 'argument_name');
$this->cloneStored();
$this->refreshIndex();
+ $this->modified = false;
return $this;
}
@@ -360,6 +363,7 @@ class IcingaArguments implements Iterator, Countable, IcingaConfigRenderer
}
$this->refreshIndex();
$this->cloneStored();
+ $this->modified = false;
}
/**
@@ -393,7 +397,9 @@ class IcingaArguments implements Iterator, Countable, IcingaConfigRenderer
unset($this->arguments[$key]);
}
+ $this->refreshIndex();
$this->cloneStored();
+ $this->modified = false;
return $this;
}
diff --git a/library/Director/Objects/IcingaCommand.php b/library/Director/Objects/IcingaCommand.php
index 35f38a4..8c5aed0 100644
--- a/library/Director/Objects/IcingaCommand.php
+++ b/library/Director/Objects/IcingaCommand.php
@@ -2,9 +2,7 @@
namespace Icinga\Module\Director\Objects;
-use Icinga\Module\Director\Db;
use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
-use Icinga\Module\Director\Exception\DuplicateKeyException;
use Icinga\Module\Director\IcingaConfig\IcingaConfigHelper as c;
use Icinga\Module\Director\IcingaConfig\IcingaLegacyConfigHelper as c1;
use Icinga\Module\Director\Objects\Extension\Arguments;
@@ -129,10 +127,8 @@ class IcingaCommand extends IcingaObject implements ObjectWithArguments, ExportI
// return $value;
}
- if (self::$pluginDir !== null) {
- if (($pos = strpos($value, self::$pluginDir)) === 0) {
- $value = substr($value, strlen(self::$pluginDir) + 1);
- }
+ if (isset($value, self::$pluginDir) && strpos($value, self::$pluginDir) === 0) {
+ $value = substr($value, strlen(self::$pluginDir) + 1);
}
return $value;
@@ -212,89 +208,6 @@ class IcingaCommand extends IcingaObject implements ObjectWithArguments, ExportI
return $this->getObjectName();
}
- /**
- * @return object
- * @deprecated please use \Icinga\Module\Director\Data\Exporter
- * @throws \Icinga\Exception\NotFoundError
- */
- public function export()
- {
- $props = (array) $this->toPlainObject();
- if (isset($props['arguments'])) {
- foreach ($props['arguments'] as $key => $argument) {
- if (property_exists($argument, 'command_id')) {
- unset($props['arguments'][$key]->command_id);
- }
- }
- }
- $props['fields'] = $this->loadFieldReferences();
- ksort($props);
-
- return (object) $props;
- }
-
- /**
- * @param $plain
- * @param Db $db
- * @param bool $replace
- * @return IcingaCommand
- * @throws DuplicateKeyException
- * @throws \Icinga\Exception\NotFoundError
- */
- public static function import($plain, Db $db, $replace = false)
- {
- $properties = (array) $plain;
- $name = $properties['object_name'];
- $key = $name;
-
- if ($replace && static::exists($key, $db)) {
- $object = static::load($key, $db);
- } elseif (static::exists($key, $db)) {
- throw new DuplicateKeyException(
- 'Command "%s" already exists',
- $name
- );
- } else {
- $object = static::create([], $db);
- }
-
- unset($properties['fields']);
- $object->setProperties($properties);
-
- return $object;
- }
-
- /**
- * @deprecated please use \Icinga\Module\Director\Data\FieldReferenceLoader
- * @return array
- */
- protected function loadFieldReferences()
- {
- $db = $this->getDb();
-
- $res = $db->fetchAll(
- $db->select()->from([
- 'cf' => 'icinga_command_field'
- ], [
- 'cf.datafield_id',
- 'cf.is_required',
- 'cf.var_filter',
- ])->join(['df' => 'director_datafield'], 'df.id = cf.datafield_id', [])
- ->where('command_id = ?', $this->get('id'))
- ->order('varname ASC')
- );
-
- if (empty($res)) {
- return [];
- } else {
- foreach ($res as $field) {
- $field->datafield_id = (int) $field->datafield_id;
- }
-
- return $res;
- }
- }
-
protected function renderCommand()
{
$command = $this->get('command');
diff --git a/library/Director/Objects/IcingaDependency.php b/library/Director/Objects/IcingaDependency.php
index c9d9b89..abd92e6 100644
--- a/library/Director/Objects/IcingaDependency.php
+++ b/library/Director/Objects/IcingaDependency.php
@@ -3,9 +3,7 @@
namespace Icinga\Module\Director\Objects;
use Icinga\Exception\ConfigurationError;
-use Icinga\Module\Director\Db;
use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
-use Icinga\Module\Director\Exception\DuplicateKeyException;
use Icinga\Module\Director\IcingaConfig\IcingaConfigHelper as c;
use Icinga\Exception\NotFoundError;
use Icinga\Data\Filter\Filter;
@@ -80,52 +78,21 @@ class IcingaDependency extends IcingaObject implements ExportInterface
return $this->getObjectName();
}
- /**
- * @return object
- * @deprecated please use \Icinga\Module\Director\Data\Exporter
- * @throws \Icinga\Exception\NotFoundError
- */
- public function export()
+ public function parentHostIsVar()
{
- $props = (array) $this->toPlainObject();
- ksort($props);
-
- return (object) $props;
+ return $this->get('parent_host_var') !== null;
}
/**
- * @param $plain
- * @param Db $db
- * @param bool $replace
- * @return static
- * @throws DuplicateKeyException
- * @throws \Icinga\Exception\NotFoundError
+ * Check if the given string is a custom variable
+ *
+ * @param $string string
+ *
+ * @return false|int
*/
- public static function import($plain, Db $db, $replace = false)
+ protected function isCustomVar(string $string)
{
- $properties = (array) $plain;
- $name = $properties['object_name'];
- $key = $name;
-
- if ($replace && static::exists($key, $db)) {
- $object = static::load($key, $db);
- } elseif (static::exists($key, $db)) {
- throw new DuplicateKeyException(
- 'Dependency "%s" already exists',
- $name
- );
- } else {
- $object = static::create([], $db);
- }
-
- $object->setProperties($properties);
-
- return $object;
- }
-
- public function parentHostIsVar()
- {
- return $this->get('parent_host_var') !== null;
+ return preg_match('/^(?:host|service)\.vars\..+$/', $string);
}
/**
@@ -485,9 +452,16 @@ class IcingaDependency extends IcingaObject implements ExportInterface
public function renderParent_service_by_name()
{
// @codingStandardsIgnoreEnd
+ $var = $this->get('parent_service_by_name');
+ if ($this->isCustomVar($var)) {
+ return c::renderKeyValue(
+ 'parent_service_name',
+ $var
+ );
+ }
return c::renderKeyValue(
'parent_service_name',
- c::renderString($this->get('parent_service_by_name'))
+ c::renderString($var)
);
}
@@ -506,7 +480,7 @@ class IcingaDependency extends IcingaObject implements ExportInterface
protected function resolveUnresolvedRelatedProperty($name)
{
$short = substr($name, 0, -3);
- /** @var IcingaObject $class */
+ /** @var IcingaObject|string $class */
$class = $this->getRelationClass($short);
$objKey = $this->unresolvedRelatedProperties[$name];
@@ -589,7 +563,25 @@ class IcingaDependency extends IcingaObject implements ExportInterface
$this->reallySet($name, $object->get('id'));
unset($this->unresolvedRelatedProperties[$name]);
} else {
- throw new NotFoundError('Unable to resolve related property: "%s"', $name);
+ // Depend on a single service on a single host. Rare case, as usually you want to
+ // depend on a service on the very same host - and leave the Host field empty. The
+ // latter is already being handled above. This duplicates some code, but I'll leave
+ // it this way for now. There might have been a reason for the parent_host_id = null
+ // check in that code.
+ if ($name === 'parent_service_id' && $this->get('object_type') === 'apply') {
+ $this->reallySet(
+ 'parent_service_by_name',
+ $this->unresolvedRelatedProperties[$name]
+ );
+ $this->reallySet('parent_service_id', null);
+ unset($this->unresolvedRelatedProperties[$name]);
+ return;
+ }
+ throw new NotFoundError(sprintf(
+ 'Unable to resolve related property: %s "%s"',
+ $name,
+ $this->unresolvedRelatedProperties[$name]
+ ));
}
}
@@ -620,8 +612,12 @@ class IcingaDependency extends IcingaObject implements ExportInterface
$related = parent::getRelatedProperty($key);
// handle special case for plain string parent service on Dependency
// Apply rules
- if ($related === null && $key === 'parent_service'
- && null !== $this->get('parent_service_by_name')
+ if ($related === null
+ && $key === 'parent_service'
+ && (
+ $this->get('parent_service_by_name')
+ && ! $this->isCustomVar($this->get('parent_service_by_name'))
+ )
) {
return $this->get('parent_service_by_name');
}
diff --git a/library/Director/Objects/IcingaHost.php b/library/Director/Objects/IcingaHost.php
index 2731f4a..7859324 100644
--- a/library/Director/Objects/IcingaHost.php
+++ b/library/Director/Objects/IcingaHost.php
@@ -7,7 +7,6 @@ use Icinga\Exception\NotFoundError;
use Icinga\Module\Director\Data\PropertiesFilter;
use Icinga\Module\Director\Db;
use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
-use Icinga\Module\Director\Exception\DuplicateKeyException;
use Icinga\Module\Director\IcingaConfig\IcingaConfig;
use Icinga\Module\Director\IcingaConfig\IcingaConfigHelper as c;
use Icinga\Module\Director\IcingaConfig\IcingaLegacyConfigHelper as c1;
@@ -310,88 +309,18 @@ class IcingaHost extends IcingaObject implements ExportInterface
}
}
- /**
- * @return object
- * @deprecated please use \Icinga\Module\Director\Data\Exporter
- * @throws \Icinga\Exception\NotFoundError
- */
- public function export()
+ protected function rendersConditionalTemplate(): bool
{
- // TODO: ksort in toPlainObject?
- $props = (array) $this->toPlainObject();
- $props['fields'] = $this->loadFieldReferences();
- ksort($props);
-
- return (object) $props;
- }
-
- /**
- * @param $plain
- * @param Db $db
- * @param bool $replace
- * @return IcingaHost
- * @throws DuplicateKeyException
- * @throws \Icinga\Exception\NotFoundError
- */
- public static function import($plain, Db $db, $replace = false)
- {
- $properties = (array) $plain;
- $name = $properties['object_name'];
- if ($properties['object_type'] !== 'template') {
- throw new InvalidArgumentException(sprintf(
- 'Can import only Templates, got "%s" for "%s"',
- $properties['object_type'],
- $name
- ));
- }
- $key = $name;
-
- if ($replace && static::exists($key, $db)) {
- $object = static::load($key, $db);
- } elseif (static::exists($key, $db)) {
- throw new DuplicateKeyException(
- 'Service Template "%s" already exists',
- $name
- );
- } else {
- $object = static::create([], $db);
- }
-
- // $object->newFields = $properties['fields'];
- unset($properties['fields']);
- $object->setProperties($properties);
-
- return $object;
+ return $this->getRenderingZone() === self::ALL_NON_GLOBAL_ZONES;
}
- /**
- * @deprecated please use \Icinga\Module\Director\Data\FieldReferenceLoader
- * @return array
- */
- protected function loadFieldReferences()
+ protected function getDefaultZone(IcingaConfig $config = null)
{
- $db = $this->getDb();
-
- $res = $db->fetchAll(
- $db->select()->from([
- 'hf' => 'icinga_host_field'
- ], [
- 'hf.datafield_id',
- 'hf.is_required',
- 'hf.var_filter',
- ])->join(['df' => 'director_datafield'], 'df.id = hf.datafield_id', [])
- ->where('host_id = ?', $this->get('id'))
- ->order('varname ASC')
- );
-
- if (empty($res)) {
- return [];
- } else {
- foreach ($res as $field) {
- $field->datafield_id = (int) $field->datafield_id;
- }
- return $res;
+ if ($this->isTemplate()) {
+ return self::ALL_NON_GLOBAL_ZONES;
}
+
+ return parent::getDefaultZone();
}
public function beforeDelete()
diff --git a/library/Director/Objects/IcingaHostGroup.php b/library/Director/Objects/IcingaHostGroup.php
index e11f672..e78e931 100644
--- a/library/Director/Objects/IcingaHostGroup.php
+++ b/library/Director/Objects/IcingaHostGroup.php
@@ -31,12 +31,8 @@ class IcingaHostGroup extends IcingaObjectGroup
return $this;
}
- protected function notifyResolvers()
+ protected function getMemberShipResolver()
{
- $resolver = $this->getHostGroupMembershipResolver();
- $resolver->addGroup($this);
- $resolver->refreshDb();
-
- return $this;
+ return $this->getHostGroupMembershipResolver();
}
}
diff --git a/library/Director/Objects/IcingaNotification.php b/library/Director/Objects/IcingaNotification.php
index 9c5d08d..4768704 100644
--- a/library/Director/Objects/IcingaNotification.php
+++ b/library/Director/Objects/IcingaNotification.php
@@ -2,9 +2,8 @@
namespace Icinga\Module\Director\Objects;
-use Icinga\Module\Director\Db;
+use Icinga\Module\Director\CustomVariable\CustomVariables;
use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
-use Icinga\Module\Director\Exception\DuplicateKeyException;
use Icinga\Module\Director\IcingaConfig\IcingaConfigHelper as c;
use RuntimeException;
@@ -29,6 +28,8 @@ class IcingaNotification extends IcingaObject implements ExportInterface
'notification_interval' => null,
'period_id' => null,
'zone_id' => null,
+ 'users_var' => null,
+ 'user_groups_var' => null,
'assign_filter' => null,
];
@@ -85,92 +86,76 @@ class IcingaNotification extends IcingaObject implements ExportInterface
* @codingStandardsIgnoreStart
* @return string
*/
- protected function renderTimes_end()
+ protected function renderUsers_var()
{
// @codingStandardsIgnoreEnd
- return c::renderKeyValue('times.end', c::renderInterval($this->get('times_end')));
- }
-
- public function getUniqueIdentifier()
- {
- return $this->getObjectName();
+ return '';
}
/**
- * @return \stdClass
- * @deprecated please use \Icinga\Module\Director\Data\Exporter
- * @throws \Icinga\Exception\NotFoundError
+ * @codingStandardsIgnoreStart
+ * @return string
*/
- public function export()
+ protected function renderUser_groups_var()
{
- // TODO: ksort in toPlainObject?
- $props = (array) $this->toPlainObject();
- $props['fields'] = $this->loadFieldReferences();
- ksort($props);
-
- return (object) $props;
+ // @codingStandardsIgnoreEnd
+ return '';
}
- /**
- * @param $plain
- * @param Db $db
- * @param bool $replace
- * @return static
- * @throws DuplicateKeyException
- * @throws \Icinga\Exception\NotFoundError
- */
- public static function import($plain, Db $db, $replace = false)
+ protected function renderUserVarsSuffixFor($property)
{
- $properties = (array) $plain;
- $name = $properties['object_name'];
- $key = $name;
-
- if ($replace && static::exists($key, $db)) {
- $object = static::load($key, $db);
- } elseif (static::exists($key, $db)) {
- throw new DuplicateKeyException(
- 'Notification "%s" already exists',
- $name
- );
- } else {
- $object = static::create([], $db);
+ $varName = $this->getResolvedProperty("{$property}_var");
+ if ($varName === null) {
+ return '';
+ }
+
+ $varSuffix = CustomVariables::renderKeySuffix($varName);
+ $indent = ' ';
+ $objectType = $this->get('apply_to');
+ if ($objectType === 'service') {
+ return "{$indent}if (service.vars$varSuffix) {\n"
+ . c::renderKeyOperatorValue($property, '+=', "service.vars$varSuffix", $indent . ' ')
+ . "$indent} else {\n"
+ . $this->getHostSnippet($indent . ' ')
+ . "$indent if (host.vars$varSuffix) { "
+ . c::renderKeyOperatorValue($property, '+=', "host.vars$varSuffix }", '')
+ . "$indent}\n";
+ } elseif ($objectType === 'host') {
+ return $this->getHostSnippet()
+ . "{$indent}if (host.vars$varSuffix) { "
+ . c::renderKeyOperatorValue($property, '+=', "host.vars$varSuffix }");
}
- // $object->newFields = $properties['fields'];
- unset($properties['fields']);
- $object->setProperties($properties);
+ return '';
+ }
+
+ protected function getHostSnippet($indent = ' ')
+ {
+ return "{$indent}if (! host) {\n"
+ . "$indent var host = get_host(host_name)\n"
+ . "$indent}\n";
+ }
- return $object;
+ protected function renderSuffix()
+ {
+ return $this->renderUserVarsSuffixFor('users')
+ . $this->renderUserVarsSuffixFor('user_groups')
+ . parent::renderSuffix();
}
/**
- * @deprecated please use \Icinga\Module\Director\Data\FieldReferenceLoader
- * @return array
+ * @codingStandardsIgnoreStart
+ * @return string
*/
- protected function loadFieldReferences()
+ protected function renderTimes_end()
{
- $db = $this->getDb();
-
- $res = $db->fetchAll(
- $db->select()->from([
- 'nf' => 'icinga_notification_field'
- ], [
- 'nf.datafield_id',
- 'nf.is_required',
- 'nf.var_filter',
- ])->join(['df' => 'director_datafield'], 'df.id = nf.datafield_id', [])
- ->where('notification_id = ?', $this->get('id'))
- ->order('varname ASC')
- );
-
- if (empty($res)) {
- return [];
- } else {
- foreach ($res as $field) {
- $field->datafield_id = (int) $field->datafield_id;
- }
- return $res;
- }
+ // @codingStandardsIgnoreEnd
+ return c::renderKeyValue('times.end', c::renderInterval($this->get('times_end')));
+ }
+
+ public function getUniqueIdentifier()
+ {
+ return $this->getObjectName();
}
/**
diff --git a/library/Director/Objects/IcingaObject.php b/library/Director/Objects/IcingaObject.php
index 04ae32b..3b6236d 100644
--- a/library/Director/Objects/IcingaObject.php
+++ b/library/Director/Objects/IcingaObject.php
@@ -27,6 +27,7 @@ use RuntimeException;
abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
{
const RESOLVE_ERROR = '(unable to resolve)';
+ const ALL_NON_GLOBAL_ZONES = '(all non-global zones)';
protected $keyName = 'object_name';
@@ -63,9 +64,6 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
protected $type;
- /* key/value!! */
- protected $booleans = [];
-
// Property suffixed with _id must exist
protected $relations = [
// property => PropertyClass
@@ -142,11 +140,6 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
return $this->connection;
}
- public function propertyIsBoolean($property)
- {
- return array_key_exists($property, $this->booleans);
- }
-
public function propertyIsInterval($property)
{
return array_key_exists($property, $this->intervalProperties);
@@ -771,10 +764,6 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
return $this;
}
- if ($this->propertyIsBoolean($key)) {
- return parent::set($key, DbDataFormatter::normalizeBoolean($value));
- }
-
// e.g. zone_id
if ($this->propertyIsRelation($key)) {
return $this->setRelation($key, $value);
@@ -906,20 +895,20 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
$type = strtolower($this->getType());
$query = $this->db->select()->from(
- ['gr' => "icinga_${type}group_${type}_resolved"],
+ ['gr' => "icinga_{$type}group_{$type}_resolved"],
['g.object_name']
)->join(
- ['g' => "icinga_${type}group"],
- "g.id = gr.${type}group_id",
+ ['g' => "icinga_{$type}group"],
+ "g.id = gr.{$type}group_id",
[]
)->joinLeft(
- ['go' => "icinga_${type}group_${type}"],
- "go.${type}group_id = gr.${type}group_id AND go.${type}_id = " . (int) $id,
+ ['go' => "icinga_{$type}group_{$type}"],
+ "go.{$type}group_id = gr.{$type}group_id AND go.{$type}_id = " . (int) $id,
[]
)->where(
- "gr.${type}_id = ?",
+ "gr.{$type}_id = ?",
(int) $id
- )->where("go.${type}_id IS NULL")->order('g.object_name');
+ )->where("go.{$type}_id IS NULL")->order('g.object_name');
return $this->db->fetchCol($query);
}
@@ -1812,9 +1801,21 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
return;
}
- $config->configFile(
- 'zones.d/' . $this->getRenderingZone($config) . '/' . $this->getRenderingFilename()
- )->addObject($this);
+ foreach ($this->getRenderingZones($config) as $zone) {
+ $config->configFile(
+ 'zones.d/' . $zone . '/' . $this->getRenderingFilename()
+ )->addObject($this);
+ }
+ }
+
+ protected function getRenderingZones(IcingaConfig $config): array
+ {
+ $zone = $this->getRenderingZone($config);
+ if ($zone === self::ALL_NON_GLOBAL_ZONES) {
+ return $config->listNonGlobalZones();
+ }
+
+ return [$zone];
}
public function getRenderingFilename()
@@ -2193,7 +2194,12 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
protected function renderSuffix()
{
- return "}\n\n";
+ $prefix = '';
+ if ($this->rendersConditionalTemplate()) {
+ $prefix = '} ';
+ }
+
+ return "$prefix}\n\n";
}
protected function renderLegacySuffix()
@@ -2418,14 +2424,25 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
protected function renderObjectHeader()
{
+ $prefix = '';
+ $renderedName = c::renderString($this->getObjectName());
+ if ($this->rendersConditionalTemplate()) {
+ $prefix = sprintf('if (! get_template(%s, %s)) { ', $this->getType(), $renderedName);
+ }
return sprintf(
- "%s %s %s {\n",
+ "%s%s %s %s {\n",
+ $prefix,
$this->getObjectTypeName(),
$this->getType(),
- c::renderString($this->getObjectName())
+ $renderedName
);
}
+ protected function rendersConditionalTemplate(): bool
+ {
+ return false;
+ }
+
public function getLegacyObjectType()
{
return strtolower($this->getType());
@@ -2674,16 +2691,16 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
/** @var DbObject $class */
$class = DbObjectTypeRegistry::classByType($type);
+ if ($keyColumn === null && is_array($class::create()->getKeyName())) {
+ return $class::loadAll($db, $query);
+ }
+
if ($keyColumn === null) {
if (method_exists($class, 'getKeyColumnName')) {
$keyColumn = $class::getKeyColumnName();
}
}
- if (is_array($class::create()->getKeyName())) {
- return $class::loadAll($db, $query);
- }
-
if (PrefetchCache::shouldBeUsed()
&& $query === null
&& $keyColumn === static::getKeyColumnName()
@@ -2890,11 +2907,14 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
}
}
}
+ if ($this->propertyIsInterval($k) && is_string($v) && ctype_digit($v)) {
+ $v = (int) $v;
+ }
// TODO: Do not ship null properties based on flag?
if (!$skipDefaults || $this->differsFromDefaultValue($k, $v)) {
if ($k === 'disabled' || $this->propertyIsBoolean($k)) {
- $props[$k] = $this->booleanForDbValue($v);
+ $props[$k] = DbDataFormatter::booleanForDbValue($v);
} else {
$props[$k] = $v;
}
@@ -3005,18 +3025,6 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
return (object) $props;
}
- protected function booleanForDbValue($value)
- {
- if ($value === 'y') {
- return true;
- }
- if ($value === 'n') {
- return false;
- }
-
- return $value; // let this fail elsewhere, if not null
- }
-
public function listImportNames()
{
if ($this->gotImports()) {
@@ -3161,7 +3169,7 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer
if ($this->differsFromDefaultValue($k, $v)) {
if ($k === 'disabled' || $this->propertyIsBoolean($k)) {
- $props[$k] = $this->booleanForDbValue($v);
+ $props[$k] = DbDataFormatter::booleanForDbValue($v);
} else {
$props[$k] = $v;
}
diff --git a/library/Director/Objects/IcingaObjectGroup.php b/library/Director/Objects/IcingaObjectGroup.php
index c0bec54..c076f52 100644
--- a/library/Director/Objects/IcingaObjectGroup.php
+++ b/library/Director/Objects/IcingaObjectGroup.php
@@ -2,9 +2,7 @@
namespace Icinga\Module\Director\Objects;
-use Icinga\Module\Director\Db;
use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
-use Icinga\Module\Director\Exception\DuplicateKeyException;
abstract class IcingaObjectGroup extends IcingaObject implements ExportInterface
{
@@ -24,53 +22,51 @@ abstract class IcingaObjectGroup extends IcingaObject implements ExportInterface
'assign_filter' => null,
];
+ protected $memberShipShouldBeRefreshed = false;
+
public function getUniqueIdentifier()
{
return $this->getObjectName();
}
- /**
- * @return object
- * @deprecated please use \Icinga\Module\Director\Data\Exporter
- * @throws \Icinga\Exception\NotFoundError
- */
- public function export()
+ protected function prefersGlobalZone()
{
- return $this->toPlainObject();
+ return true;
}
- /**
- * @param $plain
- * @param Db $db
- * @param bool $replace
- * @return IcingaObjectGroup
- * @throws DuplicateKeyException
- * @throws \Icinga\Exception\NotFoundError
- */
- public static function import($plain, Db $db, $replace = false)
+ protected function beforeStore()
{
- $properties = (array) $plain;
- $name = $properties['object_name'];
- $key = $name;
-
- if ($replace && static::exists($key, $db)) {
- $object = static::load($key, $db);
- } elseif (static::exists($key, $db)) {
- throw new DuplicateKeyException(
- 'Group "%s" already exists',
- $name
- );
+ parent::beforeStore();
+ if ($this->hasBeenLoadedFromDb()) {
+ if (!array_key_exists('assign_filter', $this->getModifiedProperties())) {
+ $this->memberShipShouldBeRefreshed = false;
+ return;
+ }
} else {
- $object = static::create([], $db);
+ if ($this->hasProperty('assign_filter') && $this->get('assign_filter') === null) {
+ $this->memberShipShouldBeRefreshed = false;
+ return;
+ }
}
- $object->setProperties($properties);
+ if ($this->hasProperty('assign_filter')) {
+ $this->memberShipShouldBeRefreshed = true;
+ }
+ }
- return $object;
+ protected function notifyResolvers()
+ {
+ if ($this->memberShipShouldBeRefreshed) {
+ $resolver = $this->getMemberShipResolver();
+ $resolver->addGroup($this);
+ $resolver->refreshDb();
+ }
+
+ return $this;
}
- protected function prefersGlobalZone()
+ protected function getMemberShipResolver()
{
- return true;
+ return null;
}
}
diff --git a/library/Director/Objects/IcingaObjectGroups.php b/library/Director/Objects/IcingaObjectGroups.php
index 8bef1b1..8683c77 100644
--- a/library/Director/Objects/IcingaObjectGroups.php
+++ b/library/Director/Objects/IcingaObjectGroups.php
@@ -183,6 +183,10 @@ class IcingaObjectGroups implements Iterator, Countable, IcingaConfigRenderer
return $this;
}
+ if (is_int($group)) {
+ $group = (string) $group;
+ }
+
/** @var IcingaObjectGroup $class */
$class = $this->getGroupClass();
@@ -224,7 +228,7 @@ class IcingaObjectGroups implements Iterator, Countable, IcingaConfigRenderer
} else {
throw new RuntimeException(
'Invalid group object: %s',
- var_export($group, 1)
+ var_export($group, true)
);
}
diff --git a/library/Director/Objects/IcingaObjectLegacyAssignments.php b/library/Director/Objects/IcingaObjectLegacyAssignments.php
index 6ab75c8..2db29b4 100644
--- a/library/Director/Objects/IcingaObjectLegacyAssignments.php
+++ b/library/Director/Objects/IcingaObjectLegacyAssignments.php
@@ -36,7 +36,7 @@ class IcingaObjectLegacyAssignments
$assigns = array();
$ignores = array();
foreach ($values as $type => $value) {
- if (strpos($value, '|') !== false || strpos($value, '&' !== false)) {
+ if (strpos($value, '|') !== false || strpos($value, '&') !== false) {
$value = '(' . $value . ')';
}
diff --git a/library/Director/Objects/IcingaObjectMultiRelations.php b/library/Director/Objects/IcingaObjectMultiRelations.php
index a1ec9a2..5931595 100644
--- a/library/Director/Objects/IcingaObjectMultiRelations.php
+++ b/library/Director/Objects/IcingaObjectMultiRelations.php
@@ -239,7 +239,7 @@ class IcingaObjectMultiRelations implements Iterator, Countable, IcingaConfigRen
} else {
throw new ProgrammingError(
'Invalid related object: %s',
- var_export($relation, 1)
+ var_export($relation, true)
);
}
diff --git a/library/Director/Objects/IcingaRelatedObject.php b/library/Director/Objects/IcingaRelatedObject.php
index d35bcb0..ca33688 100644
--- a/library/Director/Objects/IcingaRelatedObject.php
+++ b/library/Director/Objects/IcingaRelatedObject.php
@@ -180,7 +180,7 @@ class IcingaRelatedObject
} else {
throw new ProgrammingError(
'Related object can be name or object, got: %s',
- var_export($related, 1)
+ var_export($related, true)
);
}
diff --git a/library/Director/Objects/IcingaService.php b/library/Director/Objects/IcingaService.php
index 9479ef7..cbf9ca5 100644
--- a/library/Director/Objects/IcingaService.php
+++ b/library/Director/Objects/IcingaService.php
@@ -8,7 +8,6 @@ use Icinga\Module\Director\Data\PropertiesFilter;
use Icinga\Module\Director\Db;
use Icinga\Module\Director\Db\Cache\PrefetchCache;
use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
-use Icinga\Module\Director\Exception\DuplicateKeyException;
use Icinga\Module\Director\IcingaConfig\IcingaConfig;
use Icinga\Module\Director\IcingaConfig\IcingaConfigHelper as c;
use Icinga\Module\Director\IcingaConfig\IcingaLegacyConfigHelper as c1;
@@ -170,94 +169,6 @@ class IcingaService extends IcingaObject implements ExportInterface
}
/**
- * @return object
- * @deprecated please use \Icinga\Module\Director\Data\Exporter
- * @throws \Icinga\Exception\NotFoundError
- */
- public function export()
- {
- // TODO: ksort in toPlainObject?
- $props = (array) $this->toPlainObject();
- $props['fields'] = $this->loadFieldReferences();
- ksort($props);
-
- return (object) $props;
- }
-
- /**
- * @param $plain
- * @param Db $db
- * @param bool $replace
- * @return IcingaService
- * @throws DuplicateKeyException
- * @throws \Icinga\Exception\NotFoundError
- */
- public static function import($plain, Db $db, $replace = false)
- {
- $properties = (array) $plain;
- $name = $properties['object_name'];
- if ($properties['object_type'] !== 'template') {
- throw new InvalidArgumentException(sprintf(
- 'Can import only Templates, got "%s" for "%s"',
- $properties['object_type'],
- $name
- ));
- }
- $key = [
- 'object_type' => 'template',
- 'object_name' => $name
- ];
-
- if ($replace && static::exists($key, $db)) {
- $object = static::load($key, $db);
- } elseif (static::exists($key, $db)) {
- throw new DuplicateKeyException(
- 'Service Template "%s" already exists',
- $name
- );
- } else {
- $object = static::create([], $db);
- }
-
- // $object->newFields = $properties['fields'];
- unset($properties['fields']);
- $object->setProperties($properties);
-
- return $object;
- }
-
- /**
- * @deprecated please use \Icinga\Module\Director\Data\FieldReferenceLoader
- * @return array
- */
- protected function loadFieldReferences()
- {
- $db = $this->getDb();
-
- $res = $db->fetchAll(
- $db->select()->from([
- 'sf' => 'icinga_service_field'
- ], [
- 'sf.datafield_id',
- 'sf.is_required',
- 'sf.var_filter',
- ])->join(['df' => 'director_datafield'], 'df.id = sf.datafield_id', [])
- ->where('service_id = ?', $this->get('id'))
- ->order('varname ASC')
- );
-
- if (empty($res)) {
- return [];
- } else {
- foreach ($res as $field) {
- $field->datafield_id = (int) $field->datafield_id;
- }
-
- return $res;
- }
- }
-
- /**
* @param string $key
* @return $this
*/
@@ -473,6 +384,11 @@ class IcingaService extends IcingaObject implements ExportInterface
}
}
+ protected function rendersConditionalTemplate(): bool
+ {
+ return $this->getRenderingZone() === self::ALL_NON_GLOBAL_ZONES;
+ }
+
/**
* @return bool
*/
@@ -667,11 +583,22 @@ class IcingaService extends IcingaObject implements ExportInterface
protected function getDefaultZone(IcingaConfig $config = null)
{
+ // Hint: this isn't possible yet, as we're unable to render dependent apply rules to multiple zones as well
+ // if ($this->isTemplate()) {
+ // return self::ALL_NON_GLOBAL_ZONES;
+ // }
if ($this->get('host_id') === null) {
return parent::getDefaultZone();
} else {
- return $this->getRelatedObject('host', $this->get('host_id'))
+ $zone = $this->getRelatedObject('host', $this->get('host_id'))
->getRenderingZone($config);
+
+ // Hint: this avoids problems with host templates rendered to all non-global zones
+ if ($zone === self::ALL_NON_GLOBAL_ZONES) {
+ $zone = $this->connection->getDefaultGlobalZoneName();
+ }
+
+ return $zone;
}
}
diff --git a/library/Director/Objects/IcingaServiceGroup.php b/library/Director/Objects/IcingaServiceGroup.php
index ae43ff3..92fb1cb 100644
--- a/library/Director/Objects/IcingaServiceGroup.php
+++ b/library/Director/Objects/IcingaServiceGroup.php
@@ -31,12 +31,8 @@ class IcingaServiceGroup extends IcingaObjectGroup
return $this;
}
- protected function notifyResolvers()
+ protected function getMemberShipResolver()
{
- $resolver = $this->getServiceGroupMembershipResolver();
- $resolver->addGroup($this);
- $resolver->refreshDb();
-
- return $this;
+ return $this->getServiceGroupMembershipResolver();
}
}
diff --git a/library/Director/Objects/IcingaServiceSet.php b/library/Director/Objects/IcingaServiceSet.php
index 8217a59..252c52a 100644
--- a/library/Director/Objects/IcingaServiceSet.php
+++ b/library/Director/Objects/IcingaServiceSet.php
@@ -5,16 +5,14 @@ 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;
use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
use Icinga\Module\Director\Exception\DuplicateKeyException;
use Icinga\Module\Director\IcingaConfig\IcingaConfig;
use Icinga\Module\Director\Resolver\HostServiceBlacklist;
use InvalidArgumentException;
use Ramsey\Uuid\Uuid;
-use RuntimeException;
+use stdClass;
class IcingaServiceSet extends IcingaObject implements ExportInterface
{
@@ -46,6 +44,33 @@ class IcingaServiceSet extends IcingaObject implements ExportInterface
'host' => 'IcingaHost',
);
+ /** @var IcingaService[] Cached services */
+ protected $cachedServices = [];
+
+ /** @var IcingaService[]|null */
+ private $services;
+
+ /**
+ * Set the services to be cached
+ *
+ * @param $services IcingaService[]
+ * @return void
+ */
+ public function setCachedServices($services)
+ {
+ $this->cachedServices = $services;
+ }
+
+ /**
+ * Get the cached services
+ *
+ * @return IcingaService[]
+ */
+ public function getCachedServices()
+ {
+ return $this->cachedServices;
+ }
+
public function isDisabled()
{
return false;
@@ -79,6 +104,61 @@ class IcingaServiceSet extends IcingaObject implements ExportInterface
}
/**
+ * @param stdClass[] $services
+ * @return void
+ */
+ public function setServices(array $services)
+ {
+ $existing = $this->getServices();
+ $uuidMap = [];
+ foreach ($existing as $service) {
+ $uuidMap[$service->getUniqueId()->getBytes()] = $service;
+ }
+ $this->services = [];
+ foreach ($services as $service) {
+ if (isset($service->uuid)) {
+ $uuid = Uuid::fromString($service->uuid)->getBytes();
+ $current = $uuidMap[$uuid] ?? IcingaService::create([], $this->connection);
+ } else {
+ if (! is_object($service)) {
+ var_dump($service);
+ exit;
+ }
+ $current = $existing[$service->object_name] ?? IcingaService::create([], $this->connection);
+ }
+ $current->setProperties((array) $service);
+ $this->services[] = $current;
+ }
+ }
+
+ protected function storeRelatedServices()
+ {
+ if ($this->services === null) {
+ $cachedServices = $this->getCachedServices();
+ if ($cachedServices) {
+ $this->services = $cachedServices;
+ } else {
+ return;
+ }
+ }
+
+ $seen = [];
+ /** @var IcingaService $service */
+ foreach ($this->services as $service) {
+ $seen[$service->getUniqueId()->getBytes()] = true;
+ $service->set('service_set_id', $this->get('id'));
+ $service->store();
+ }
+
+ foreach ($this->fetchServices() as $service) {
+ if (!isset($seen[$service->getUniqueId()->getBytes()])) {
+ $service->delete();
+ }
+ }
+ }
+
+ /**
+ * @deprecated
* @return IcingaService[]
* @throws \Icinga\Exception\NotFoundError
*/
@@ -126,129 +206,9 @@ class IcingaServiceSet extends IcingaObject implements ExportInterface
return $this->getObjectName();
}
- /**
- * @return object
- * @deprecated please use \Icinga\Module\Director\Data\Exporter
- * @throws \Icinga\Exception\NotFoundError
- */
- public function export()
- {
- if ($this->get('host_id')) {
- $result = $this->exportSetOnHost();
- } else {
- $result = $this->exportTemplate();
- }
-
- unset($result->uuid);
- return $result;
- }
-
- protected function exportSetOnHost()
- {
- // TODO.
- throw new RuntimeException('Not yet');
- }
-
- /**
- * @return object
- * @deprecated
- * @throws \Icinga\Exception\NotFoundError
- */
- protected function exportTemplate()
- {
- $props = $this->getProperties();
- unset($props['id'], $props['host_id']);
- $props['services'] = [];
- foreach ($this->getServiceObjects() as $serviceObject) {
- $props['services'][$serviceObject->getObjectName()] = $serviceObject->export();
- }
- ksort($props);
-
- return (object) $props;
- }
-
- /**
- * @param $plain
- * @param Db $db
- * @param bool $replace
- * @return IcingaServiceSet
- * @throws DuplicateKeyException
- * @throws \Icinga\Exception\NotFoundError
- */
- public static function import($plain, Db $db, $replace = false)
- {
- $properties = (array) $plain;
- $name = $properties['object_name'];
- if (isset($properties['services'])) {
- $services = $properties['services'];
- unset($properties['services']);
- } else {
- $services = [];
- }
-
- if ($properties['object_type'] !== 'template') {
- throw new InvalidArgumentException(sprintf(
- 'Can import only Templates, got "%s" for "%s"',
- $properties['object_type'],
- $name
- ));
- }
- if ($replace && static::exists($name, $db)) {
- $object = static::load($name, $db);
- } elseif (static::exists($name, $db)) {
- throw new DuplicateKeyException(
- 'Service Set "%s" already exists',
- $name
- );
- } else {
- $object = static::create([], $db);
- }
-
- $object->setProperties($properties);
-
- // This is not how other imports work, but here we need an ID
- if (! $object->hasBeenLoadedFromDb()) {
- $object->store();
- }
-
- $setId = $object->get('id');
- $sQuery = $db->getDbAdapter()->select()->from(
- ['s' => 'icinga_service'],
- 's.*'
- )->where('service_set_id = ?', $setId);
- $existingServices = IcingaService::loadAll($db, $sQuery, 'object_name');
- $serviceNames = [];
- foreach ($services as $service) {
- if (isset($service->fields)) {
- unset($service->fields);
- }
- $name = $service->object_name;
- $serviceNames[] = $name;
- if (isset($existingServices[$name])) {
- $existing = $existingServices[$name];
- $existing->setProperties((array) $service);
- $existing->set('service_set_id', $setId);
- if ($existing->hasBeenModified()) {
- $existing->store();
- }
- } else {
- $new = IcingaService::create((array) $service, $db);
- $new->set('service_set_id', $setId);
- $new->store();
- }
- }
-
- foreach ($existingServices as $existing) {
- if (!in_array($existing->getObjectName(), $serviceNames)) {
- $existing->delete();
- }
- }
-
- return $object;
- }
-
public function beforeDelete()
{
+ $this->setCachedServices($this->getServices());
// check if this is a template, or directly assigned to a host
if ($this->get('host_id') === null) {
// find all host sets and delete them
@@ -295,8 +255,8 @@ class IcingaServiceSet extends IcingaObject implements ExportInterface
*/
public function renderToConfig(IcingaConfig $config)
{
- // always print the header, so you have minimal info present
- $file = $this->getConfigFileWithHeader($config);
+ $files = [];
+ $zone = $this->getRenderingZone($config) ;
if ($this->get('assign_filter') === null && $this->isTemplate()) {
return;
@@ -334,7 +294,15 @@ class IcingaServiceSet extends IcingaObject implements ExportInterface
}
$this->copyVarsToService($service);
- $file->addObject($service);
+ foreach ($service->getRenderingZones($config) as $serviceZone) {
+ $file = $this->getConfigFileWithHeader($config, $serviceZone, $files);
+ $file->addObject($service);
+ }
+ }
+
+ if (empty($files)) {
+ // always print the header, so you have minimal info present
+ $this->getConfigFileWithHeader($config, $zone, $files);
}
}
@@ -355,14 +323,18 @@ class IcingaServiceSet extends IcingaObject implements ExportInterface
return $lookup->getBlacklistedHostnamesForService($service);
}
- protected function getConfigFileWithHeader(IcingaConfig $config)
+ protected function getConfigFileWithHeader(IcingaConfig $config, $zone, &$files = [])
{
- $file = $config->configFile(
- 'zones.d/' . $this->getRenderingZone($config) . '/servicesets'
- );
+ if (!isset($files[$zone])) {
+ $file = $config->configFile(
+ 'zones.d/' . $zone . '/servicesets'
+ );
- $file->addContent($this->getConfigHeaderComment($config));
- return $file;
+ $file->addContent($this->getConfigHeaderComment($config));
+ $files[$zone] = $file;
+ }
+
+ return $files[$zone];
}
protected function getConfigHeaderComment(IcingaConfig $config)
@@ -372,13 +344,13 @@ class IcingaServiceSet extends IcingaObject implements ExportInterface
if ($config->isLegacy()) {
if ($assign !== null) {
- return "## applied Service Set '${name}'\n\n";
+ return "## applied Service Set '{$name}'\n\n";
} else {
- return "## Service Set '${name}' on this host\n\n";
+ return "## Service Set '{$name}' on this host\n\n";
}
} else {
$comment = [
- "Service Set: ${name}",
+ "Service Set: {$name}",
];
if (($host = $this->get('host')) !== null) {
@@ -505,7 +477,23 @@ class IcingaServiceSet extends IcingaObject implements ExportInterface
/**
* @return IcingaService[]
*/
- public function fetchServices()
+ public function getServices(): array
+ {
+ if ($this->services !== null) {
+ return $this->services;
+ }
+
+ if ($this->hasBeenLoadedFromDb()) {
+ return $this->fetchServices();
+ }
+
+ return [];
+ }
+
+ /**
+ * @return IcingaService[]
+ */
+ public function fetchServices(): array
{
if ($store = self::$dbObjectStore) {
$uuid = $store->getBranch()->getUuid();
@@ -569,6 +557,24 @@ class IcingaServiceSet extends IcingaObject implements ExportInterface
}
}
+ public function onStore()
+ {
+ $this->storeRelatedServices();
+ }
+
+ public function hasBeenModified()
+ {
+ if ($this->services !== null) {
+ foreach ($this->services as $service) {
+ if ($service->hasBeenModified()) {
+ return true;
+ }
+ }
+ }
+
+ return parent::hasBeenModified();
+ }
+
public function toSingleIcingaConfig()
{
$config = parent::toSingleIcingaConfig();
diff --git a/library/Director/Objects/IcingaTemplateChoice.php b/library/Director/Objects/IcingaTemplateChoice.php
index 1a1be90..a2be07a 100644
--- a/library/Director/Objects/IcingaTemplateChoice.php
+++ b/library/Director/Objects/IcingaTemplateChoice.php
@@ -3,9 +3,7 @@
namespace Icinga\Module\Director\Objects;
use Icinga\Exception\ProgrammingError;
-use Icinga\Module\Director\Db;
use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
-use Icinga\Module\Director\Exception\DuplicateKeyException;
use Icinga\Module\Director\Web\Form\QuickForm;
class IcingaTemplateChoice extends IcingaObject implements ExportInterface
@@ -36,63 +34,6 @@ class IcingaTemplateChoice extends IcingaObject implements ExportInterface
return $this->getObjectName();
}
- /**
- * @param $plain
- * @param Db $db
- * @param bool $replace
- * @return IcingaTemplateChoice
- * @throws DuplicateKeyException
- * @throws \Icinga\Exception\NotFoundError
- */
- public static function import($plain, Db $db, $replace = false)
- {
- $properties = (array) $plain;
- if (isset($properties['originalId'])) {
- unset($properties['originalId']);
- }
- $name = $properties['object_name'];
- $key = $name;
-
- if ($replace && static::exists($key, $db)) {
- $object = static::load($key, $db);
- } elseif (static::exists($key, $db)) {
- throw new DuplicateKeyException(
- 'Template Choice "%s" already exists',
- $name
- );
- } else {
- $object = static::create([], $db);
- }
-
- $object->setProperties($properties);
-
- return $object;
- }
-
- /**
- * @deprecated please use \Icinga\Module\Director\Data\Exporter
- * @return array|object|\stdClass
- */
- public function export()
- {
- $plain = (object) $this->getProperties();
- $plain->originalId = $plain->id;
- unset($plain->id);
- $requiredId = $plain->required_template_id;
- unset($plain->required_template_id);
- if ($requiredId) {
- $db = $this->getDb();
- $query = $db->select()
- ->from(['o' => $this->getObjectTableName()], 'o.object_name')->where("o.object_type = 'template'")
- ->where('o.id = ?', $this->get('id'));
- $plain->required_template = $db->fetchOne($query);
- }
-
- $plain->members = array_values($this->getMembers());
-
- return $plain;
- }
-
public function isMainChoice()
{
return $this->hasBeenLoadedFromDb()
@@ -155,7 +96,7 @@ class IcingaTemplateChoice extends IcingaObject implements ExportInterface
public function hasBeenModified()
{
- if ($this->newChoices !== null && $this->choices !== $this->newChoices) {
+ if ($this->newChoices !== null && ($this->choices ?? $this->fetchChoices()) !== $this->newChoices) {
return true;
}
@@ -284,7 +225,7 @@ class IcingaTemplateChoice extends IcingaObject implements ExportInterface
} else {
throw new ProgrammingError(
'Expected array or null for allowed_roles, got %s',
- var_export($roles, 1)
+ var_export($roles, true)
);
}
}
diff --git a/library/Director/Objects/IcingaTemplateResolver.php b/library/Director/Objects/IcingaTemplateResolver.php
index 61122a0..7f14967 100644
--- a/library/Director/Objects/IcingaTemplateResolver.php
+++ b/library/Director/Objects/IcingaTemplateResolver.php
@@ -45,7 +45,6 @@ class IcingaTemplateResolver
{
$this->object = $object;
$this->type = $object->getShortTableName();
- $this->table = $object->getTableName();
$this->connection = $object->getConnection();
$this->db = $this->connection->getDbAdapter();
diff --git a/library/Director/Objects/IcingaTimePeriod.php b/library/Director/Objects/IcingaTimePeriod.php
index 1232366..081cdd9 100644
--- a/library/Director/Objects/IcingaTimePeriod.php
+++ b/library/Director/Objects/IcingaTimePeriod.php
@@ -2,9 +2,7 @@
namespace Icinga\Module\Director\Objects;
-use Icinga\Module\Director\Db;
use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
-use Icinga\Module\Director\Exception\DuplicateKeyException;
class IcingaTimePeriod extends IcingaObject implements ExportInterface
{
@@ -56,48 +54,6 @@ class IcingaTimePeriod extends IcingaObject implements ExportInterface
}
/**
- * @deprecated please use \Icinga\Module\Director\Data\Exporter
- * @return object
- * @throws \Icinga\Exception\NotFoundError
- */
- public function export()
- {
- $props = (array) $this->toPlainObject();
- ksort($props);
-
- return (object) $props;
- }
-
- /**
- * @param $plain
- * @param Db $db
- * @param bool $replace
- * @return static
- * @throws DuplicateKeyException
- * @throws \Icinga\Exception\NotFoundError
- */
- public static function import($plain, Db $db, $replace = false)
- {
- $properties = (array) $plain;
- $name = $properties['object_name'];
- $key = $name;
-
- if ($replace && static::exists($key, $db)) {
- $object = static::load($key, $db);
- } elseif (static::exists($key, $db)) {
- throw new DuplicateKeyException(
- 'Time Period "%s" already exists',
- $name
- );
- } else {
- $object = static::create([], $db);
- }
- $object->setProperties($properties);
-
- return $object;
- }
-
- /**
* Render update property
*
* Avoid complaints for method names with underscore:
diff --git a/library/Director/Objects/IcingaUser.php b/library/Director/Objects/IcingaUser.php
index 394e849..4100245 100644
--- a/library/Director/Objects/IcingaUser.php
+++ b/library/Director/Objects/IcingaUser.php
@@ -48,43 +48,6 @@ class IcingaUser extends IcingaObject implements ExportInterface
'zone' => 'IcingaZone',
);
- public function export()
- {
- return ImportExportHelper::simpleExport($this);
- }
-
- /**
- * @param $plain
- * @param Db $db
- * @param bool $replace
- * @return IcingaUser
- * @throws DuplicateKeyException
- * @throws \Icinga\Exception\NotFoundError
- */
- public static function import($plain, Db $db, $replace = false)
- {
- $properties = (array) $plain;
- $key = $properties['object_name'];
-
- if ($replace && static::exists($key, $db)) {
- $object = static::load($key, $db);
- } elseif (static::exists($key, $db)) {
- throw new DuplicateKeyException(
- 'Cannot import, %s "%s" already exists',
- static::create([])->getShortTableName(),
- $key
- );
- } else {
- $object = static::create([], $db);
- }
-
- // $object->newFields = $properties['fields'];
- unset($properties['fields']);
- $object->setProperties($properties);
-
- return $object;
- }
-
public function getUniqueIdentifier()
{
return $this->getObjectName();
diff --git a/library/Director/Objects/ImportExportHelper.php b/library/Director/Objects/ImportExportHelper.php
deleted file mode 100644
index 98d34c6..0000000
--- a/library/Director/Objects/ImportExportHelper.php
+++ /dev/null
@@ -1,68 +0,0 @@
-<?php
-
-namespace Icinga\Module\Director\Objects;
-
-use Icinga\Module\Director\Db;
-
-/**
- * Helper class, allows to reduce duplicate code. Might be moved elsewhere
- * afterwards
- */
-class ImportExportHelper
-{
- /**
- * Does not support every type out of the box
- *
- * @param IcingaObject $object
- * @return object
- * @throws \Icinga\Exception\NotFoundError
- */
- public static function simpleExport(IcingaObject $object)
- {
- $props = (array) $object->toPlainObject();
- $props['fields'] = static::fetchFields($object);
- ksort($props); // TODO: ksort in toPlainObject?
-
- return (object) $props;
- }
-
- public static function fetchFields(IcingaObject $object)
- {
- return static::loadFieldReferences(
- $object->getConnection(),
- $object->getShortTableName(),
- $object->get('id')
- );
- }
-
- /**
- * @param Db $connection
- * @param string $type Warning: this will not be validated.
- * @param int $id
- * @return array
- */
- public static function loadFieldReferences(Db $connection, $type, $id)
- {
- $db = $connection->getDbAdapter();
- $res = $db->fetchAll(
- $db->select()->from([
- 'f' => "icinga_${type}_field"
- ], [
- 'f.datafield_id',
- 'f.is_required',
- 'f.var_filter',
- ])->join(['df' => 'director_datafield'], 'df.id = f.datafield_id', [])
- ->where("${type}_id = ?", $id)
- ->order('varname ASC')
- );
-
- if (empty($res)) {
- return [];
- }
-
- foreach ($res as $field) {
- $field->datafield_id = (int) $field->datafield_id;
- }
- return $res;
- }
-}
diff --git a/library/Director/Objects/ImportRowModifier.php b/library/Director/Objects/ImportRowModifier.php
index 76982c2..afebbad 100644
--- a/library/Director/Objects/ImportRowModifier.php
+++ b/library/Director/Objects/ImportRowModifier.php
@@ -18,13 +18,14 @@ class ImportRowModifier extends DbObjectWithSettings implements InstantiatedViaH
protected $autoincKeyName = 'id';
protected $defaultProperties = [
- 'id' => null,
- 'source_id' => null,
- 'property_name' => null,
- 'provider_class' => null,
- 'target_property' => null,
- 'priority' => null,
- 'description' => null,
+ 'id' => null,
+ 'source_id' => null,
+ 'property_name' => null,
+ 'provider_class' => null,
+ 'target_property' => null,
+ 'filter_expression' => null,
+ 'priority' => null,
+ 'description' => null,
];
protected $settingsTable = 'import_row_modifier_setting';
diff --git a/library/Director/Objects/ImportSource.php b/library/Director/Objects/ImportSource.php
index fd892ef..7477472 100644
--- a/library/Director/Objects/ImportSource.php
+++ b/library/Director/Objects/ImportSource.php
@@ -3,12 +3,14 @@
namespace Icinga\Module\Director\Objects;
use Icinga\Application\Benchmark;
+use Icinga\Data\Filter\Filter;
use Icinga\Exception\NotFoundError;
use Icinga\Module\Director\Application\MemoryLimit;
use Icinga\Module\Director\Data\Db\DbObjectWithSettings;
use Icinga\Module\Director\Db;
use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
use Icinga\Module\Director\Exception\DuplicateKeyException;
+use Icinga\Module\Director\Filter\FilterEnrichment;
use Icinga\Module\Director\Hook\PropertyModifierHook;
use Icinga\Module\Director\Import\Import;
use Icinga\Module\Director\Import\SyncUtils;
@@ -52,68 +54,6 @@ class ImportSource extends DbObjectWithSettings implements ExportInterface
private $newRowModifiers;
- /**
- * @deprecated please use \Icinga\Module\Director\Data\FieldReferenceLoader
- * @return \stdClass
- */
- public function export()
- {
- $plain = $this->getProperties();
- $plain['originalId'] = $plain['id'];
- unset($plain['id']);
-
- foreach ($this->stateProperties as $key) {
- unset($plain[$key]);
- }
-
- $plain['settings'] = (object) $this->getSettings();
- $plain['modifiers'] = $this->exportRowModifiers();
- ksort($plain);
-
- return (object) $plain;
- }
-
- /**
- * @param $plain
- * @param Db $db
- * @param bool $replace
- * @return ImportSource
- * @throws DuplicateKeyException
- * @throws NotFoundError
- */
- public static function import($plain, Db $db, $replace = false)
- {
- $properties = (array) $plain;
- if (isset($properties['originalId'])) {
- $id = $properties['originalId'];
- unset($properties['originalId']);
- } else {
- $id = null;
- }
- $name = $properties['source_name'];
-
- if ($replace && $id && static::existsWithNameAndId($name, $id, $db)) {
- $object = static::loadWithAutoIncId($id, $db);
- } elseif ($replace && static::exists($name, $db)) {
- $object = static::load($name, $db);
- } elseif (static::existsWithName($name, $db)) {
- throw new DuplicateKeyException(
- 'Import Source %s already exists',
- $name
- );
- } else {
- $object = static::create([], $db);
- }
-
- if (! isset($properties['modifiers'])) {
- $properties['modifiers'] = [];
- }
-
- $object->setProperties($properties);
-
- return $object;
- }
-
public function setModifiers(array $modifiers)
{
if ($this->loadedRowModifiers === null && $this->hasBeenLoadedFromDb()) {
@@ -314,10 +254,14 @@ class ImportSource extends DbObjectWithSettings implements ExportInterface
foreach ($modifiers as $modPair) {
/** @var PropertyModifierHook $modifier */
- list($property, $modifier) = $modPair;
+ /** @var ?Filter $filter */
+ list($property, $modifier, $filter) = $modPair;
$rejected = [];
$newRows = [];
foreach ($data as $key => $row) {
+ if ($filter && ! $filter->matches($row)) {
+ continue;
+ }
$this->applyPropertyModifierToRow($modifier, $property, $row);
if ($modifier->rejectsRow()) {
$rejected[] = $key;
@@ -434,7 +378,12 @@ class ImportSource extends DbObjectWithSettings implements ExportInterface
{
$mods = [];
foreach ($this->fetchRowModifiers() as $mod) {
- $mods[] = [$mod->get('property_name'), $mod->getInstance()];
+ if ($filterExpression = $mod->get('filter_expression')) {
+ $filter = FilterEnrichment::enrichFilter(Filter::fromQueryString($filterExpression));
+ } else {
+ $filter = null;
+ }
+ $mods[] = [$mod->get('property_name'), $mod->getInstance(), $filter];
}
return $mods;
@@ -530,7 +479,7 @@ class ImportSource extends DbObjectWithSettings implements ExportInterface
protected function raiseLimits()
{
MemoryLimit::raiseTo('1024M');
- ini_set('max_execution_time', 0);
+ ini_set('max_execution_time', '0');
return $this;
}
diff --git a/library/Director/Objects/ObjectApplyMatches.php b/library/Director/Objects/ObjectApplyMatches.php
index 018c880..d749d6a 100644
--- a/library/Director/Objects/ObjectApplyMatches.php
+++ b/library/Director/Objects/ObjectApplyMatches.php
@@ -172,7 +172,7 @@ abstract class ObjectApplyMatches
$col = $filter->getColumn();
$type = static::$type;
- if ($type && substr($col, 0, strlen($type) + 1) === "${type}.") {
+ if ($type && substr($col, 0, strlen($type) + 1) === "{$type}.") {
$filter->setColumn($col = substr($col, strlen($type) + 1));
}
diff --git a/library/Director/Objects/SyncRule.php b/library/Director/Objects/SyncRule.php
index 89f7fd1..270a882 100644
--- a/library/Director/Objects/SyncRule.php
+++ b/library/Director/Objects/SyncRule.php
@@ -43,6 +43,10 @@ class SyncRule extends DbObject implements ExportInterface
'last_attempt',
];
+ protected $booleans = [
+ 'purge_existing' => 'purge_existing',
+ ];
+
private $sync;
private $purgeStrategy;
@@ -60,8 +64,6 @@ class SyncRule extends DbObject implements ExportInterface
private $newSyncProperties;
- private $originalId;
-
public function listInvolvedSourceIds()
{
if (! $this->hasBeenLoadedFromDb()) {
@@ -257,61 +259,13 @@ class SyncRule extends DbObject implements ExportInterface
}
/**
- * @deprecated please use \Icinga\Module\Director\Data\Exporter
- * @return object
- */
- public function export()
- {
- $plain = $this->getProperties();
- $plain['originalId'] = $plain['id'];
- unset($plain['id']);
-
- foreach ($this->stateProperties as $key) {
- unset($plain[$key]);
- }
- $plain['properties'] = $this->exportSyncProperties();
- ksort($plain);
-
- return (object) $plain;
- }
-
- /**
- * @param object $plain
- * @param Db $db
- * @param bool $replace
- * @return static
- * @throws DuplicateKeyException
- * @throws \Icinga\Exception\NotFoundError
+ * Flat object has 'properties', but setProperties() is not available in DbObject
+ *
+ * @return void
*/
- public static function import($plain, Db $db, $replace = false)
+ public function setSyncProperties(?array $value)
{
- $properties = (array) $plain;
- if (isset($properties['originalId'])) {
- $id = $properties['originalId'];
- unset($properties['originalId']);
- } else {
- $id = null;
- }
- $name = $properties['rule_name'];
-
- if ($replace && $id && static::existsWithNameAndId($name, $id, $db)) {
- $object = static::loadWithAutoIncId($id, $db);
- } elseif ($replace && static::exists($name, $db)) {
- $object = static::load($name, $db);
- } elseif (static::existsWithName($name, $db)) {
- throw new DuplicateKeyException(
- 'Sync Rule %s already exists',
- $name
- );
- } else {
- $object = static::create([], $db);
- }
-
- $object->newSyncProperties = $properties['properties'];
- unset($properties['properties']);
- $object->setProperties($properties);
-
- return $object;
+ $this->newSyncProperties = $value;
}
public function getUniqueIdentifier()
@@ -329,12 +283,6 @@ class SyncRule extends DbObject implements ExportInterface
$connection = $this->getConnection();
$db = $connection->getDbAdapter();
$myId = $this->get('id');
- if ($this->originalId === null) {
- $originalId = $myId;
- } else {
- $originalId = $this->originalId;
- $this->originalId = null;
- }
if ($this->hasBeenLoadedFromDb()) {
$db->delete(
'sync_property',
diff --git a/library/Director/PlainObjectRenderer.php b/library/Director/PlainObjectRenderer.php
index 4dadf4f..e613f1f 100644
--- a/library/Director/PlainObjectRenderer.php
+++ b/library/Director/PlainObjectRenderer.php
@@ -105,7 +105,7 @@ class PlainObjectRenderer
} elseif (is_string($object)) {
return self::renderString($object);
} else {
- return '(UNKNOWN TYPE) ' . var_export($object, 1);
+ return '(UNKNOWN TYPE) ' . var_export($object, true);
}
}
diff --git a/library/Director/PropertyModifier/PropertyModifierArrayFilter.php b/library/Director/PropertyModifier/PropertyModifierArrayFilter.php
index 0b52987..a8fcbf7 100644
--- a/library/Director/PropertyModifier/PropertyModifierArrayFilter.php
+++ b/library/Director/PropertyModifier/PropertyModifierArrayFilter.php
@@ -116,7 +116,7 @@ class PropertyModifierArrayFilter extends PropertyModifierHook
default:
throw new ConfigurationError(
'%s is not a valid value for an ArrayFilter filter_method',
- var_export($method, 1)
+ var_export($method, true)
);
}
diff --git a/library/Director/PropertyModifier/PropertyModifierExtractFromDN.php b/library/Director/PropertyModifier/PropertyModifierExtractFromDN.php
index c79c5b2..6b0651d 100644
--- a/library/Director/PropertyModifier/PropertyModifierExtractFromDN.php
+++ b/library/Director/PropertyModifier/PropertyModifierExtractFromDN.php
@@ -74,7 +74,7 @@ class PropertyModifierExtractFromDN extends PropertyModifierHook
default:
throw new InvalidPropertyException(
'DN part extraction failed for %s',
- var_export($value, 1)
+ var_export($value, true)
);
}
}
diff --git a/library/Director/PropertyModifier/PropertyModifierGetHostByName.php b/library/Director/PropertyModifier/PropertyModifierGetHostByName.php
index 36884e8..d7921de 100644
--- a/library/Director/PropertyModifier/PropertyModifierGetHostByName.php
+++ b/library/Director/PropertyModifier/PropertyModifierGetHostByName.php
@@ -34,7 +34,8 @@ class PropertyModifierGetHostByName extends PropertyModifierHook
}
$host = gethostbyname($value);
- if (strlen(@inet_pton($host)) !== 4) {
+ $inAddr = inet_pton($host);
+ if (! $inAddr || strlen($inAddr) !== 4) {
switch ($this->getSetting('on_failure')) {
case 'null':
return null;
diff --git a/library/Director/PropertyModifier/PropertyModifierJsonDecode.php b/library/Director/PropertyModifier/PropertyModifierJsonDecode.php
index f6b9af8..4ab119a 100644
--- a/library/Director/PropertyModifier/PropertyModifierJsonDecode.php
+++ b/library/Director/PropertyModifier/PropertyModifierJsonDecode.php
@@ -2,8 +2,9 @@
namespace Icinga\Module\Director\PropertyModifier;
+use Exception;
+use gipfl\Json\JsonString;
use Icinga\Exception\InvalidPropertyException;
-use Icinga\Module\Director\Exception\JsonException;
use Icinga\Module\Director\Hook\PropertyModifierHook;
use Icinga\Module\Director\Web\Form\QuickForm;
@@ -38,16 +39,22 @@ class PropertyModifierJsonDecode extends PropertyModifierHook
/**
* @param $value
* @return mixed|null
- * @throws InvalidPropertyException
+ * @throws InvalidPropertyException|\gipfl\Json\JsonDecodeException
*/
public function transform($value)
{
if (null === $value) {
- return $value;
+ return null;
}
-
- $decoded = @json_decode($value);
- if ($decoded === null && JSON_ERROR_NONE !== json_last_error()) {
+ try {
+ if (is_string($value)) {
+ $decoded = JsonString::decode($value);
+ } else {
+ throw new InvalidPropertyException(
+ 'JSON decode expects a string, got ' . gettype($value)
+ );
+ }
+ } catch (Exception $e) {
switch ($this->getSetting('on_failure')) {
case 'null':
return null;
@@ -55,11 +62,7 @@ class PropertyModifierJsonDecode extends PropertyModifierHook
return $value;
case 'fail':
default:
- throw new InvalidPropertyException(
- 'JSON decoding failed with "%s" for %s',
- JsonException::getJsonErrorMessage(json_last_error()),
- substr($value, 0, 128)
- );
+ throw $e;
}
}
diff --git a/library/Director/PropertyModifier/PropertyModifierMap.php b/library/Director/PropertyModifier/PropertyModifierMap.php
index a6cb422..e411c54 100644
--- a/library/Director/PropertyModifier/PropertyModifierMap.php
+++ b/library/Director/PropertyModifier/PropertyModifierMap.php
@@ -4,6 +4,7 @@ namespace Icinga\Module\Director\PropertyModifier;
use Icinga\Exception\InvalidPropertyException;
use Icinga\Module\Director\Hook\PropertyModifierHook;
+use Icinga\Module\Director\Import\SyncUtils;
use Icinga\Module\Director\Web\Form\QuickForm;
class PropertyModifierMap extends PropertyModifierHook
@@ -30,15 +31,37 @@ class PropertyModifierMap extends PropertyModifierHook
. ' or interrupt the import process'
),
'multiOptions' => $form->optionalEnum(array(
- 'null' => $form->translate('Set null'),
- 'keep' => $form->translate('Return lookup key unmodified'),
- 'fail' => $form->translate('Let the import fail'),
+ 'null' => $form->translate('Set null'),
+ 'keep' => $form->translate('Return lookup key unmodified'),
+ 'custom' => $form->translate('Return custom default value'),
+ 'fail' => $form->translate('Let the import fail'),
)),
+ 'class' => 'autosubmit',
));
+ $method = $form->getSetting('on_missing');
+ if ($method == 'custom') {
+ $form->addElement('text', 'custom_value', array(
+ 'label' => $form->translate('Default value'),
+ 'required' => true,
+ 'description' => $form->translate(
+ 'This value will be evaluated, and variables like ${some_column}'
+ . ' will be filled accordingly. A typical use-case is generating'
+ . ' unique service identifiers via ${host}!${service} in case your'
+ . ' data source doesn\'t allow you to ship such. The chosen "property"'
+ . ' has no effect here and will be ignored.'
+ )
+ ));
+ }
+
// TODO: ignore case
}
+ public function requiresRow()
+ {
+ return true;
+ }
+
public function transform($value)
{
$this->loadCache();
@@ -53,6 +76,9 @@ class PropertyModifierMap extends PropertyModifierHook
case 'keep':
return $value;
+ case 'custom':
+ return SyncUtils::fillVariables($this->getSetting('custom_value'), $this->getRow());
+
case 'fail':
default:
throw new InvalidPropertyException(
diff --git a/library/Director/PropertyModifier/PropertyModifierRegexReplace.php b/library/Director/PropertyModifier/PropertyModifierRegexReplace.php
index 59cb245..eeae69e 100644
--- a/library/Director/PropertyModifier/PropertyModifierRegexReplace.php
+++ b/library/Director/PropertyModifier/PropertyModifierRegexReplace.php
@@ -9,25 +9,36 @@ class PropertyModifierRegexReplace extends PropertyModifierHook
{
public static function addSettingsFormFields(QuickForm $form)
{
- $form->addElement('text', 'pattern', array(
- 'label' => 'Regex pattern',
+ $form->addElement('text', 'pattern', [
+ 'label' => $form->translate('Regex pattern'),
'description' => $form->translate(
'The pattern you want to search for. This can be a regular expression like /^www\d+\./'
),
'required' => true,
- ));
+ ]);
- $form->addElement('text', 'replacement', array(
- 'label' => 'Replacement',
+ $form->addElement('text', 'replacement', [
+ 'label' => $form->translate('Replacement'),
'description' => $form->translate(
- 'The string that should be used as a preplacement'
+ 'The string that should be used as a replacement'
),
- ));
+ ]);
+ $form->addElement('select', 'when_not_matched', [
+ 'label' => $form->translate('When not matched'),
+ 'description' => $form->translate(
+ "What should happen, if the given pattern doesn't match"
+ ),
+ 'value' => 'keep',
+ 'multiOptions' => [
+ 'keep' => $form->translate('Keep the given string'),
+ 'set_null' => $form->translate('Set the value to NULL')
+ ]
+ ]);
}
public function getName()
{
- return 'Regular expression based replacement';
+ return mt('director', 'Regular expression based replacement');
}
public function transform($value)
@@ -36,10 +47,13 @@ class PropertyModifierRegexReplace extends PropertyModifierHook
return null;
}
- return preg_replace(
- $this->getSetting('pattern'),
- $this->getSetting('replacement'),
- $value
- );
+ $result = preg_replace($this->getSetting('pattern'), $this->getSetting('replacement'), $value);
+ if ($result === $value && $this->getSetting('when_not_matched', 'keep') === 'set_null') {
+ if (!preg_match($this->getSetting('pattern'), $value)) {
+ return null;
+ }
+ }
+
+ return $result;
}
}
diff --git a/library/Director/PropertyModifier/PropertyModifierRejectOrSelect.php b/library/Director/PropertyModifier/PropertyModifierRejectOrSelect.php
index 1485d5d..04c49c5 100644
--- a/library/Director/PropertyModifier/PropertyModifierRejectOrSelect.php
+++ b/library/Director/PropertyModifier/PropertyModifierRejectOrSelect.php
@@ -128,7 +128,7 @@ class PropertyModifierRejectOrSelect extends PropertyModifierHook
default:
throw new ConfigurationError(
'%s is not a valid value for an ArrayFilter filter_method',
- var_export($method, 1)
+ var_export($method, true)
);
}
diff --git a/library/Director/PropertyModifier/PropertyModifierSplit.php b/library/Director/PropertyModifier/PropertyModifierSplit.php
index 4a6fef6..3eec77d 100644
--- a/library/Director/PropertyModifier/PropertyModifierSplit.php
+++ b/library/Director/PropertyModifier/PropertyModifierSplit.php
@@ -33,7 +33,7 @@ class PropertyModifierSplit extends PropertyModifierHook
public function transform($value)
{
- if (! strlen(trim($value))) {
+ if ($value === null || ! strlen(trim($value))) {
if ($this->getSetting('when_empty', 'empty_array') === 'empty_array') {
return array();
} else {
diff --git a/library/Director/ProvidedHook/IcingaDbCubeLinks.php b/library/Director/ProvidedHook/IcingaDbCubeLinks.php
index 234f61f..f3fe402 100644
--- a/library/Director/ProvidedHook/IcingaDbCubeLinks.php
+++ b/library/Director/ProvidedHook/IcingaDbCubeLinks.php
@@ -7,6 +7,7 @@ use Icinga\Exception\ProgrammingError;
use Icinga\Module\Cube\Hook\IcingaDbActionsHook;
use Icinga\Module\Cube\IcingaDb\IcingaDbCube;
use Icinga\Module\Cube\IcingaDb\IcingaDbHostStatusCube;
+use ipl\Stdlib\Filter\Condition;
class IcingaDbCubeLinks extends IcingaDbActionsHook
{
@@ -25,17 +26,19 @@ class IcingaDbCubeLinks extends IcingaDbActionsHook
if ($filterChain->count() === 1) {
$url = 'director/host/edit?';
- $params = ['name' => $filterChain->getIterator()->current()->getValue()];
+ /** @var Condition $rule */
+ $rule = $filterChain->getIterator()->current();
+ /** @var string $name */
+ $name = $rule->getValue();
+ $params = ['name' => $name];
$title = t('Modify a host');
- $description = sprintf(
- t('This allows you to modify properties for "%s"'),
- $filterChain->getIterator()->current()->getValue()
- );
+ $description = sprintf(t('This allows you to modify properties for "%s"'), $name);
} else {
$params = null;
$urlFilter = Filter::matchAny();
+ /** @var Condition $filter */
foreach ($filterChain as $filter) {
$urlFilter->addFilter(
Filter::matchAny(
diff --git a/library/Director/ProvidedHook/Icingadb/HostActions.php b/library/Director/ProvidedHook/Icingadb/HostActions.php
new file mode 100644
index 0000000..d7332ea
--- /dev/null
+++ b/library/Director/ProvidedHook/Icingadb/HostActions.php
@@ -0,0 +1,78 @@
+<?php
+
+namespace Icinga\Module\Director\ProvidedHook\Icingadb;
+
+use Exception;
+use Icinga\Application\Config;
+use Icinga\Module\Director\Auth\Permission;
+use Icinga\Module\Director\Db;
+use Icinga\Module\Director\Integration\Icingadb\IcingadbBackend;
+use Icinga\Module\Director\Objects\IcingaHost;
+use Icinga\Module\Director\Util;
+use Icinga\Module\Icingadb\Hook\HostActionsHook;
+use Icinga\Module\Icingadb\Model\Host;
+use ipl\Web\Url;
+use ipl\Web\Widget\Link;
+
+class HostActions extends HostActionsHook
+{
+ public function getActionsForObject(Host $host): array
+ {
+ try {
+ return $this->getThem($host);
+ } catch (Exception $e) {
+ return [];
+ }
+ }
+
+ protected function getThem(Host $host): array
+ {
+ $actions = [];
+ $db = $this->db();
+ if (! $db) {
+ return $actions;
+ }
+ $hostname = $host->name;
+ if (Util::hasPermission(Permission::INSPECT)) {
+ $actions[] = new Link(
+ mt('director', 'Inspect'),
+ Url::fromPath(
+ 'director/inspect/object',
+ ['type' => 'host', 'plural' => 'hosts', 'name' => $hostname]
+ )
+ );
+ }
+
+ $allowEdit = false;
+ if (Util::hasPermission(Permission::HOSTS) && IcingaHost::exists($hostname, $db)) {
+ $allowEdit = true;
+ }
+ if (Util::hasPermission(Permission::ICINGADB_HOSTS)) {
+ if ((new IcingadbBackend())->canModifyHost($hostname)) {
+ $allowEdit = IcingaHost::exists($hostname, $db);
+ }
+ }
+
+ if ($allowEdit) {
+ $label = mt('director', 'Modify');
+ $actions[] = new Link(
+ $label,
+ Url::fromPath('director/host/edit', [
+ 'name' => $hostname
+ ])
+ );
+ }
+
+ return $actions;
+ }
+
+ protected function db()
+ {
+ $resourceName = Config::module('director')->get('db', 'resource');
+ if (! $resourceName) {
+ return false;
+ }
+
+ return Db::fromResourceName($resourceName);
+ }
+}
diff --git a/library/Director/ProvidedHook/Icingadb/IcingadbSupport.php b/library/Director/ProvidedHook/Icingadb/IcingadbSupport.php
new file mode 100644
index 0000000..5a59304
--- /dev/null
+++ b/library/Director/ProvidedHook/Icingadb/IcingadbSupport.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace Icinga\Module\Director\ProvidedHook\Icingadb;
+
+use Icinga\Module\Icingadb\Hook\IcingadbSupportHook;
+
+class IcingadbSupport extends IcingadbSupportHook
+{
+
+}
diff --git a/library/Director/ProvidedHook/Icingadb/ServiceActions.php b/library/Director/ProvidedHook/Icingadb/ServiceActions.php
new file mode 100644
index 0000000..1603dc3
--- /dev/null
+++ b/library/Director/ProvidedHook/Icingadb/ServiceActions.php
@@ -0,0 +1,87 @@
+<?php
+
+namespace Icinga\Module\Director\ProvidedHook\Icingadb;
+
+use Exception;
+use Icinga\Application\Config;
+use Icinga\Module\Director\Auth\Permission;
+use Icinga\Module\Director\Db;
+use Icinga\Module\Director\Integration\Icingadb\IcingadbBackend;
+use Icinga\Module\Director\Objects\IcingaHost;
+use Icinga\Module\Director\Util;
+use Icinga\Module\Icingadb\Hook\ServiceActionsHook;
+use Icinga\Module\Icingadb\Model\Service;
+use ipl\Web\Url;
+use ipl\Web\Widget\Link;
+
+class ServiceActions extends ServiceActionsHook
+{
+ public function getActionsForObject(Service $service): array
+ {
+ try {
+ return $this->getThem($service);
+ } catch (Exception $e) {
+ return [];
+ }
+ }
+
+ /**
+ * @param Service $service
+ * @return array
+ * @throws \Icinga\Exception\ProgrammingError
+ */
+ protected function getThem(Service $service)
+ {
+ $actions = [];
+ $db = $this->db();
+ if (! $db) {
+ return [];
+ }
+
+ $hostname = $service->host->name;
+ $serviceName = $service->name;
+ if (Util::hasPermission(Permission::INSPECT)) {
+ $actions[] = new Link(
+ mt('director', 'Inspect'),
+ Url::fromPath('director/inspect/object', [
+ 'type' => 'service',
+ 'plural' => 'services',
+ 'name' => sprintf('%s!%s', $hostname, $serviceName)
+ ])
+ );
+ }
+
+ $title = null;
+ if (Util::hasPermission(Permission::HOSTS)) {
+ $title = mt('director', 'Modify');
+ } elseif (Util::hasPermission(Permission::ICINGADB_SERVICES)) {
+ if ((new IcingadbBackend())->canModifyService($hostname, $serviceName)) {
+ $title = mt('director', 'Modify');
+ }
+ } elseif (Util::hasPermission(Permission::ICINGADB_SERVICES_RO)) {
+ $title = mt('director', 'Configuration');
+ }
+
+ if ($title && IcingaHost::exists($hostname, $db)) {
+ $actions[] = new Link(
+ $title,
+ Url::fromPath('director/host/findservice', [
+ 'name' => $hostname,
+ 'service' => $serviceName
+ ])
+ );
+ }
+
+ return $actions;
+ }
+
+ protected function db()
+ {
+ $resourceName = Config::module('director')->get('db', 'resource');
+ if (! $resourceName) {
+ return false;
+ }
+
+ return Db::fromResourceName($resourceName);
+ }
+}
diff --git a/library/Director/ProvidedHook/Monitoring/HostActions.php b/library/Director/ProvidedHook/Monitoring/HostActions.php
index 2e3fba0..2d0469d 100644
--- a/library/Director/ProvidedHook/Monitoring/HostActions.php
+++ b/library/Director/ProvidedHook/Monitoring/HostActions.php
@@ -5,8 +5,9 @@ namespace Icinga\Module\Director\ProvidedHook\Monitoring;
use Exception;
use Icinga\Application\Config;
use Icinga\Authentication\Auth;
+use Icinga\Module\Director\Auth\Permission;
use Icinga\Module\Director\Db;
-use Icinga\Module\Director\Monitoring;
+use Icinga\Module\Director\Integration\MonitoringModule\Monitoring;
use Icinga\Module\Director\Objects\IcingaHost;
use Icinga\Module\Director\Util;
use Icinga\Module\Monitoring\Hook\HostActionsHook;
@@ -32,7 +33,7 @@ class HostActions extends HostActionsHook
return $actions;
}
$hostname = $host->host_name;
- if (Util::hasPermission('director/inspect')) {
+ if (Util::hasPermission(Permission::INSPECT)) {
$actions[mt('director', 'Inspect')] = Url::fromPath(
'director/inspect/object',
array('type' => 'host', 'plural' => 'hosts', 'name' => $hostname)
@@ -40,22 +41,17 @@ class HostActions extends HostActionsHook
}
$allowEdit = false;
- if (Util::hasPermission('director/hosts') && IcingaHost::exists($hostname, $db)) {
+ if (Util::hasPermission(Permission::HOSTS) && IcingaHost::exists($hostname, $db)) {
$allowEdit = true;
}
- $auth = Auth::getInstance();
- if (Util::hasPermission('director/monitoring/hosts')) {
- $monitoring = new Monitoring();
- if ($monitoring->isAvailable() && $monitoring->authCanEditHost($auth, $hostname)) {
+ if (Util::hasPermission(Permission::MONITORING_HOSTS)) {
+ if ((new Monitoring(Auth::getInstance()))->canModifyHost($hostname)) {
$allowEdit = IcingaHost::exists($hostname, $db);
}
}
if ($allowEdit) {
- $actions[mt('director', 'Modify')] = Url::fromPath(
- 'director/host/edit',
- array('name' => $hostname)
- );
+ $actions[mt('director', 'Modify')] = Url::fromPath('director/host/edit', ['name' => $hostname]);
}
return $actions;
diff --git a/library/Director/ProvidedHook/Monitoring/ServiceActions.php b/library/Director/ProvidedHook/Monitoring/ServiceActions.php
index b2e303a..834b166 100644
--- a/library/Director/ProvidedHook/Monitoring/ServiceActions.php
+++ b/library/Director/ProvidedHook/Monitoring/ServiceActions.php
@@ -5,8 +5,9 @@ namespace Icinga\Module\Director\ProvidedHook\Monitoring;
use Exception;
use Icinga\Application\Config;
use Icinga\Authentication\Auth;
+use Icinga\Module\Director\Auth\Permission;
use Icinga\Module\Director\Db;
-use Icinga\Module\Director\Monitoring;
+use Icinga\Module\Director\Integration\MonitoringModule\Monitoring;
use Icinga\Module\Director\Objects\IcingaHost;
use Icinga\Module\Director\Util;
use Icinga\Module\Monitoring\Hook\ServiceActionsHook;
@@ -39,7 +40,7 @@ class ServiceActions extends ServiceActionsHook
$hostname = $service->host_name;
$serviceName = $service->service_description;
- if (Util::hasPermission('director/inspect')) {
+ if (Util::hasPermission(Permission::INSPECT)) {
$actions[mt('director', 'Inspect')] = Url::fromPath('director/inspect/object', [
'type' => 'service',
'plural' => 'services',
@@ -52,16 +53,13 @@ class ServiceActions extends ServiceActionsHook
}
$title = null;
- if (Util::hasPermission('director/hosts')) {
+ if (Util::hasPermission(Permission::HOSTS)) {
$title = mt('director', 'Modify');
- } elseif (Util::hasPermission('director/monitoring/services')) {
- $monitoring = new Monitoring();
- if ($monitoring->isAvailable()
- && $monitoring->authCanEditService(Auth::getInstance(), $hostname, $serviceName)
- ) {
+ } elseif (Util::hasPermission(Permission::MONITORING_SERVICES)) {
+ if ((new Monitoring(Auth::getInstance()))->canModifyService($hostname, $serviceName)) {
$title = mt('director', 'Modify');
}
- } elseif (Util::hasPermission('director/monitoring/services-ro')) {
+ } elseif (Util::hasPermission(Permission::MONITORING_SERVICES_RO)) {
$title = mt('director', 'Configuration');
}
diff --git a/library/Director/Resolver/CommandUsage.php b/library/Director/Resolver/CommandUsage.php
index 7e3e0c5..49fc31b 100644
--- a/library/Director/Resolver/CommandUsage.php
+++ b/library/Director/Resolver/CommandUsage.php
@@ -75,7 +75,7 @@ class CommandUsage
$suffix = $urlSuffix[$objectType];
$links[] = Link::create(
sprintf($caption, $res->$objectType),
- "director/${type}s$suffix",
+ "director/{$type}s$suffix",
['command' => $name]
);
}
@@ -96,7 +96,7 @@ class CommandUsage
$query = $this->db->select()->from("icinga_$table", $columns);
foreach ($rels as $rel) {
- $query->orWhere("${rel}_id = ?", $id);
+ $query->orWhere("{$rel}_id = ?", $id);
}
return $this->db->fetchRow($query);
diff --git a/library/Director/Resolver/IcingaObjectResolver.php b/library/Director/Resolver/IcingaObjectResolver.php
index 540e2c2..c751476 100644
--- a/library/Director/Resolver/IcingaObjectResolver.php
+++ b/library/Director/Resolver/IcingaObjectResolver.php
@@ -176,7 +176,7 @@ class IcingaObjectResolver
$object->groups = $groups;
}
- $templates = $this->getTemplateNamesById($id);
+ $templates = $this->getTemplateNamesByID($id);
if (! empty($templates)) {
$object->templates = \array_reverse($templates);
}
@@ -198,7 +198,7 @@ class IcingaObjectResolver
}
$query = $this->db->select()
->from([
- 'oi' => "${baseTable}_inheritance"
+ 'oi' => "{$baseTable}_inheritance"
], [
$relColumn,
$groupColumn
@@ -494,12 +494,12 @@ class IcingaObjectResolver
{
$type = $this->getType();
$groupsTable = $this->baseTable . 'group';
- $groupMembershipTable = "${groupsTable}_$type";
+ $groupMembershipTable = "{$groupsTable}_$type";
if ($resolved) {
$groupMembershipTable .= '_resolved';
}
- $oRef = "${type}_id";
- $gRef = "${type}group_id";
+ $oRef = "{$type}_id";
+ $gRef = "{$type}group_id";
return $this->db->select()
->from(['gm' => $groupMembershipTable], [
diff --git a/library/Director/Resolver/TemplateTree.php b/library/Director/Resolver/TemplateTree.php
index f8d8fed..bf941e2 100644
--- a/library/Director/Resolver/TemplateTree.php
+++ b/library/Director/Resolver/TemplateTree.php
@@ -80,13 +80,13 @@ class TemplateTree
$map = [];
$db = $this->db;
$type = $this->type;
- $table = "icinga_${type}_inheritance";
+ $table = "icinga_{$type}_inheritance";
$query = $db->select()->from(
['i' => $table],
[
- 'object' => "i.${type}_id",
- 'parent' => "i.parent_${type}_id",
+ 'object' => "i.{$type}_id",
+ 'parent' => "i.parent_{$type}_id",
]
)->order('i.weight');
@@ -439,12 +439,12 @@ class TemplateTree
if ($type === 'command') {
$joinCondition = $db->quoteInto(
- "p.id = i.parent_${type}_id",
+ "p.id = i.parent_{$type}_id",
'template'
);
} else {
$joinCondition = $db->quoteInto(
- "p.id = i.parent_${type}_id AND p.object_type = ?",
+ "p.id = i.parent_{$type}_id AND p.object_type = ?",
'template'
);
}
@@ -466,7 +466,7 @@ class TemplateTree
['p' => $table],
$joinCondition,
[]
- )->order('o.id')->order('i.weight');
+ )->order('o.object_name')->order('i.weight');
if ($type !== 'command') {
$query->where(
diff --git a/library/Director/RestApi/RestApiClient.php b/library/Director/RestApi/RestApiClient.php
index 2ebc4d4..6dcf93c 100644
--- a/library/Director/RestApi/RestApiClient.php
+++ b/library/Director/RestApi/RestApiClient.php
@@ -8,7 +8,6 @@ use RuntimeException;
class RestApiClient
{
- /** @var resource */
private $curl;
/** @var string HTTP or HTTPS */
@@ -287,7 +286,7 @@ class RestApiClient
if ($statusCode >= 400) {
throw new RuntimeException(
- "Got $statusCode: " . \var_export($res, 1)
+ "Got $statusCode: " . \var_export($res, true)
);
}
@@ -295,14 +294,14 @@ class RestApiClient
}
/**
- * @return resource
+ * @throws RuntimeException
*/
protected function curl()
{
if ($this->curl === null) {
- $this->curl = \curl_init(\sprintf('https://%s:%d', $this->host, $this->port));
+ $this->curl = curl_init(sprintf('https://%s:%d', $this->host, $this->port));
if (! $this->curl) {
- throw new RuntimeException('CURL INIT ERROR: ' . \curl_error($this->curl));
+ throw new RuntimeException('CURL INIT FAILED');
}
}
diff --git a/library/Director/RestApi/RestApiParams.php b/library/Director/RestApi/RestApiParams.php
index c237ac5..741eeed 100644
--- a/library/Director/RestApi/RestApiParams.php
+++ b/library/Director/RestApi/RestApiParams.php
@@ -19,8 +19,9 @@ class RestApiParams
}
$exporter->enableHostServices();
}
+ /** @var ?string $properties */
$properties = $params->shift('properties');
- if ($properties !== null && strlen($properties)) {
+ if ($properties) {
$exporter->filterProperties(preg_split('/\s*,\s*/', $properties, -1, PREG_SPLIT_NO_EMPTY));
}
$exporter->resolveObjects($resolved);
diff --git a/library/Director/Restriction/FilterByNameRestriction.php b/library/Director/Restriction/FilterByNameRestriction.php
index 8c3b256..aef28c4 100644
--- a/library/Director/Restriction/FilterByNameRestriction.php
+++ b/library/Director/Restriction/FilterByNameRestriction.php
@@ -30,7 +30,7 @@ class FilterByNameRestriction extends ObjectRestriction
protected function setNameForType($type)
{
- $this->name = "director/${type}/filter-by-name";
+ $this->name = "director/{$type}/filter-by-name";
}
public function allows(IcingaObject $object)
diff --git a/library/Director/Restriction/HostgroupRestriction.php b/library/Director/Restriction/HostgroupRestriction.php
index 1a6792b..f2face4 100644
--- a/library/Director/Restriction/HostgroupRestriction.php
+++ b/library/Director/Restriction/HostgroupRestriction.php
@@ -2,7 +2,7 @@
namespace Icinga\Module\Director\Restriction;
-use Icinga\Exception\ProgrammingError;
+use Icinga\Module\Director\Auth\Restriction;
use Icinga\Module\Director\Db\IcingaObjectFilterHelper;
use Icinga\Module\Director\Objects\IcingaHost;
use Icinga\Module\Director\Objects\IcingaHostGroup;
@@ -11,7 +11,7 @@ use Zend_Db_Select as ZfSelect;
class HostgroupRestriction extends ObjectRestriction
{
- protected $name = 'director/filter/hostgroups';
+ protected $name = Restriction::FILTER_HOSTGROUPS;
public function allows(IcingaObject $object)
{
@@ -93,7 +93,7 @@ class HostgroupRestriction extends ObjectRestriction
['id']
)->where('id = ?', $hostgroup->id);
- $this->filterHostGroupsQuery($query);
+ $this->filterHostGroupsQuery($query, 'h');
return (int) $this->db->fetchOne($query) === (int) $hostgroup->get('id');
}
@@ -141,7 +141,7 @@ class HostgroupRestriction extends ObjectRestriction
if (empty($groups)) {
$query->where('(1 = 0)');
} else {
- $query->where("${tableAlias}.object_name IN (?)", $groups);
+ $query->where("{$tableAlias}.object_name IN (?)", $groups);
}
}
diff --git a/library/Director/StartupLogRenderer.php b/library/Director/StartupLogRenderer.php
index bc7b3ea..9d5810f 100644
--- a/library/Director/StartupLogRenderer.php
+++ b/library/Director/StartupLogRenderer.php
@@ -33,7 +33,9 @@ class StartupLogRenderer implements ValidHtml
// len [stage] + 1
$markReplace = ' ^';
- foreach (preg_split('/\n/', $log) as $line) {
+ /** @var string[] $logLines */
+ $logLines = preg_split('/\n/', $log);
+ foreach ($logLines as $line) {
if (preg_match('/^\[([\d\s\:\+\-]+)\]\s/', $line, $m)) {
$time = $m[1];
// TODO: we might use new DateTime($time) and show a special "timeAgo"
@@ -83,9 +85,9 @@ class StartupLogRenderer implements ValidHtml
}
if ($time === null) {
- $lines[] .= $line;
+ $lines[] = $line;
} else {
- $lines[] .= "[$time] $line";
+ $lines[] = "[$time] $line";
}
}
return implode("\n", $lines);
diff --git a/library/Director/Test/BaseTestCase.php b/library/Director/Test/BaseTestCase.php
index 611805b..f0cf8e5 100644
--- a/library/Director/Test/BaseTestCase.php
+++ b/library/Director/Test/BaseTestCase.php
@@ -10,20 +10,15 @@ use Icinga\Module\Director\Db;
use Icinga\Module\Director\Db\Migrations;
use Icinga\Module\Director\Objects\IcingaObject;
use Icinga\Module\Director\Objects\IcingaZone;
-use PHPUnit_Framework_TestCase;
+use Icinga\Test\BaseTestCase as IcingaBaseTestCase;
-abstract class BaseTestCase extends PHPUnit_Framework_TestCase
+abstract class BaseTestCase extends IcingaBaseTestCase
{
private static $app;
/** @var Db */
private static $db;
- public function setUp()
- {
- $this->app();
- }
-
protected function skipForMissingDb()
{
if ($this->hasDb()) {
@@ -69,6 +64,9 @@ abstract class BaseTestCase extends PHPUnit_Framework_TestCase
if (array_key_exists('DIRECTOR_TESTDB_HOST', $_SERVER)) {
$dbConfig->host = $_SERVER['DIRECTOR_TESTDB_HOST'];
}
+ if (array_key_exists('DIRECTOR_TESTDB_PORT', $_SERVER)) {
+ $dbConfig->port = $_SERVER['DIRECTOR_TESTDB_PORT'];
+ }
if (array_key_exists('DIRECTOR_TESTDB_USER', $_SERVER)) {
$dbConfig->username = $_SERVER['DIRECTOR_TESTDB_USER'];
}
@@ -78,11 +76,14 @@ abstract class BaseTestCase extends PHPUnit_Framework_TestCase
self::$db = new Db($dbConfig);
$migrations = new Migrations(self::$db);
$migrations->applyPendingMigrations();
- IcingaZone::create([
+ $zone = IcingaZone::create([
'object_name' => 'director-global',
'object_type' => 'external_object',
'is_global' => 'y'
- ])->store(self::$db);
+ ]);
+ if (! IcingaZone::exists($zone->getId(), self::$db)) {
+ $zone->store(self::$db);
+ }
}
return self::$db;
diff --git a/library/Director/Test/IcingaObjectTestCase.php b/library/Director/Test/IcingaObjectTestCase.php
index a37fced..5d0dde3 100644
--- a/library/Director/Test/IcingaObjectTestCase.php
+++ b/library/Director/Test/IcingaObjectTestCase.php
@@ -76,7 +76,7 @@ abstract class IcingaObjectTestCase extends BaseTestCase
/**
* @inheritdoc
*/
- public function tearDown()
+ public function tearDown(): void
{
if ($this->hasDb()) {
/** @var IcingaObject $object */
@@ -88,5 +88,7 @@ abstract class IcingaObjectTestCase extends BaseTestCase
$this->subject->delete();
}
}
+
+ parent::tearDown();
}
}
diff --git a/library/Director/Test/SyncTest.php b/library/Director/Test/SyncTest.php
index 7614ff9..d118eea 100644
--- a/library/Director/Test/SyncTest.php
+++ b/library/Director/Test/SyncTest.php
@@ -29,8 +29,9 @@ abstract class SyncTest extends BaseTestCase
/** @var Sync */
protected $sync;
- public function setUp()
+ public function setUp(): void
{
+ parent::setUp();
$this->source = ImportSource::create(array(
'source_name' => 'testimport',
'provider_class' => 'Icinga\\Module\\Director\\Test\\ImportSourceDummy',
@@ -49,7 +50,7 @@ abstract class SyncTest extends BaseTestCase
$this->sync = new Sync($this->rule);
}
- public function tearDown()
+ public function tearDown(): void
{
// properties should be deleted automatically
if ($this->rule !== null && $this->rule->hasBeenLoadedFromDb()) {
@@ -75,6 +76,7 @@ abstract class SyncTest extends BaseTestCase
// make sure cache is clean for other tests
PrefetchCache::forget();
DbObject::clearAllPrefetchCaches();
+ parent::tearDown();
}
/**
diff --git a/library/Director/Test/TestSuiteLint.php b/library/Director/Test/TestSuiteLint.php
index 41941eb..0010c28 100644
--- a/library/Director/Test/TestSuiteLint.php
+++ b/library/Director/Test/TestSuiteLint.php
@@ -10,6 +10,8 @@ class TestSuiteLint extends TestSuite
protected $failed;
+ protected $result = [];
+
public function run()
{
$this->checked = $this->failed = array();
diff --git a/library/Director/Test/TestSuiteUnit.php b/library/Director/Test/TestSuiteUnit.php
index 8156eba..93dc692 100644
--- a/library/Director/Test/TestSuiteUnit.php
+++ b/library/Director/Test/TestSuiteUnit.php
@@ -4,6 +4,8 @@ namespace Icinga\Module\Director\Test;
abstract class TestSuiteUnit
{
+ private $testdoxFile;
+
public function run()
{
}
@@ -15,7 +17,7 @@ abstract class TestSuiteUnit
public function __destruct()
{
if ($this->testdoxFile && file_exists($this->testdoxFile)) {
- unlink($this->testDoxfile);
+ unlink($this->testdoxFile);
}
}
diff --git a/library/Director/Web/Controller/ActionController.php b/library/Director/Web/Controller/ActionController.php
index 6282a16..e851d82 100644
--- a/library/Director/Web/Controller/ActionController.php
+++ b/library/Director/Web/Controller/ActionController.php
@@ -4,10 +4,14 @@ namespace Icinga\Module\Director\Web\Controller;
use gipfl\Translation\StaticTranslator;
use Icinga\Application\Benchmark;
+use Icinga\Application\Modules\Module;
use Icinga\Data\Paginatable;
use Icinga\Exception\NotFoundError;
use Icinga\Exception\ProgrammingError;
-use Icinga\Module\Director\Monitoring;
+use Icinga\Module\Director\Integration\Icingadb\IcingadbBackend;
+use Icinga\Module\Director\Integration\BackendInterface;
+use Icinga\Module\Director\Integration\MonitoringModule\Monitoring;
+use Icinga\Module\Director\ProvidedHook\Icingadb\IcingadbSupport;
use Icinga\Module\Director\Web\Controller\Extension\CoreApi;
use Icinga\Module\Director\Web\Controller\Extension\DirectorDb;
use Icinga\Module\Director\Web\Controller\Extension\RestApi;
@@ -36,8 +40,8 @@ abstract class ActionController extends Controller implements ControlsAndContent
/** @var UrlParams Hint for IDE, somehow does not work in web */
protected $params;
- /** @var Monitoring */
- private $monitoring;
+ /** @var BackendInterface */
+ private $backend;
/**
* @throws SecurityException
@@ -219,7 +223,7 @@ abstract class ActionController extends Controller implements ControlsAndContent
// Hint -> $this->view->compact is the only way since v2.8.0
if ($this->view->compact || $this->getOriginalUrl()->getParam('view') === 'compact') {
if ($this->view->controls) {
- $this->controls()->getAttributes()->add('style', 'display: none;');
+ $this->controls()->getAttributes()->add('class', 'compact');
}
}
} else {
@@ -240,14 +244,18 @@ abstract class ActionController extends Controller implements ControlsAndContent
}
/**
- * @return Monitoring
+ * @return BackendInterface
*/
- protected function monitoring()
+ protected function backend(): BackendInterface
{
- if ($this->monitoring === null) {
- $this->monitoring = new Monitoring;
+ if ($this->backend === null) {
+ if (Module::exists('icingadb') && IcingadbSupport::useIcingaDbAsBackend()) {
+ $this->backend = new IcingadbBackend();
+ } else {
+ $this->backend = new Monitoring($this->getAuth());
+ }
}
- return $this->monitoring;
+ return $this->backend;
}
}
diff --git a/library/Director/Web/Controller/BranchHelper.php b/library/Director/Web/Controller/BranchHelper.php
index ac2a480..89aa6c1 100644
--- a/library/Director/Web/Controller/BranchHelper.php
+++ b/library/Director/Web/Controller/BranchHelper.php
@@ -3,12 +3,13 @@
namespace Icinga\Module\Director\Web\Controller;
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\Db\Branch\PreferredBranchSupport;
use Icinga\Module\Director\Objects\IcingaObject;
use Icinga\Module\Director\Web\Widget\NotInBranchedHint;
+use Ramsey\Uuid\UuidInterface;
trait BranchHelper
{
@@ -18,15 +19,21 @@ trait BranchHelper
/** @var BranchStore */
protected $branchStore;
+ /** @var ?bool */
+ protected $hasPreferredBranch = null;
+
/**
- * @return false|\Ramsey\Uuid\UuidInterface
+ * @return ?UuidInterface
*/
- protected function getBranchUuid()
+ protected function getBranchUuid(): ?UuidInterface
{
return $this->getBranch()->getUuid();
}
- protected function getBranch()
+ /**
+ * @return Branch
+ */
+ protected function getBranch(): Branch
{
if ($this->branch === null) {
/** @var ActionController $this */
@@ -39,7 +46,7 @@ trait BranchHelper
/**
* @return BranchStore
*/
- protected function getBranchStore()
+ protected function getBranchStore(): BranchStore
{
if ($this->branchStore === null) {
$this->branchStore = new BranchStore($this->db());
@@ -48,12 +55,15 @@ trait BranchHelper
return $this->branchStore;
}
- protected function hasBranch()
+ /**
+ * @return bool
+ */
+ protected function hasBranch(): bool
{
return $this->getBranchUuid() !== null;
}
- protected function enableStaticObjectLoader($table)
+ protected function enableStaticObjectLoader($table): void
{
if (BranchSupport::existsForTableName($table)) {
IcingaObject::setDbObjectStore(new DbObjectStore($this->db(), $this->getBranch()));
@@ -64,7 +74,7 @@ trait BranchHelper
* @param string $subject
* @return bool
*/
- protected function showNotInBranch($subject)
+ protected function showNotInBranch($subject): bool
{
if ($this->getBranch()->isBranch()) {
$this->content()->add(new NotInBranchedHint($subject, $this->getBranch(), $this->Auth()));
@@ -73,4 +83,18 @@ trait BranchHelper
return false;
}
+
+ protected function hasPreferredBranch(): bool
+ {
+ if ($this->hasPreferredBranch === null) {
+ $implementation = Branch::optionalHook();
+ if ($implementation instanceof PreferredBranchSupport) {
+ $this->hasPreferredBranch = $implementation->hasPreferredBranch($this->Auth());
+ } else {
+ $this->hasPreferredBranch = false;
+ }
+ }
+
+ return $this->hasPreferredBranch;
+ }
}
diff --git a/library/Director/Web/Controller/Extension/DirectorDb.php b/library/Director/Web/Controller/Extension/DirectorDb.php
index 03bec81..c36e358 100644
--- a/library/Director/Web/Controller/Extension/DirectorDb.php
+++ b/library/Director/Web/Controller/Extension/DirectorDb.php
@@ -2,6 +2,7 @@
namespace Icinga\Module\Director\Web\Controller\Extension;
+use Icinga\Module\Director\Auth\Restriction;
use Icinga\Module\Director\Db;
use Icinga\Module\Director\Web\Controller\ActionController;
use Icinga\Module\Director\Web\Window;
@@ -65,7 +66,7 @@ trait DirectorDb
$auth = $this->Auth();
$available = $this->listAvailableDbResourceNames();
- if ($resourceNames = $auth->getRestrictions('director/db_resource')) {
+ if ($resourceNames = $auth->getRestrictions(Restriction::DB_RESOURCE)) {
$names = [];
foreach ($resourceNames as $rNames) {
foreach ($this->splitList($rNames) as $name) {
diff --git a/library/Director/Web/Controller/Extension/RestApi.php b/library/Director/Web/Controller/Extension/RestApi.php
index 3158f49..fb10c86 100644
--- a/library/Director/Web/Controller/Extension/RestApi.php
+++ b/library/Director/Web/Controller/Extension/RestApi.php
@@ -4,6 +4,7 @@ namespace Icinga\Module\Director\Web\Controller\Extension;
use Icinga\Exception\AuthenticationException;
use Icinga\Exception\NotFoundError;
+use Icinga\Module\Director\Auth\Permission;
use Icinga\Module\Director\Exception\JsonException;
use Icinga\Web\Response;
use InvalidArgumentException;
@@ -55,7 +56,7 @@ trait RestApi
*/
protected function assertApiPermission()
{
- if (! $this->hasPermission('director/api')) {
+ if (! $this->hasPermission(Permission::API)) {
throw new AuthenticationException('You are not allowed to access this API');
}
}
diff --git a/library/Director/Web/Controller/Extension/SingleObjectApiHandler.php b/library/Director/Web/Controller/Extension/SingleObjectApiHandler.php
deleted file mode 100644
index bc51548..0000000
--- a/library/Director/Web/Controller/Extension/SingleObjectApiHandler.php
+++ /dev/null
@@ -1,236 +0,0 @@
-<?php
-
-namespace Icinga\Module\Director\Web\Controller\Extension;
-
-use Exception;
-use Icinga\Exception\IcingaException;
-use Icinga\Exception\InvalidPropertyException;
-use Icinga\Exception\NotFoundError;
-use Icinga\Module\Director\Forms\IcingaDeleteObjectForm;
-use Icinga\Module\Director\Objects\IcingaObject;
-use Icinga\Web\Request;
-use Icinga\Web\Response;
-
-class SingleObjectApiHandler
-{
- use DirectorDb;
-
- /** @var IcingaObject */
- private $object;
-
- /** @var string */
- private $type;
-
- /** @var Request */
- private $request;
-
- /** @var Response */
- private $response;
-
- /** @var \Icinga\Web\UrlParams */
- private $params;
-
- public function __construct($type, Request $request, Response $response)
- {
- $this->type = $type;
- $this->request = $request;
- $this->response = $response;
- $this->params = $request->getUrl()->getParams();
- }
-
- public function runFailSafe()
- {
- try {
- $this->loadObject();
- $this->run();
- } catch (NotFoundError $e) {
- $this->sendJsonError($e->getMessage(), 404);
- } catch (Exception $e) {
- $response = $this->response;
- if ($response->getHttpResponseCode() === 200) {
- $response->setHttpResponseCode(500);
- }
-
- $this->sendJsonError($e->getMessage());
- }
- }
-
- protected function retrieveObject()
- {
- $this->requireObject();
- $this->sendJson(
- $this->object->toPlainObject(
- $this->params->shift('resolved'),
- ! $this->params->shift('withNull'),
- $this->params->shift('properties')
- )
- );
- }
-
- protected function deleteObject()
- {
- $this->requireObject();
- $obj = $this->object->toPlainObject(false, true);
- $form = new IcingaDeleteObjectForm();
- $form->setObject($this->object)
- ->setRequest($this->request)
- ->onSuccess();
-
- $this->sendJson($obj);
- }
-
- protected function storeObject()
- {
- $data = json_decode($this->request->getRawBody());
-
- if ($data === null) {
- $this->response->setHttpResponseCode(400);
- throw new IcingaException(
- 'Invalid JSON: %s' . $this->request->getRawBody(),
- $this->getLastJsonError()
- );
- } else {
- $data = (array) $data;
- }
-
- if ($object = $this->object) {
- if ($this->request->getMethod() === 'POST') {
- $object->setProperties($data);
- } else {
- $data = array_merge([
- 'object_type' => $object->object_type,
- 'object_name' => $object->object_name
- ], $data);
- $object->replaceWith(
- IcingaObject::createByType($this->type, $data, $db)
- );
- }
- } else {
- $object = IcingaObject::createByType($this->type, $data, $db);
- }
-
- if ($object->hasBeenModified()) {
- $status = $object->hasBeenLoadedFromDb() ? 200 : 201;
- $object->store();
- $this->response->setHttpResponseCode($status);
- } else {
- $this->response->setHttpResponseCode(304);
- }
-
- $this->sendJson($object->toPlainObject(false, true));
- }
-
- public function run()
- {
- switch ($this->request->getMethod()) {
- case 'DELETE':
- $this->deleteObject();
- break;
-
- case 'POST':
- case 'PUT':
- $this->storeObject();
- break;
-
- case 'GET':
- $this->retrieveObject();
- break;
-
- default:
- $this->response->setHttpResponseCode(400);
- throw new IcingaException(
- 'Unsupported method: %s',
- $this->request->getMethod()
- );
- }
- }
-
- protected function requireObject()
- {
- if (! $this->object) {
- $this->response->setHttpResponseCode(404);
- if (! $this->params->get('name')) {
- throw new NotFoundError('You need to pass a "name" parameter to access a specific object');
- } else {
- throw new NotFoundError('No such object available');
- }
- }
- }
-
- // TODO: just return json_last_error_msg() for PHP >= 5.5.0
- protected function getLastJsonError()
- {
- switch (json_last_error()) {
- case JSON_ERROR_DEPTH:
- return 'The maximum stack depth has been exceeded';
- case JSON_ERROR_CTRL_CHAR:
- return 'Control character error, possibly incorrectly encoded';
- case JSON_ERROR_STATE_MISMATCH:
- return 'Invalid or malformed JSON';
- case JSON_ERROR_SYNTAX:
- return 'Syntax error';
- case JSON_ERROR_UTF8:
- return 'Malformed UTF-8 characters, possibly incorrectly encoded';
- default:
- return 'An error occured when parsing a JSON string';
- }
- }
-
- protected function sendJson($object)
- {
- $this->response->setHeader('Content-Type', 'application/json', true);
- $this->_helper->layout()->disableLayout();
- $this->_helper->viewRenderer->setNoRender(true);
- echo json_encode($object, JSON_PRETTY_PRINT) . "\n";
- }
-
- protected function sendJsonError($message, $code = null)
- {
- $response = $this->response;
-
- if ($code !== null) {
- $response->setHttpResponseCode((int) $code);
- }
-
- $this->sendJson((object) ['error' => $message]);
- }
-
- protected function loadObject()
- {
- if ($this->object === null) {
- if ($name = $this->params->get('name')) {
- $this->object = IcingaObject::loadByType(
- $this->type,
- $name,
- $this->db()
- );
-
- if (! $this->allowsObject($this->object)) {
- $this->object = null;
- throw new NotFoundError('No such object available');
- }
- } elseif ($id = $this->params->get('id')) {
- $this->object = IcingaObject::loadByType(
- $this->type,
- (int) $id,
- $this->db()
- );
- } elseif ($this->request->isApiRequest()) {
- if ($this->request->isGet()) {
- $this->response->setHttpResponseCode(422);
-
- throw new InvalidPropertyException(
- 'Cannot load object, missing parameters'
- );
- }
- }
- }
-
- return $this->object;
- }
-
- protected function allowsObject(IcingaObject $object)
- {
- return true;
- }
-}
diff --git a/library/Director/Web/Controller/ObjectController.php b/library/Director/Web/Controller/ObjectController.php
index 0c06937..994a717 100644
--- a/library/Director/Web/Controller/ObjectController.php
+++ b/library/Director/Web/Controller/ObjectController.php
@@ -10,6 +10,7 @@ use Icinga\Exception\ProgrammingError;
use Icinga\Module\Director\Data\Db\DbObjectTypeRegistry;
use Icinga\Module\Director\Db\Branch\Branch;
use Icinga\Module\Director\Db\Branch\BranchedObject;
+use Icinga\Module\Director\Db\Branch\BranchSupport;
use Icinga\Module\Director\Db\Branch\UuidLookup;
use Icinga\Module\Director\Deployment\DeploymentInfo;
use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
@@ -61,8 +62,11 @@ abstract class ObjectController extends ActionController
public function init()
{
- parent::init();
$this->enableStaticObjectLoader($this->getTableName());
+ if (! $this->getRequest()->isApiRequest()) {
+ $this->loadOptionalObject();
+ }
+ parent::init();
if ($this->getRequest()->isApiRequest()) {
$this->initializeRestApi();
} else {
@@ -97,7 +101,6 @@ abstract class ObjectController extends ActionController
protected function initializeWebRequest()
{
- $this->loadOptionalObject();
if ($this->getRequest()->getActionName() === 'add') {
$this->addSingleTab(
sprintf($this->translate('Add %s'), ucfirst($this->getType())),
@@ -151,8 +154,11 @@ abstract class ObjectController extends ActionController
$this->addObject();
}
$branch = $this->getBranch();
- if ($branch->isBranch() && ! $this->getRequest()->isApiRequest()) {
- $this->content()->add(new BranchedObjectHint($branch, $this->Auth()));
+ if (! $this->getRequest()->isApiRequest()) {
+ $hasPreferred = $this->hasPreferredBranch();
+ if ($branch->isBranch() || $hasPreferred) {
+ $this->content()->add(new BranchedObjectHint($branch, $this->Auth(), null, $hasPreferred));
+ }
}
$form->handleRequest();
@@ -267,7 +273,7 @@ abstract class ObjectController extends ActionController
if ($id = $this->params->get('field_id')) {
$form->loadObject([
- "${type}_id" => $object->id,
+ "{$type}_id" => $object->id,
'datafield_id' => $id
]);
@@ -362,7 +368,7 @@ abstract class ObjectController extends ActionController
$this->actions()->add([
Link::create(
$this->translate('Usage'),
- "director/${type}template/usage",
+ "director/{$type}template/usage",
['name' => $object->getObjectName()],
['class' => 'icon-sitemap']
)
@@ -558,8 +564,16 @@ abstract class ObjectController extends ActionController
if (! $this->allowsObject($object)) {
throw new NotFoundError('No such object available');
}
- if ($showHint && $branch->isBranch() && $object->isObject() && ! $this->getRequest()->isApiRequest()) {
- $this->content()->add(new BranchedObjectHint($branch, $this->Auth(), $branchedObject));
+ if ($showHint) {
+ $hasPreferredBranch = $this->hasPreferredBranch();
+ if (($hasPreferredBranch || $branch->isBranch())
+ && $object->isObject()
+ && ! $this->getRequest()->isApiRequest()
+ ) {
+ $this->content()->add(
+ new BranchedObjectHint($branch, $this->Auth(), $branchedObject, $hasPreferredBranch)
+ );
+ }
}
return $object;
diff --git a/library/Director/Web/Controller/ObjectsController.php b/library/Director/Web/Controller/ObjectsController.php
index 8c10b44..c4c96c5 100644
--- a/library/Director/Web/Controller/ObjectsController.php
+++ b/library/Director/Web/Controller/ObjectsController.php
@@ -13,6 +13,7 @@ use Icinga\Module\Director\Forms\IcingaMultiEditForm;
use Icinga\Module\Director\Objects\IcingaCommand;
use Icinga\Module\Director\Objects\IcingaHost;
use Icinga\Module\Director\Objects\IcingaObject;
+use Icinga\Module\Director\Objects\IcingaService;
use Icinga\Module\Director\RestApi\IcingaObjectsHandler;
use Icinga\Module\Director\Web\ActionBar\ObjectsActionBar;
use Icinga\Module\Director\Web\ActionBar\TemplateActionBar;
@@ -84,8 +85,9 @@ abstract class ObjectsController extends ActionController
} elseif ($request->getActionName() === 'applyrules') {
$table->filterObjectType('apply');
}
+ /** @var ?string $search */
$search = $this->params->get('q');
- if ($search !== null && \strlen($search) > 0) {
+ if ($search) {
$table->search($search);
}
@@ -110,7 +112,7 @@ abstract class ObjectsController extends ActionController
$type = $this->getType();
if ($this->params->get('format') === 'json') {
$filename = sprintf(
- "director-${type}_%s.json",
+ "director-{$type}_%s.json",
date('YmdHis')
);
$this->getResponse()->setHeader('Content-disposition', "attachment; filename=$filename", true);
@@ -124,7 +126,7 @@ abstract class ObjectsController extends ActionController
->addTitle($this->translate(ucfirst($this->getPluralType())))
->actions(new ObjectsActionBar($this->getBaseObjectUrl(), $this->url()));
- $this->content()->add(new BranchedObjectsHint($this->getBranch(), $this->Auth()));
+ $this->content()->add(new BranchedObjectsHint($this->getBranch(), $this->Auth(), $this->hasPreferredBranch()));
if ($type === 'command' && $this->params->get('type') === 'external_object') {
$this->tabs()->activate('external');
@@ -143,8 +145,7 @@ abstract class ObjectsController extends ActionController
*/
protected function getTable()
{
- $table = ObjectsTable::create($this->getType(), $this->db())
- ->setAuth($this->getAuth())
+ $table = ObjectsTable::create($this->getType(), $this->db(), $this->getAuth())
->setBranchUuid($this->getBranchUuid())
->setBaseObjectUrl($this->getBaseObjectUrl());
@@ -157,7 +158,7 @@ abstract class ObjectsController extends ActionController
*/
protected function getApplyRulesTable()
{
- $table = new ApplyRulesTable($this->db());
+ $table = (new ApplyRulesTable($this->db()))->setBranch($this->getBranch());
$table->setType($this->getType())
->setBaseObjectUrl($this->getBaseObjectUrl());
$this->eventuallyFilterCommand($table);
@@ -235,7 +236,7 @@ abstract class ObjectsController extends ActionController
if ($this->params->get('format') === 'json') {
$filename = sprintf(
- "director-${type}-templates_%s.json",
+ "director-{$type}-templates_%s.json",
date('YmdHis')
);
$this->getResponse()->setHeader('Content-disposition', "attachment; filename=$filename", true);
@@ -290,7 +291,7 @@ abstract class ObjectsController extends ActionController
if ($this->params->get('format') === 'json') {
$filename = sprintf(
- "director-${type}-applyrules_%s.json",
+ "director-{$type}-applyrules_%s.json",
date('YmdHis')
);
$this->getResponse()->setHeader('Content-disposition', "attachment; filename=$filename", true);
@@ -313,7 +314,7 @@ abstract class ObjectsController extends ActionController
->add(
Link::create(
$this->translate('Add'),
- "${baseUrl}/add",
+ "{$baseUrl}/add",
['type' => 'apply'],
[
'title' => sprintf(
@@ -354,7 +355,7 @@ abstract class ObjectsController extends ActionController
$this->actions()->add(
Link::create(
$this->translate('Add'),
- "director/${type}set/add",
+ "director/{$type}set/add",
null,
[
'title' => sprintf(
@@ -409,7 +410,12 @@ abstract class ObjectsController extends ActionController
$objects[$name] = $class::load($name, $db);
} elseif ($col === 'uuid') {
$object = $store->load($table, Uuid::fromString($ex->getExpression()));
- $objects[$object->getObjectName()] = $object;
+ if ($object instanceof IcingaService) {
+ $host = $object->getRelated('host');
+ $objects[$host->getObjectName() . ': ' . $object->getObjectName()] = $object;
+ } else {
+ $objects[$object->getObjectName()] = $object;
+ }
} else {
throw new InvalidArgumentException("'$col' is no a valid key component for '$type'");
}
diff --git a/library/Director/Web/Controller/TemplateController.php b/library/Director/Web/Controller/TemplateController.php
index c368a82..4f0898a 100644
--- a/library/Director/Web/Controller/TemplateController.php
+++ b/library/Director/Web/Controller/TemplateController.php
@@ -3,6 +3,7 @@
namespace Icinga\Module\Director\Web\Controller;
use gipfl\Web\Widget\Hint;
+use Icinga\Module\Director\Auth\Permission;
use Icinga\Module\Director\DirectorObject\Automation\ExportInterface;
use Icinga\Module\Director\Exception\NestingError;
use Icinga\Module\Director\Objects\IcingaCommand;
@@ -10,6 +11,7 @@ use Icinga\Module\Director\Objects\IcingaObject;
use Icinga\Module\Director\Web\Controller\Extension\DirectorDb;
use Icinga\Module\Director\Web\Table\ApplyRulesTable;
use Icinga\Module\Director\Web\Table\ObjectsTable;
+use Icinga\Module\Director\Web\Table\ObjectsTableSetMembers;
use Icinga\Module\Director\Web\Table\TemplatesTable;
use Icinga\Module\Director\Web\Table\TemplateUsageTable;
use Icinga\Module\Director\Web\Tabs\ObjectTabs;
@@ -23,6 +25,8 @@ abstract class TemplateController extends CompatController
{
use DirectorDb;
+ use BranchHelper;
+
/** @var IcingaObject */
protected $template;
@@ -39,9 +43,29 @@ abstract class TemplateController extends CompatController
$template->getObjectName()
)->addBackToUsageLink($template);
- ObjectsTable::create($this->getType(), $this->db())
- ->setAuth($this->Auth())
+ ObjectsTable::create($this->getType(), $this->db(), $this->Auth())
+ ->setBranch($this->getBranch())
+ ->setBaseObjectUrl($this->getBaseObjectUrl())
+ ->filterTemplate($template, $this->getInheritance())
+ ->renderTo($this);
+ }
+
+ public function setmembersAction()
+ {
+ $template = $this->requireTemplate();
+ $plural = $this->getTranslatedPluralType();
+ $this
+ ->addSingleTab($plural)
+ ->setAutorefreshInterval(10)
+ ->addTitle(
+ $this->translate('%s in service sets based on %s'),
+ $plural,
+ $template->getObjectName()
+ )->addBackToUsageLink($template);
+
+ ObjectsTableSetMembers::create($this->getType(), $this->db(), $this->Auth())
->setBaseObjectUrl($this->getBaseObjectUrl())
+ ->setBranch($this->getBranch())
->filterTemplate($template, $this->getInheritance())
->renderTo($this);
}
@@ -60,6 +84,7 @@ abstract class TemplateController extends CompatController
ApplyRulesTable::create($type, $this->db())
->setBaseObjectUrl($this->getBaseObjectUrl())
+ ->setBranch($this->getBranch())
->filterTemplate($template, $this->params->get('inheritance', 'direct'))
->renderTo($this);
}
@@ -93,7 +118,7 @@ abstract class TemplateController extends CompatController
$this->actions()->add(
Link::create(
$this->translate('Back'),
- "director/${type}template/usage",
+ "director/{$type}template/usage",
['name' => $template->getObjectName()],
['class' => 'icon-left-big']
)
@@ -152,7 +177,7 @@ abstract class TemplateController extends CompatController
)]
));
}
- if ($auth->hasPermission('director/admin')) {
+ if ($auth->hasPermission(Permission::ADMIN)) {
$list->addItem(new FormattedString(
$this->translate('Create a new %s inheriting from this one'),
[Link::create(
@@ -188,7 +213,7 @@ abstract class TemplateController extends CompatController
try {
$this->content()->add(
- TemplateUsageTable::forTemplate($template)
+ TemplateUsageTable::forTemplate($template, $this->Auth(), $this->getBranch())
);
} catch (NestingError $e) {
$this->content()->add(Hint::error($e->getMessage()));
diff --git a/library/Director/Web/Form/CloneImportSourceForm.php b/library/Director/Web/Form/CloneImportSourceForm.php
index 0849dd4..46dc7a3 100644
--- a/library/Director/Web/Form/CloneImportSourceForm.php
+++ b/library/Director/Web/Form/CloneImportSourceForm.php
@@ -2,8 +2,10 @@
namespace Icinga\Module\Director\Web\Form;
+use gipfl\Web\Form;
use Icinga\Module\Director\Data\Exporter;
-use ipl\Html\Form;
+use Icinga\Module\Director\Data\ObjectImporter;
+use Icinga\Module\Director\Db;
use ipl\Html\FormDecorator\DdDtDecorator;
use gipfl\Translation\TranslationHelper;
use gipfl\IcingaWeb2\Url;
@@ -36,37 +38,25 @@ class CloneImportSourceForm extends Form
]);
}
- /**
- * @return \Icinga\Module\Director\Db
- */
- protected function getTargetDb()
- {
- return $this->source->getConnection();
- }
-
- /**
- * @throws \Icinga\Module\Director\Exception\DuplicateKeyException
- */
public function onSuccess()
{
- $db = $this->getTargetDb();
+ $db = $this->source->getConnection();
+ assert($db instanceof Db);
$export = (new Exporter($db))->export($this->source);
$newName = $this->getElement('source_name')->getValue();
$export->source_name = $newName;
- unset($export->originalId);
+ unset($export->uuid);
+
if (ImportSource::existsWithName($newName, $db)) {
$this->getElement('source_name')->addMessage('Name already exists');
}
- $this->newSource = ImportSource::import($export, $db);
+ $importer = new ObjectImporter($db);
+ $this->newSource = $importer->import(ImportSource::class, $export);
$this->newSource->store();
}
public function getSuccessUrl()
{
- if ($this->newSource === null) {
- return parent::getSuccessUrl();
- } else {
- return Url::fromPath('director/importsource', ['id' => $this->newSource->get('id')]);
- }
+ return Url::fromPath('director/importsource', ['id' => $this->newSource->get('id')]);
}
}
diff --git a/library/Director/Web/Form/CloneSyncRuleForm.php b/library/Director/Web/Form/CloneSyncRuleForm.php
index f90b593..ccd61ec 100644
--- a/library/Director/Web/Form/CloneSyncRuleForm.php
+++ b/library/Director/Web/Form/CloneSyncRuleForm.php
@@ -2,8 +2,10 @@
namespace Icinga\Module\Director\Web\Form;
+use gipfl\Web\Form;
use Icinga\Module\Director\Data\Exporter;
-use ipl\Html\Form;
+use Icinga\Module\Director\Data\ObjectImporter;
+use Icinga\Module\Director\Db;
use ipl\Html\FormDecorator\DdDtDecorator;
use gipfl\Translation\TranslationHelper;
use gipfl\IcingaWeb2\Url;
@@ -37,40 +39,30 @@ class CloneSyncRuleForm extends Form
}
/**
- * @return \Icinga\Module\Director\Db
- */
- protected function getTargetDb()
- {
- return $this->rule->getConnection();
- }
-
- /**
* @throws \Icinga\Exception\NotFoundError
* @throws \Icinga\Module\Director\Exception\DuplicateKeyException
*/
public function onSuccess()
{
- $db = $this->getTargetDb();
+ $db = $this->rule->getConnection();
+ assert($db instanceof Db);
$exporter = new Exporter($db);
$export = $exporter->export($this->rule);
$newName = $this->getValue('rule_name');
$export->rule_name = $newName;
- unset($export->originalId);
+ unset($export->uuid);
if (SyncRule::existsWithName($newName, $db)) {
$this->getElement('rule_name')->addMessage('Name already exists');
}
- $this->newRule = SyncRule::import($export, $db);
+ $importer = new ObjectImporter($db);
+ $this->newRule = $importer->import(SyncRule::class, $export);
$this->newRule->store();
}
public function getSuccessUrl()
{
- if ($this->newRule === null) {
- return parent::getSuccessUrl();
- } else {
- return Url::fromPath('director/syncrule', ['id' => $this->newRule->get('id')]);
- }
+ return Url::fromPath('director/syncrule', ['id' => $this->newRule->get('id')]);
}
}
diff --git a/library/Director/Web/Form/CsrfToken.php b/library/Director/Web/Form/CsrfToken.php
index 24edf88..f6c29ec 100644
--- a/library/Director/Web/Form/CsrfToken.php
+++ b/library/Director/Web/Form/CsrfToken.php
@@ -17,7 +17,7 @@ class CsrfToken
return false;
}
- list($seed, $token) = explode('|', $elementValue);
+ list($seed, $token) = explode('|', $token);
if (!is_numeric($seed)) {
return false;
diff --git a/library/Director/Web/Form/DbSelectorForm.php b/library/Director/Web/Form/DbSelectorForm.php
index 52fe5ea..8b4f432 100644
--- a/library/Director/Web/Form/DbSelectorForm.php
+++ b/library/Director/Web/Form/DbSelectorForm.php
@@ -69,7 +69,7 @@ class DbSelectorForm extends Form
$params = [];
}
- if (array_key_exists($name, $params)) {
+ if (is_array($params) && array_key_exists($name, $params)) {
return $params[$name];
}
diff --git a/library/Director/Web/Form/DirectorForm.php b/library/Director/Web/Form/DirectorForm.php
index 145be5b..36c0577 100644
--- a/library/Director/Web/Form/DirectorForm.php
+++ b/library/Director/Web/Form/DirectorForm.php
@@ -34,7 +34,7 @@ abstract class DirectorForm extends QuickForm
public static function load()
{
return new static([
- 'icingaModule' => Icinga::App()->getModuleManager()->getModule('director')
+ 'icingaModule' => Icinga::app()->getModuleManager()->getModule('director')
]);
}
diff --git a/library/Director/Web/Form/DirectorObjectForm.php b/library/Director/Web/Form/DirectorObjectForm.php
index b70bd7b..abbd4f0 100644
--- a/library/Director/Web/Form/DirectorObjectForm.php
+++ b/library/Director/Web/Form/DirectorObjectForm.php
@@ -5,6 +5,7 @@ namespace Icinga\Module\Director\Web\Form;
use Exception;
use gipfl\IcingaWeb2\Url;
use Icinga\Authentication\Auth;
+use Icinga\Module\Director\Auth\Permission;
use Icinga\Module\Director\Data\Db\DbObjectStore;
use Icinga\Module\Director\Db;
use Icinga\Module\Director\Data\Db\DbObject;
@@ -443,7 +444,7 @@ abstract class DirectorObjectForm extends DirectorForm
$this->setInheritedValue(
$el,
$object->getRelatedObjectName($k, $v),
- $origins->{"${k}_id"}
+ $origins->{"{$k}_id"}
);
}
}
@@ -540,7 +541,9 @@ abstract class DirectorObjectForm extends DirectorForm
'inherited_groups',
'applied_groups',
'users',
+ 'users_var',
'user_groups',
+ 'user_groups_var',
'apply_to',
'command_id', // Notification
'notification_interval',
@@ -788,7 +791,9 @@ abstract class DirectorObjectForm extends DirectorForm
return;
}
- $post = $values = $this->getRequest()->getPost();
+ /** @var array $post */
+ $post = $this->getRequest()->getPost();
+ $values = $post;
foreach ($post as $key => $value) {
if (preg_match('/^(.+?)_(\d+)__(MOVE_DOWN|MOVE_UP|REMOVE)$/', $key, $m)) {
@@ -1239,7 +1244,7 @@ abstract class DirectorObjectForm extends DirectorForm
if ($this->hasBeenSent()) {
$this->addError($this->translate('No template has been chosen'));
} else {
- if ($this->hasPermission('director/admin')) {
+ if ($this->hasPermission(Permission::ADMIN)) {
$html = $this->translate('Please define a related template first');
} else {
$html = $this->translate('No related template has been provided yet');
@@ -1274,7 +1279,7 @@ abstract class DirectorObjectForm extends DirectorForm
'required' => $required,
'spellcheck' => 'false',
'hideOptions' => $choiceNames,
- 'suggest' => "${type}templates",
+ 'suggest' => "{$type}templates",
// 'multiOptions' => $this->optionallyAddFromEnum($enum),
'sorted' => true,
'value' => $this->presetImports,
@@ -1516,6 +1521,7 @@ abstract class DirectorObjectForm extends DirectorForm
return [];
}
+ /** @var int|string $id */
$id = $object->get('id');
if (array_key_exists($id, $tpl)) {
diff --git a/library/Director/Web/Form/Element/DataFilter.php b/library/Director/Web/Form/Element/DataFilter.php
index adae07d..7beb651 100644
--- a/library/Director/Web/Form/Element/DataFilter.php
+++ b/library/Director/Web/Form/Element/DataFilter.php
@@ -2,6 +2,7 @@
namespace Icinga\Module\Director\Web\Form\Element;
+use gipfl\Json\JsonString;
use Icinga\Data\Filter\Filter;
use Icinga\Data\Filter\FilterChain;
use Icinga\Data\Filter\FilterExpression;
@@ -268,13 +269,13 @@ class DataFilter extends FormElement
return Filter::expression(
$entry['column'],
'=',
- json_encode(true)
+ $this->jsonEncode(true)
);
} elseif ($entry['sign'] === 'false') {
return Filter::expression(
$entry['column'],
'=',
- json_encode(false)
+ $this->jsonEncode(false)
);
} elseif ($entry['sign'] === 'in') {
if (array_key_exists('value', $entry)) {
@@ -291,13 +292,13 @@ class DataFilter extends FormElement
return Filter::expression(
$entry['column'],
'=',
- json_encode($value)
+ $this->jsonEncode($value)
);
} elseif ($entry['sign'] === 'contains') {
$value = array_key_exists('value', $entry) ? $entry['value'] : null;
return Filter::expression(
- json_encode($value),
+ $this->jsonEncode($value),
'=',
$entry['column']
);
@@ -307,11 +308,20 @@ class DataFilter extends FormElement
return Filter::expression(
$entry['column'],
$entry['sign'],
- json_encode($value)
+ $this->jsonEncode($value)
);
}
}
+ protected function jsonEncode($string)
+ {
+ return preg_replace(
+ ['/&/u', '/\|/u', '/!/u', '/=/u', '/>/u', '/</u'],
+ ['\u0026', '\u007c', '\u0021', '\u003d', '\u003e', '\u003c'],
+ JsonString::encode($string)
+ );
+ }
+
protected function entryAction($entry)
{
if (array_key_exists('action', $entry)) {
diff --git a/library/Director/Web/Form/Element/ExtensibleSet.php b/library/Director/Web/Form/Element/ExtensibleSet.php
index f3c968f..e443b06 100644
--- a/library/Director/Web/Form/Element/ExtensibleSet.php
+++ b/library/Director/Web/Form/Element/ExtensibleSet.php
@@ -28,7 +28,7 @@ class ExtensibleSet extends FormElement
if (! is_array($value)) {
throw new InvalidArgumentException(sprintf(
'ExtensibleSet expects to work with Arrays, got %s',
- var_export($value, 1)
+ var_export($value, true)
));
}
$value = array_filter($value, 'strlen');
diff --git a/library/Director/Web/Form/IcingaObjectFieldLoader.php b/library/Director/Web/Form/IcingaObjectFieldLoader.php
index c900edf..ae00855 100644
--- a/library/Director/Web/Form/IcingaObjectFieldLoader.php
+++ b/library/Director/Web/Form/IcingaObjectFieldLoader.php
@@ -613,7 +613,7 @@ class IcingaObjectFieldLoader
$fields = [];
/** @var HostFieldHook|ServiceFieldHook $hook */
$type = ucfirst($object->getShortTableName());
- foreach (Hook::all("Director\\${type}Field") as $hook) {
+ foreach (Hook::all("Director\\{$type}Field") as $hook) {
if ($hook->wants($object)) {
$id = $object->get('id');
$spec = $hook->getFieldSpec($object);
diff --git a/library/Director/Web/Form/IplElement/ExtensibleSetElement.php b/library/Director/Web/Form/IplElement/ExtensibleSetElement.php
index a4dbb20..b723d47 100644
--- a/library/Director/Web/Form/IplElement/ExtensibleSetElement.php
+++ b/library/Director/Web/Form/IplElement/ExtensibleSetElement.php
@@ -26,6 +26,8 @@ class ExtensibleSetElement extends BaseHtmlElement
private $description;
+ private $descriptions;
+
private $multiOptions;
private $validOptions;
@@ -109,7 +111,7 @@ class ExtensibleSetElement extends BaseHtmlElement
if (null !== $value && ! is_array($value)) {
throw new ProgrammingError(
'Got unexpected value, no array: %s',
- var_export($value, 1)
+ var_export($value, true)
);
}
@@ -323,7 +325,7 @@ class ExtensibleSetElement extends BaseHtmlElement
} else {
return \sprintf(
$this->translate('%s (not an Array!)'),
- \var_export($this->inherited, 1)
+ \var_export($this->inherited, true)
);
}
}
diff --git a/library/Director/Web/Form/QuickForm.php b/library/Director/Web/Form/QuickForm.php
index 91c8f00..6100ec9 100644
--- a/library/Director/Web/Form/QuickForm.php
+++ b/library/Director/Web/Form/QuickForm.php
@@ -620,6 +620,10 @@ abstract class QuickForm extends QuickBaseForm
$this->hasBeenSent = true;
} elseif ($req->isPost()) {
$post = $req->getPost();
+ if (! CsrfToken::isValid($post[self::CSRF])) {
+ throw new Exception('Invalid CSRF token provided');
+ }
+
$this->hasBeenSent = array_key_exists(self::ID, $post) &&
$post[self::ID] === $this->getName();
} else {
diff --git a/library/Director/Web/SelfService.php b/library/Director/Web/SelfService.php
index 33756b7..723cbf6 100644
--- a/library/Director/Web/SelfService.php
+++ b/library/Director/Web/SelfService.php
@@ -155,19 +155,6 @@ class SelfService
['class' => 'logfile'],
$wizard->renderIcinga4WindowsWizardCommand($key)
),
- Html::tag('h3', $this->translate('Icinga 2 Powershell Module')),
- Html::tag('p', Html::sprintf(
- $this->translate('In case you\'re using the legacy %s, please run:'),
- Html::tag('a', [
- 'href' => 'https://github.com/Icinga/icinga2-powershell-module',
- 'target' => '_blank',
- ], $this->translate('Icinga 2 Powershell Module'))
- )),
- Html::tag(
- 'pre',
- ['class' => 'logfile'],
- $wizard->renderPowershellModuleInstaller($key)
- ),
];
}
@@ -264,6 +251,7 @@ class SelfService
null,
['class' => 'icon-download', 'target' => '_blank']
),
+
Html::tag('p', null, $this->translate('Just download and run this script on your Linux Client Machine:')),
Html::tag('pre', $class, $wizard->renderLinuxInstaller())
]);
diff --git a/library/Director/Web/Table/ActivityLogTable.php b/library/Director/Web/Table/ActivityLogTable.php
index 5460bc2..a57b86e 100644
--- a/library/Director/Web/Table/ActivityLogTable.php
+++ b/library/Director/Web/Table/ActivityLogTable.php
@@ -2,14 +2,15 @@
namespace Icinga\Module\Director\Web\Table;
-use gipfl\Format\LocalTimeFormat;
+use DateTime;
use gipfl\IcingaWeb2\Link;
-use gipfl\IcingaWeb2\Table\ZfQueryBasedTable;
+use Icinga\Module\Director\Auth\Permission;
use Icinga\Module\Director\Util;
+use IntlDateFormatter;
use ipl\Html\Html;
use ipl\Html\HtmlElement;
-class ActivityLogTable extends ZfQueryBasedTable
+class ActivityLogTable extends IntlZfQueryBasedTable
{
protected $filters = [];
@@ -27,9 +28,6 @@ class ActivityLogTable extends ZfQueryBasedTable
'object_type',
];
- /** @var LocalTimeFormat */
- protected $timeFormat;
-
protected $ranges = [];
/** @var ?object */
@@ -44,7 +42,6 @@ class ActivityLogTable extends ZfQueryBasedTable
public function __construct($db)
{
parent::__construct($db);
- $this->timeFormat = new LocalTimeFormat();
}
public function assemble()
@@ -96,7 +93,9 @@ class ActivityLogTable extends ZfQueryBasedTable
if (! $this->hasObjectFilter) {
$columns[] = $this->makeRangeInfo($row->id);
}
- $columns[] = $this::td($this->timeFormat->getTime($row->ts_change_time));
+
+
+ $columns[] = $this::td($this->getTime($row->ts_change_time));
return $this::tr($columns)->addAttributes(['class' => $action]);
}
@@ -107,7 +106,7 @@ class ActivityLogTable extends ZfQueryBasedTable
*/
protected function renderDayIfNew($timestamp)
{
- $day = $this->getDateFormatter()->getFullDay($timestamp);
+ $day = $this->getDateFormatter()->format((new DateTime())->setTimestamp($timestamp));
if ($this->lastDay !== $day) {
$this->nextHeader()->add(
@@ -206,7 +205,7 @@ class ActivityLogTable extends ZfQueryBasedTable
$type = substr($type, 7);
}
- if (Util::hasPermission('director/showconfig')) {
+ if (Util::hasPermission(Permission::SHOW_CONFIG)) {
// Later on replacing, service_set -> serviceset
// multi column key :(
diff --git a/library/Director/Web/Table/ApplyRulesTable.php b/library/Director/Web/Table/ApplyRulesTable.php
index a861bac..a5379b5 100644
--- a/library/Director/Web/Table/ApplyRulesTable.php
+++ b/library/Director/Web/Table/ApplyRulesTable.php
@@ -6,6 +6,7 @@ use Icinga\Authentication\Auth;
use Icinga\Data\Filter\Filter;
use Icinga\Exception\IcingaException;
use Icinga\Module\Director\Db;
+use Icinga\Module\Director\Db\DbSelectParenthesis;
use Icinga\Module\Director\Db\DbUtil;
use Icinga\Module\Director\Db\IcingaObjectFilterHelper;
use Icinga\Module\Director\IcingaConfig\AssignRenderer;
@@ -20,6 +21,8 @@ use Zend_Db_Select as ZfSelect;
class ApplyRulesTable extends ZfQueryBasedTable
{
+ use TableWithBranchSupport;
+
protected $searchColumns = [
'o.object_name',
'o.assign_filter',
@@ -94,10 +97,14 @@ class ApplyRulesTable extends ZfQueryBasedTable
// NOT (YET) static::td($this->createActionLinks($row))->setSeparator(' ')
]);
+ $classes = $this->getRowClasses($row);
+
if ($row->disabled === 'y') {
- $tr->getAttributes()->add('class', 'disabled');
+ $classes[] = 'disabled';
}
+ $tr->getAttributes()->add('class', $classes);
+
return $tr;
}
@@ -117,7 +124,8 @@ class ApplyRulesTable extends ZfQueryBasedTable
$this->getQuery(),
$template,
'o',
- $inheritance
+ $inheritance,
+ $this->branchUuid
);
return $this;
@@ -146,7 +154,7 @@ class ApplyRulesTable extends ZfQueryBasedTable
$links = [];
$links[] = Link::create(
Icon::create('sitemap'),
- "${baseUrl}template/applytargets",
+ "{$baseUrl}template/applytargets",
['id' => $row->id],
['title' => $this->translate('Show affected Objects')]
);
@@ -196,6 +204,15 @@ class ApplyRulesTable extends ZfQueryBasedTable
return FilterRenderer::applyToQuery($filter, $query);
}
+ protected function getRowClasses($row)
+ {
+ // TODO: remove isset, to figure out where it is missing
+ if (isset($row->branch_uuid) && $row->branch_uuid !== null) {
+ return ['branch_modified'];
+ }
+ return [];
+ }
+
/**
* @return IcingaObject
@@ -216,6 +233,7 @@ class ApplyRulesTable extends ZfQueryBasedTable
'id' => 'o.id',
'uuid' => 'o.uuid',
'object_name' => 'o.object_name',
+ 'object_type' => 'o.object_type',
'disabled' => 'o.disabled',
'assign_filter' => 'o.assign_filter',
'apply_for' => '(NULL)',
@@ -224,17 +242,92 @@ class ApplyRulesTable extends ZfQueryBasedTable
if ($table === 'icinga_service') {
$columns['apply_for'] = 'o.apply_for';
}
+
+ $conn = $this->connection();
$query = $this->db()->select()->from(
['o' => $table],
$columns
- )->where(
- "object_type = 'apply'"
)->order('o.object_name');
- if ($this->type === 'service') {
- $query->where('service_set_id IS NULL');
+ if ($this->branchUuid) {
+ $columns = $this->branchifyColumns($columns);
+ $columns['branch_uuid'] = 'bo.branch_uuid';
+ if ($conn->isPgsql()) {
+ $columns['imports'] = 'CONCAT(\'[\', ARRAY_TO_STRING(ARRAY_AGG'
+ . '(CONCAT(\'"\', sub_o.object_name, \'"\')), \',\'), \']\')';
+ } else {
+ $columns['imports'] = 'CONCAT(\'[\', '
+ . 'GROUP_CONCAT(CONCAT(\'"\', sub_o.object_name, \'"\')), \']\')';
+ }
+
+ $this->stripSearchColumnAliases();
+
+ $query->reset('columns');
+ $right = clone($query);
+
+ $query->columns($columns)
+ ->joinLeft(
+ ['oi' => $table . '_inheritance'],
+ 'o.id = oi.' . $this->getType() . '_id',
+ []
+ )->joinLeft(
+ ['sub_o' => $table],
+ 'sub_o.id = oi.parent_' . $this->getType() . '_id',
+ []
+ )->group(['o.id', 'bo.uuid', 'bo.branch_uuid']);
+
+ $query->joinLeft(
+ ['bo' => "branched_$table"],
+ // TODO: PgHexFunc
+ $this->db()->quoteInto(
+ 'bo.uuid = o.uuid AND bo.branch_uuid = ?',
+ DbUtil::quoteBinaryLegacy($this->branchUuid->getBytes(), $this->db())
+ ),
+ []
+ )->where("(bo.branch_deleted IS NULL OR bo.branch_deleted = 'n')");
+
+ if ($this->type === 'service') {
+ $query->where('o.service_set_id IS NULL AND bo.service_set IS NULL');
+ }
+
+ $columns['imports'] = 'bo.imports';
+
+ $right->columns($columns)
+ ->joinRight(
+ ['bo' => "branched_$table"],
+ 'bo.uuid = o.uuid',
+ []
+ )
+ ->where('o.uuid IS NULL')
+ ->where('bo.branch_uuid = ?', $conn->quoteBinary($this->branchUuid->getBytes()));
+
+ $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);
+ } else {
+ if ($this->type === 'service') {
+ $query->where('service_set_id IS NULL');
+ }
}
+ $query->where(
+ "object_type = 'apply'"
+ );
+
+ $this->applyRestrictions($query);
+
return $this->applyRestrictions($query);
}
+
+ /**
+ * @return Db
+ */
+ public function connection()
+ {
+ return parent::connection();
+ }
}
diff --git a/library/Director/Web/Table/BasketSnapshotTable.php b/library/Director/Web/Table/BasketSnapshotTable.php
index 08f808a..6fe0e16 100644
--- a/library/Director/Web/Table/BasketSnapshotTable.php
+++ b/library/Director/Web/Table/BasketSnapshotTable.php
@@ -4,13 +4,12 @@ namespace Icinga\Module\Director\Web\Table;
use ipl\Html\Html;
use gipfl\IcingaWeb2\Link;
-use gipfl\IcingaWeb2\Table\ZfQueryBasedTable;
use Icinga\Date\DateFormatter;
use Icinga\Module\Director\Core\Json;
use Icinga\Module\Director\DirectorObject\Automation\Basket;
use RuntimeException;
-class BasketSnapshotTable extends ZfQueryBasedTable
+class BasketSnapshotTable extends IntlZfQueryBasedTable
{
use DbHelper;
@@ -67,7 +66,7 @@ class BasketSnapshotTable extends ZfQueryBasedTable
if (! is_object($summary) && ! is_array($summary)) {
throw new RuntimeException(sprintf(
'Got invalid basket summary: %s ',
- var_export($summary, 1)
+ var_export($summary, true)
));
}
diff --git a/library/Director/Web/Table/BranchActivityTable.php b/library/Director/Web/Table/BranchActivityTable.php
index e7131ef..cbf940d 100644
--- a/library/Director/Web/Table/BranchActivityTable.php
+++ b/library/Director/Web/Table/BranchActivityTable.php
@@ -2,15 +2,14 @@
namespace Icinga\Module\Director\Web\Table;
-use gipfl\Format\LocalTimeFormat;
+use Icinga\Module\Director\Auth\Permission;
use Icinga\Module\Director\Db;
use Icinga\Module\Director\Db\Branch\BranchActivity;
use Icinga\Module\Director\Util;
use gipfl\IcingaWeb2\Link;
-use gipfl\IcingaWeb2\Table\ZfQueryBasedTable;
use Ramsey\Uuid\UuidInterface;
-class BranchActivityTable extends ZfQueryBasedTable
+class BranchActivityTable extends IntlZfQueryBasedTable
{
protected $extraParams = [];
@@ -20,16 +19,12 @@ class BranchActivityTable extends ZfQueryBasedTable
/** @var ?UuidInterface */
protected $objectUuid;
- /** @var LocalTimeFormat */
- protected $timeFormat;
-
protected $linkToObject = true;
public function __construct(UuidInterface $branchUuid, $db, UuidInterface $objectUuid = null)
{
$this->branchUuid = $branchUuid;
$this->objectUuid = $objectUuid;
- $this->timeFormat = new LocalTimeFormat();
parent::__construct($db);
}
@@ -45,7 +40,7 @@ class BranchActivityTable extends ZfQueryBasedTable
$activity = BranchActivity::fromDbRow($row);
return $this::tr([
$this::td($this->makeBranchLink($activity))->setSeparator(' '),
- $this::td($this->timeFormat->getTime($ts))
+ $this::td($this->getTime($ts))
])->addAttributes(['class' => ['action-' . $activity->getAction(), 'branched']]);
}
@@ -75,7 +70,7 @@ class BranchActivityTable extends ZfQueryBasedTable
{
$type = preg_replace('/^icinga_/', '', $activity->getObjectTable());
- if (Util::hasPermission('director/showconfig')) {
+ if (Util::hasPermission(Permission::SHOW_CONFIG)) {
// Later on replacing, service_set -> serviceset
return [
'[' . $activity->getAuthor() . ']',
diff --git a/library/Director/Web/Table/ChoicesTable.php b/library/Director/Web/Table/ChoicesTable.php
index 4ba2460..9574520 100644
--- a/library/Director/Web/Table/ChoicesTable.php
+++ b/library/Director/Web/Table/ChoicesTable.php
@@ -44,7 +44,7 @@ class ChoicesTable extends ZfQueryBasedTable
public function renderRow($row)
{
$type = $this->getType();
- $url = Url::fromPath("director/templatechoice/${type}", [
+ $url = Url::fromPath("director/templatechoice/{$type}", [
'name' => $row->object_name
]);
@@ -56,7 +56,7 @@ class ChoicesTable extends ZfQueryBasedTable
protected function prepareQuery()
{
$type = $this->getType();
- $table = "icinga_${type}_template_choice";
+ $table = "icinga_{$type}_template_choice";
return $this->db()
->select()
->from(['o' => $table], 'object_name')
diff --git a/library/Director/Web/Table/CustomvarTable.php b/library/Director/Web/Table/CustomvarTable.php
index f9a3844..1aead19 100644
--- a/library/Director/Web/Table/CustomvarTable.php
+++ b/library/Director/Web/Table/CustomvarTable.php
@@ -91,11 +91,11 @@ class CustomvarTable extends ZfQueryBasedTable
$columns["cnt_$type"] = 'COUNT(*)';
$columns["distinct_$type"] = 'COUNT(DISTINCT varvalue)';
return $db->select()->from(
- ['v' => "icinga_${type}_var"],
+ ['v' => "icinga_{$type}_var"],
$columns
)->join(
- ['o' => "icinga_${type}"],
- "o.id = v.${type}_id",
+ ['o' => "icinga_{$type}"],
+ "o.id = v.{$type}_id",
[]
)->where('o.object_type != ?', 'external_object')->group('varname');
}
diff --git a/library/Director/Web/Table/CustomvarVariantsTable.php b/library/Director/Web/Table/CustomvarVariantsTable.php
index 80fca70..8447e36 100644
--- a/library/Director/Web/Table/CustomvarVariantsTable.php
+++ b/library/Director/Web/Table/CustomvarVariantsTable.php
@@ -108,11 +108,11 @@ class CustomvarVariantsTable extends ZfQueryBasedTable
$columns["cnt_$type"] = 'COUNT(*)';
$columns['format'] = 'v.format';
return $db->select()->from(
- ['v' => "icinga_${type}_var"],
+ ['v' => "icinga_{$type}_var"],
$columns
)->join(
- ['o' => "icinga_${type}"],
- "o.id = v.${type}_id",
+ ['o' => "icinga_{$type}"],
+ "o.id = v.{$type}_id",
[]
)->where(
'v.varname = ?',
diff --git a/library/Director/Web/Table/DatafieldTable.php b/library/Director/Web/Table/DatafieldTable.php
index 4b321d7..0062626 100644
--- a/library/Director/Web/Table/DatafieldTable.php
+++ b/library/Director/Web/Table/DatafieldTable.php
@@ -5,12 +5,13 @@ namespace Icinga\Module\Director\Web\Table;
use gipfl\IcingaWeb2\Link;
use gipfl\IcingaWeb2\Table\ZfQueryBasedTable;
use Zend_Db_Adapter_Abstract as ZfDbAdapter;
+use Zend_Db_Expr as DbExpr;
use Zend_Db_Select as ZfDbSelect;
class DatafieldTable extends ZfQueryBasedTable
{
protected $searchColumns = [
- 'df.varname',
+ 'lc_varname',
'df.caption',
];
@@ -19,6 +20,7 @@ class DatafieldTable extends ZfQueryBasedTable
return [
'id' => 'df.id',
'varname' => 'df.varname',
+ 'lc_varname' => new DbExpr('LOWER(df.varname)'),
'caption' => 'df.caption',
'description' => 'df.description',
'datatype' => 'df.datatype',
@@ -88,6 +90,15 @@ class DatafieldTable extends ZfQueryBasedTable
)->group('df.id')->group('df.varname')->group('dfc.category_name')->order('caption ASC');
}
+ public function search($search)
+ {
+ if ($search !== null) {
+ $search = strtolower($search);
+ }
+
+ return parent::search($search);
+ }
+
/**
* @param $type
* @param ZfDbAdapter $db
@@ -96,7 +107,7 @@ class DatafieldTable extends ZfQueryBasedTable
*/
protected function makeDatafieldSub($type, ZfDbAdapter $db)
{
- return $db->select()->from("icinga_${type}_field", [
+ return $db->select()->from("icinga_{$type}_field", [
'cnt' => 'COUNT(*)',
'datafield_id'
])->group('datafield_id');
@@ -110,7 +121,7 @@ class DatafieldTable extends ZfQueryBasedTable
*/
protected function makeVarSub($type, ZfDbAdapter $db)
{
- return $db->select()->from("icinga_${type}_var", [
+ return $db->select()->from("icinga_{$type}_var", [
'cnt' => 'COUNT(*)',
'varname'
])->group('varname');
diff --git a/library/Director/Web/Table/DependencyTemplateUsageTable.php b/library/Director/Web/Table/DependencyTemplateUsageTable.php
index d7537c5..2c1de50 100644
--- a/library/Director/Web/Table/DependencyTemplateUsageTable.php
+++ b/library/Director/Web/Table/DependencyTemplateUsageTable.php
@@ -2,6 +2,8 @@
namespace Icinga\Module\Director\Web\Table;
+use Icinga\Module\Director\Db;
+
class DependencyTemplateUsageTable extends TemplateUsageTable
{
public function getTypes()
@@ -12,11 +14,15 @@ class DependencyTemplateUsageTable extends TemplateUsageTable
];
}
- protected function getTypeSummaryDefinitions()
+ protected function getSummaryTables(string $templateType, Db $connection)
{
return [
- 'templates' => $this->getSummaryLine('template'),
- 'applyrules' => $this->getSummaryLine('apply'),
+ 'templates' => TemplatesTable::create(
+ $templateType,
+ $connection
+ ),
+ 'applyrules' => ApplyRulesTable::create($templateType, $connection)
+ ->setBranchUuid($this->branchUuid)
];
}
}
diff --git a/library/Director/Web/Table/DeploymentLogTable.php b/library/Director/Web/Table/DeploymentLogTable.php
index 2d5cb94..4849218 100644
--- a/library/Director/Web/Table/DeploymentLogTable.php
+++ b/library/Director/Web/Table/DeploymentLogTable.php
@@ -3,10 +3,9 @@
namespace Icinga\Module\Director\Web\Table;
use gipfl\IcingaWeb2\Link;
-use gipfl\IcingaWeb2\Table\ZfQueryBasedTable;
use Icinga\Date\DateFormatter;
-class DeploymentLogTable extends ZfQueryBasedTable
+class DeploymentLogTable extends IntlZfQueryBasedTable
{
use DbHelper;
diff --git a/library/Director/Web/Table/GroupMemberTable.php b/library/Director/Web/Table/GroupMemberTable.php
index b0814ad..c24c6af 100644
--- a/library/Director/Web/Table/GroupMemberTable.php
+++ b/library/Director/Web/Table/GroupMemberTable.php
@@ -109,7 +109,7 @@ class GroupMemberTable extends ZfQueryBasedTable
];
}
- $url = Url::fromPath("director/${type}", $params);
+ $url = Url::fromPath("director/{$type}", $params);
$tr = $this::tr();
@@ -163,7 +163,7 @@ class GroupMemberTable extends ZfQueryBasedTable
'o.id',
'o.object_type',
'o.object_name',
- 'membership_type' => "CASE WHEN go.${type}_id IS NULL THEN 'apply' ELSE 'direct' END"
+ 'membership_type' => "CASE WHEN go.{$type}_id IS NULL THEN 'apply' ELSE 'direct' END"
];
if ($this->group === null) {
@@ -176,19 +176,19 @@ class GroupMemberTable extends ZfQueryBasedTable
}
$query = $this->db()->select()->from(
- ['gro' => "icinga_${type}group_${type}_resolved"],
+ ['gro' => "icinga_{$type}group_{$type}_resolved"],
$columns
)->join(
- ['o' => "icinga_${type}"],
- "o.id = gro.${type}_id",
+ ['o' => "icinga_{$type}"],
+ "o.id = gro.{$type}_id",
[]
)->join(
- ['g' => "icinga_${type}group"],
- "gro.${type}group_id = g.id",
+ ['g' => "icinga_{$type}group"],
+ "gro.{$type}group_id = g.id",
[]
)->joinLeft(
- ['go' => "icinga_${type}group_${type}"],
- "go.${type}_id = o.id AND go.${type}group_id = g.id",
+ ['go' => "icinga_{$type}group_{$type}"],
+ "go.{$type}_id = o.id AND go.{$type}group_id = g.id",
[]
)->order('o.object_name');
diff --git a/library/Director/Web/Table/HostTemplateUsageTable.php b/library/Director/Web/Table/HostTemplateUsageTable.php
index 2d1ee2f..672691f 100644
--- a/library/Director/Web/Table/HostTemplateUsageTable.php
+++ b/library/Director/Web/Table/HostTemplateUsageTable.php
@@ -11,12 +11,4 @@ class HostTemplateUsageTable extends TemplateUsageTable
'objects' => $this->translate('Objects'),
];
}
-
- protected function getTypeSummaryDefinitions()
- {
- return [
- 'templates' => $this->getSummaryLine('template'),
- 'objects' => $this->getSummaryLine('object'),
- ];
- }
}
diff --git a/library/Director/Web/Table/IcingaServiceSetServiceTable.php b/library/Director/Web/Table/IcingaServiceSetServiceTable.php
index c205e66..2c3dbc4 100644
--- a/library/Director/Web/Table/IcingaServiceSetServiceTable.php
+++ b/library/Director/Web/Table/IcingaServiceSetServiceTable.php
@@ -12,6 +12,7 @@ use Icinga\Module\Director\Objects\IcingaServiceSet;
use gipfl\IcingaWeb2\Link;
use gipfl\IcingaWeb2\Table\ZfQueryBasedTable;
use gipfl\IcingaWeb2\Url;
+use Ramsey\Uuid\Uuid;
class IcingaServiceSetServiceTable extends ZfQueryBasedTable
{
@@ -122,9 +123,12 @@ class IcingaServiceSetServiceTable extends ZfQueryBasedTable
];
$url = 'director/host/servicesetservice';
} else {
+ if (is_resource($row->uuid)) {
+ $row->uuid =stream_get_contents($row->uuid);
+ }
+
$params = [
- 'name' => $row->service,
- 'set' => $row->service_set
+ 'uuid' => Uuid::fromBytes($row->uuid)->toString(),
];
$url = 'director/service';
}
@@ -194,14 +198,28 @@ class IcingaServiceSetServiceTable extends ZfQueryBasedTable
$connection = $this->connection();
assert($connection instanceof Db);
$builder = new ServiceSetQueryBuilder($connection, $this->branchUuid);
- return $builder->selectServicesForSet($this->set)->limit(100);
+ $query = $builder->selectServicesForSet($this->set);
+ $alias = $this->branchUuid ? 'u' : 'o';
+
+ if ($this->affectedHost) {
+ if ($hostId = $this->affectedHost->get('id')) {
+ $query->joinLeft(
+ ['hsb' => 'icinga_host_service_blacklist'],
+ $this->db()->quoteInto("$alias.id = hsb.service_id AND hsb.host_id = ?", $hostId),
+ []
+ )->columns([
+ 'blacklisted' => "CASE WHEN hsb.service_id IS NULL THEN 'n' ELSE 'y' END"
+ ]);
+ }
+ }
+
+ return $query->limit(100);
}
protected function createFakeRemoveLinkForReadonlyView()
{
return Html::tag('span', [
- 'class' => 'icon-paste',
- 'style' => 'float: right; font-weight: normal',
+ 'class' => ['icon-paste', 'seviceset-obj-link'],
], $this->host->getObjectName());
}
@@ -209,8 +227,7 @@ class IcingaServiceSetServiceTable extends ZfQueryBasedTable
{
$hostname = $host->getObjectName();
return Link::create($hostname, 'director/host/services', ['name' => $hostname], [
- 'class' => 'icon-paste',
- 'style' => 'float: right; font-weight: normal',
+ 'class' => ['icon-paste', 'seviceset-obj-link'],
'data-base-target' => '_next',
'title' => sprintf(
$this->translate('This set has been inherited from %s'),
diff --git a/library/Director/Web/Table/IntlZfQueryBasedTable.php b/library/Director/Web/Table/IntlZfQueryBasedTable.php
new file mode 100644
index 0000000..81ef14c
--- /dev/null
+++ b/library/Director/Web/Table/IntlZfQueryBasedTable.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Icinga\Module\Director\Web\Table;
+
+use DateTime;
+use gipfl\IcingaWeb2\Table\ZfQueryBasedTable;
+use IntlDateFormatter;
+use Locale;
+
+abstract class IntlZfQueryBasedTable extends ZfQueryBasedTable
+{
+ protected function getDateFormatter()
+ {
+ return (new IntlDateFormatter(
+ Locale::getDefault(),
+ IntlDateFormatter::FULL,
+ IntlDateFormatter::NONE
+ ));
+ }
+
+ /**
+ * @param int $timestamp
+ */
+ protected function renderDayIfNew($timestamp)
+ {
+ $day = $this->getDateFormatter()->format((new DateTime())->setTimestamp($timestamp));
+
+ if ($this->lastDay !== $day) {
+ $this->nextHeader()->add(
+ $this::th($day, [
+ 'colspan' => 2,
+ 'class' => 'table-header-day'
+ ])
+ );
+
+ $this->lastDay = $day;
+ $this->nextBody();
+ }
+ }
+
+ protected function getTime(int $timeStamp)
+ {
+ $timeFormatter = $this->getDateFormatter();
+
+ $timeFormatter->setPattern(
+ in_array(Locale::getDefault(), ['en_US', 'en_US.UTF-8']) ? 'h:mm:ss a': 'H:mm:ss'
+ );
+
+ return $timeFormatter->format((new DateTime())->setTimestamp($timeStamp));
+ }
+}
diff --git a/library/Director/Web/Table/NotificationTemplateUsageTable.php b/library/Director/Web/Table/NotificationTemplateUsageTable.php
index da411a3..d8cd3d8 100644
--- a/library/Director/Web/Table/NotificationTemplateUsageTable.php
+++ b/library/Director/Web/Table/NotificationTemplateUsageTable.php
@@ -2,6 +2,8 @@
namespace Icinga\Module\Director\Web\Table;
+use Icinga\Module\Director\Db;
+
class NotificationTemplateUsageTable extends TemplateUsageTable
{
public function getTypes()
@@ -12,11 +14,15 @@ class NotificationTemplateUsageTable extends TemplateUsageTable
];
}
- protected function getTypeSummaryDefinitions()
+ protected function getSummaryTables(string $templateType, Db $connection)
{
return [
- 'templates' => $this->getSummaryLine('template'),
- 'applyrules' => $this->getSummaryLine('apply', 'o.host_id IS NULL'),
+ 'templates' => TemplatesTable::create(
+ $templateType,
+ $connection
+ ),
+ 'applyrules' => ApplyRulesTable::create($templateType, $connection)
+ ->setBranchUuid($this->branchUuid)
];
}
}
diff --git a/library/Director/Web/Table/ObjectSetTable.php b/library/Director/Web/Table/ObjectSetTable.php
index 2773841..4df9bdd 100644
--- a/library/Director/Web/Table/ObjectSetTable.php
+++ b/library/Director/Web/Table/ObjectSetTable.php
@@ -2,7 +2,9 @@
namespace Icinga\Module\Director\Web\Table;
+use gipfl\IcingaWeb2\Zf1\Db\FilterRenderer;
use Icinga\Authentication\Auth;
+use Icinga\Data\Filter\Filter;
use Icinga\Module\Director\Db;
use gipfl\IcingaWeb2\Link;
use gipfl\IcingaWeb2\Table\ZfQueryBasedTable;
@@ -28,6 +30,8 @@ class ObjectSetTable extends ZfQueryBasedTable
/** @var Auth */
private $auth;
+ protected $queries = [];
+
public static function create($type, Db $db, Auth $auth)
{
$table = new static($db);
@@ -53,7 +57,7 @@ class ObjectSetTable extends ZfQueryBasedTable
'uuid' => Uuid::fromBytes(Db\DbUtil::binaryResult($row->uuid))->toString(),
];
- $url = Url::fromPath("director/${type}set", $params);
+ $url = Url::fromPath("director/{$type}set", $params);
$classes = $this->getRowClasses($row);
$tr = static::tr([
@@ -85,7 +89,7 @@ class ObjectSetTable extends ZfQueryBasedTable
{
$type = $this->getType();
- $table = "icinga_${type}_set";
+ $table = "icinga_{$type}_set";
$columns = [
'id' => 'os.id',
'uuid' => 'os.uuid',
@@ -106,15 +110,15 @@ class ObjectSetTable extends ZfQueryBasedTable
['os' => $table],
$columns
)->joinLeft(
- ['o' => "icinga_${type}"],
- "o.${type}_set_id = os.id",
+ ['o' => "icinga_{$type}"],
+ "o.{$type}_set_id = os.id",
[]
);
$nameFilter = new FilterByNameRestriction(
$this->connection(),
$this->auth,
- "${type}_set"
+ "{$type}_set"
);
$nameFilter->applyToQuery($query, 'os');
/** @var Db $conn */
@@ -145,7 +149,20 @@ class ObjectSetTable extends ZfQueryBasedTable
$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');
}
-
+ $right->joinLeft(
+ ['bo' => "branched_icinga_{$type}"],
+ "bo.{$type}_set = bos.object_name",
+ []
+ )->group(['bo.object_name', 'o.object_name']);
+ $query->joinLeft(
+ ['bo' => "branched_icinga_{$type}"],
+ "bo.{$type}_set = bos.object_name",
+ []
+ )->group(['bo.object_name', 'o.object_name']);
+ $this->queries = [
+ $query,
+ $right
+ ];
$query = $this->db()->select()->union([
'l' => new DbSelectParenthesis($query),
'r' => new DbSelectParenthesis($right),
@@ -168,16 +185,16 @@ class ObjectSetTable extends ZfQueryBasedTable
->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",
+ // ['osi' => "icinga_{$type}_set_inheritance"],
+ // "osi.parent_{$type}_set_id = os.id",
// []
// )->joinLeft(
- // ['oso' => "icinga_${type}_set"],
- // "oso.id = oso.${type}_set_id",
+ // ['oso' => "icinga_{$type}_set"],
+ // "oso.id = oso.{$type}_set_id",
// []
// );
// 'count_hosts' => 'COUNT(DISTINCT oso.id)',
@@ -196,11 +213,40 @@ class ObjectSetTable extends ZfQueryBasedTable
->group('os.assign_filter')
->group('os.description');
};
+ $this->queries = [$query];
}
return $query;
}
+ public function search($search)
+ {
+ if (! empty($search)) {
+ $columns = $this->getSearchColumns();
+ if (strpos($search, ' ') === false) {
+ $filter = Filter::matchAny();
+ foreach ($columns as $column) {
+ $filter->addFilter(Filter::expression($column, '=', "*$search*"));
+ }
+ } else {
+ $filter = Filter::matchAll();
+ foreach (explode(' ', $search) as $s) {
+ $sub = Filter::matchAny();
+ foreach ($columns as $column) {
+ $sub->addFilter(Filter::expression($column, '=', "*$s*"));
+ }
+ $filter->addFilter($sub);
+ }
+ }
+
+ foreach ($this->queries as $query) {
+ FilterRenderer::applyToQuery($filter, $query);
+ }
+ }
+
+ return $this;
+ }
+
/**
* @return Db
*/
diff --git a/library/Director/Web/Table/ObjectsTable.php b/library/Director/Web/Table/ObjectsTable.php
index 792cb6d..4ad1166 100644
--- a/library/Director/Web/Table/ObjectsTable.php
+++ b/library/Director/Web/Table/ObjectsTable.php
@@ -14,6 +14,7 @@ use gipfl\IcingaWeb2\Link;
use gipfl\IcingaWeb2\Table\ZfQueryBasedTable;
use gipfl\IcingaWeb2\Url;
use Ramsey\Uuid\Uuid;
+use Zend_Db_Adapter_Pdo_Pgsql;
use Zend_Db_Select as ZfSelect;
class ObjectsTable extends ZfQueryBasedTable
@@ -50,12 +51,18 @@ class ObjectsTable extends ZfQueryBasedTable
/** @var Auth */
private $auth;
+ public function __construct($db, Auth $auth)
+ {
+ $this->auth = $auth;
+ parent::__construct($db);
+ }
+
/**
* @param $type
* @param Db $db
* @return static
*/
- public static function create($type, Db $db)
+ public static function create($type, Db $db, Auth $auth)
{
$class = __NAMESPACE__ . '\\ObjectsTable' . ucfirst($type);
if (! class_exists($class)) {
@@ -63,7 +70,7 @@ class ObjectsTable extends ZfQueryBasedTable
}
/** @var static $table */
- $table = new $class($db);
+ $table = new $class($db, $auth);
$table->type = $type;
return $table;
}
@@ -84,20 +91,6 @@ class ObjectsTable extends ZfQueryBasedTable
return $this;
}
- /**
- * @return Auth
- */
- public function getAuth()
- {
- return $this->auth;
- }
-
- public function setAuth(Auth $auth)
- {
- $this->auth = $auth;
- return $this;
- }
-
public function filterObjectType($type)
{
$this->filterObjectType = $type;
@@ -124,11 +117,17 @@ class ObjectsTable extends ZfQueryBasedTable
IcingaObject $template,
$inheritance = Db\IcingaObjectFilterHelper::INHERIT_DIRECT
) {
+ if ($this->branchUuid) {
+ $tableAlias = 'u';
+ } else {
+ $tableAlias = 'o';
+ }
IcingaObjectFilterHelper::filterByTemplate(
$this->getQuery(),
$template,
- 'o',
- $inheritance
+ $tableAlias,
+ $inheritance,
+ $this->branchUuid
);
return $this;
@@ -142,7 +141,7 @@ class ObjectsTable extends ZfQueryBasedTable
protected function renderObjectNameColumn($row)
{
$type = $this->baseObjectUrl;
- $url = Url::fromPath("director/${type}", [
+ $url = Url::fromPath("director/{$type}", [
'uuid' => Uuid::fromBytes($row->uuid)->toString()
]);
@@ -227,11 +226,10 @@ class ObjectsTable extends ZfQueryBasedTable
{
/** @var Db $db */
$db = $this->connection();
- $auth = $this->getAuth();
return [
- new HostgroupRestriction($db, $auth),
- new FilterByNameRestriction($db, $auth, $this->getDummyObject()->getShortTableName())
+ new HostgroupRestriction($db, $this->auth),
+ new FilterByNameRestriction($db, $this->auth, $this->getDummyObject()->getShortTableName())
];
}
@@ -279,7 +277,39 @@ class ObjectsTable extends ZfQueryBasedTable
$conn->quoteBinary($this->branchUuid->getBytes())
),
[]
- )->where("(bo.branch_deleted IS NULL OR bo.branch_deleted = 'n')");
+ );
+
+ // keep the imported templates as columns
+ $leftColumns = $columns;
+ $rightColumns = $columns;
+
+ if ($this->db() instanceof Zend_Db_Adapter_Pdo_Pgsql) {
+ $leftColumns['imports'] = 'CONCAT(\'[\', ARRAY_TO_STRING(ARRAY_AGG'
+ . '(CONCAT(\'"\', sub_o.object_name, \'"\')), \',\'), \']\')';
+ } else {
+ $leftColumns['imports'] = 'CONCAT(\'[\', '
+ . 'GROUP_CONCAT(CONCAT(\'"\', sub_o.object_name, \'"\')), \']\')';
+ }
+
+ $query->reset('columns');
+
+ $query->columns($leftColumns)
+ ->joinLeft(
+ ['oi' => $table . '_inheritance'],
+ 'o.id = oi.' . $this->getType() . '_id',
+ []
+ )->joinLeft(
+ ['sub_o' => $table],
+ 'sub_o.id = oi.parent_' . $this->getType() . '_id',
+ []
+ )->group(['o.id', 'bo.uuid', 'bo.branch_uuid']);
+
+ $rightColumns['imports'] = 'bo.imports';
+
+ $right->reset('columns');
+ $right->columns($rightColumns);
+
+ $query->where("(bo.branch_deleted IS NULL OR bo.branch_deleted = 'n')");
$this->applyObjectTypeFilter($query, $right);
$right->joinRight(
['bo' => "branched_$table"],
@@ -298,6 +328,7 @@ class ObjectsTable extends ZfQueryBasedTable
$query->order('object_name')->limit(100);
} else {
$this->applyObjectTypeFilter($query);
+ $query = $this->applyRestrictions($query);
$query->order('o.object_name')->limit(100);
}
diff --git a/library/Director/Web/Table/ObjectsTableService.php b/library/Director/Web/Table/ObjectsTableService.php
index 2d4ad41..c9bce72 100644
--- a/library/Director/Web/Table/ObjectsTableService.php
+++ b/library/Director/Web/Table/ObjectsTableService.php
@@ -203,8 +203,14 @@ class ObjectsTableService extends ObjectsTable
'hsb.service_id = o.id AND hsb.host_id = o.host_id',
[]
)->where('o.service_set_id IS NULL')
+ ->group(['o.id', 'h.id','hsb.service_id', 'hsb.host_id'])
->order('o.object_name')->order('h.object_name');
+ if ($this->branchUuid) {
+ $subQuery->where('bo.service_set IS NULL')
+ ->group(['bo.uuid', 'bo.branch_uuid']);
+ }
+
if ($this->host) {
if ($this->branchUuid) {
$subQuery->where('COALESCE(h.object_name, bo.host) = ?', $this->host->getObjectName());
diff --git a/library/Director/Web/Table/ObjectsTableSetMembers.php b/library/Director/Web/Table/ObjectsTableSetMembers.php
new file mode 100644
index 0000000..6b18ac9
--- /dev/null
+++ b/library/Director/Web/Table/ObjectsTableSetMembers.php
@@ -0,0 +1,255 @@
+<?php
+
+namespace Icinga\Module\Director\Web\Table;
+
+use Icinga\Authentication\Auth;
+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\Db\IcingaObjectFilterHelper;
+use Icinga\Module\Director\Objects\IcingaObject;
+use Icinga\Module\Director\Restriction\FilterByNameRestriction;
+use Ramsey\Uuid\Uuid;
+
+class ObjectsTableSetMembers extends ZfQueryBasedTable
+{
+ use TableWithBranchSupport;
+
+ protected $searchColumns = [
+ 'os.object_name',
+ 'o.object_name',
+ ];
+
+ private $type;
+
+ /** @var IcingaObject */
+ protected $dummyObject;
+
+ protected $baseObjectUrl;
+
+ /** @var Auth */
+ private $auth;
+
+ public static function create($type, Db $db, Auth $auth)
+ {
+ $table = new static($db);
+ $table->type = $type;
+ $table->auth = $auth;
+ return $table;
+ }
+
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ public function getColumnsToBeRendered()
+ {
+ return [
+ 'os.object_name' => 'Service Set',
+ 'o.object_name' => 'Service Name'
+ ];
+ }
+
+ public function setBaseObjectUrl($url)
+ {
+ $this->baseObjectUrl = $url;
+
+ return $this;
+ }
+
+ protected function getRowClasses($row)
+ {
+ // TODO: remove isset, to figure out where it is missing
+ if (isset($row->branch_uuid) && $row->branch_uuid !== null) {
+ return ['branch_modified'];
+ }
+ return [];
+ }
+
+ /**
+ * Should be triggered from renderRow, still unused.
+ *
+ * @param IcingaObject $template
+ * @param string $inheritance
+ * @return $this
+ * @throws \Icinga\Exception\ProgrammingError
+ */
+ public function filterTemplate(
+ IcingaObject $template,
+ $inheritance = IcingaObjectFilterHelper::INHERIT_DIRECT
+ ) {
+ IcingaObjectFilterHelper::filterByTemplate(
+ $this->getQuery(),
+ $template,
+ 'o',
+ $inheritance,
+ $this->branchUuid
+ );
+
+ return $this;
+ }
+
+
+ public function renderRow($row)
+ {
+ $url = Url::fromPath('director/service/edit', [
+ 'name' => $row->object_name,
+ 'uuid' => Uuid::fromBytes($row->uuid)->toString(),
+ ]);
+
+ return static::tr([
+ static::td([
+ Link::create($row->service_set, $url),
+ ]),
+ static::td($row->object_name),
+ ])->addAttributes(['class' => $this->getRowClasses($row)]);
+ }
+
+ /**
+ * @return IcingaObject
+ */
+ protected function getDummyObject()
+ {
+ if ($this->dummyObject === null) {
+ $type = $this->type;
+ $this->dummyObject = IcingaObject::createByType($type);
+ }
+ return $this->dummyObject;
+ }
+
+ protected function prepareQuery()
+ {
+ $table = $this->getDummyObject()->getTableName();
+ $type = $this->getType();
+
+ $columns = [
+ 'id' => 'o.id',
+ 'uuid' => 'o.uuid',
+ 'service_set' => 'os.object_name',
+ 'object_name' => 'o.object_name',
+ 'object_type' => 'os.object_type',
+ 'assign_filter' => 'os.assign_filter',
+ 'description' => 'os.description',
+ ];
+
+ $query = $this->db()->select()->from(
+ ['o' => $table],
+ $columns
+ )->joinLeft(
+ ['os' => "icinga_{$type}_set"],
+ "o.{$type}_set_id = os.id",
+ []
+ )->where('o.host_id IS NULL');
+
+ $nameFilter = new FilterByNameRestriction(
+ $this->connection(),
+ $this->auth,
+ "{$type}_set"
+ );
+ $nameFilter->applyToQuery($query, 'os');
+
+ if ($this->branchUuid) {
+ $columns['branch_uuid'] = 'bos.branch_uuid';
+ $conn = $this->connection();
+ if ($conn->isPgsql()) {
+ $columns['imports'] = 'CONCAT(\'[\', ARRAY_TO_STRING(ARRAY_AGG'
+ . '(CONCAT(\'"\', sub_o.object_name, \'"\')), \',\'), \']\')';
+ } else {
+ $columns['imports'] = 'CONCAT(\'[\', '
+ . 'GROUP_CONCAT(CONCAT(\'"\', sub_o.object_name, \'"\')), \']\')';
+ }
+
+ $columns = $this->branchifyColumns($columns);
+ $this->stripSearchColumnAliases();
+
+ $query->reset('columns');
+ $right = clone($query);
+ $conn = $this->connection();
+
+ $query->columns($columns)->joinLeft(
+ ['bos' => "branched_icinga_{$type}_set"],
+ // TODO: PgHexFunc
+ $this->db()->quoteInto(
+ 'bos.uuid = os.uuid AND bos.branch_uuid = ?',
+ $conn->quoteBinary($this->branchUuid->getBytes())
+ ),
+ []
+ )->joinLeft(
+ ['oi' => $table . '_inheritance'],
+ 'o.id = oi.' . $this->getType() . '_id',
+ []
+ )->joinLeft(
+ ['sub_o' => $table],
+ 'sub_o.id = oi.parent_' . $this->getType() . '_id',
+ []
+ )->where("(bos.branch_deleted IS NULL OR bos.branch_deleted = 'n')");
+
+ $columns['imports'] = 'bo.imports';
+ $right->columns($columns)->joinRight(
+ ['bos' => "branched_icinga_{$type}_set"],
+ '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')->group('o.id');
+ $right->group('bos.uuid')->group('os.uuid')->group('os.id')->group('bos.branch_uuid')->group('o.id');
+ }
+ $right->joinLeft(
+ ['bo' => "branched_icinga_{$type}"],
+ "bo.{$type}_set = bos.object_name",
+ []
+ )->group(['bo.object_name', 'o.object_name', 'bo.uuid', 'bo.imports']);
+ $query->joinLeft(
+ ['bo' => "branched_icinga_{$type}"],
+ "bo.{$type}_set = bos.object_name",
+ []
+ )->group(['bo.object_name', 'o.object_name', 'bo.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()) {
+ $query
+ ->group('uuid')
+ ->group('id')
+ ->group('imports')
+ ->group('branch_uuid')
+ ->group('object_name')
+ ->group('object_type')
+ ->group('assign_filter')
+ ->group('description')
+ ->group('service_set');
+ }
+ } else {
+ $query
+ ->where('o.object_type = ?', 'object')
+ ->order('os.object_name');
+ }
+
+ return $query;
+ }
+
+ /**
+ * @return Db
+ */
+ public function connection()
+ {
+ return parent::connection();
+ }
+}
diff --git a/library/Director/Web/Table/ReadOnlyFormAvpTable.php b/library/Director/Web/Table/ReadOnlyFormAvpTable.php
deleted file mode 100644
index c3b44f3..0000000
--- a/library/Director/Web/Table/ReadOnlyFormAvpTable.php
+++ /dev/null
@@ -1,113 +0,0 @@
-<?php
-
-namespace Icinga\Module\Director\Web\Table;
-
-use Icinga\Module\Director\PlainObjectRenderer;
-use Icinga\Module\Director\Web\Form\QuickForm;
-use Zend_Form_Element as ZfElement;
-use Zend_Form_DisplayGroup as ZfDisplayGroup;
-
-class ReadOnlyFormAvpTable
-{
- protected $form;
-
- public function __construct(QuickForm $form)
- {
- $this->form = $form;
- }
-
- protected function renderDisplayGroups(QuickForm $form)
- {
- $html = '';
-
- foreach ($form->getDisplayGroups() as $group) {
- $elements = $this->filterGroupElements($group);
-
- if (empty($elements)) {
- continue;
- }
-
- $html .= '<tr><th colspan="2" style="text-align: right">' . $group->getLegend() . '</th></tr>';
- $html .= $this->renderElements($elements);
- }
-
- return $html;
- }
-
- /**
- * @param ZfDisplayGroup $group
- * @return ZfElement[]
- */
- protected function filterGroupElements(ZfDisplayGroup $group)
- {
- $blacklist = array('disabled', 'assign_filter');
- $elements = array();
- /** @var ZfElement $element */
- foreach ($group->getElements() as $element) {
- if ($element->getValue() === null) {
- continue;
- }
-
- if ($element->getType() === 'Zend_Form_Element_Hidden') {
- continue;
- }
-
- if (in_array($element->getName(), $blacklist)) {
- continue;
- }
-
-
- $elements[] = $element;
- }
-
- return $elements;
- }
-
- protected function renderElements($elements)
- {
- $html = '';
- foreach ($elements as $element) {
- $html .= $this->renderElement($element);
- }
-
- return $html;
- }
-
- /**
- * @param ZfElement $element
- *
- * @return string
- */
- protected function renderElement(ZfElement $element)
- {
- $value = $element->getValue();
- return '<tr><th>'
- . $this->escape($element->getLabel())
- . '</th><td>'
- . $this->renderValue($value)
- . '</td></tr>';
- }
-
- protected function renderValue($value)
- {
- if (is_string($value)) {
- return $this->escape($value);
- } elseif (is_array($value)) {
- return $this->escape(implode(', ', $value));
- }
- return $this->escape(PlainObjectRenderer::render($value));
- }
-
- protected function escape($string)
- {
- return htmlspecialchars($string);
- }
-
- public function render()
- {
- $this->form->initializeForObject();
- return '<table class="name-value-table">' . "\n"
- . $this->renderDisplayGroups($this->form)
- . '</table>';
- }
-}
diff --git a/library/Director/Web/Table/ServiceTemplateUsageTable.php b/library/Director/Web/Table/ServiceTemplateUsageTable.php
index 82f9643..c2806f6 100644
--- a/library/Director/Web/Table/ServiceTemplateUsageTable.php
+++ b/library/Director/Web/Table/ServiceTemplateUsageTable.php
@@ -2,6 +2,9 @@
namespace Icinga\Module\Director\Web\Table;
+use Icinga\Authentication\Auth;
+use Icinga\Module\Director\Db;
+
class ServiceTemplateUsageTable extends TemplateUsageTable
{
public function getTypes()
@@ -10,18 +13,27 @@ class ServiceTemplateUsageTable extends TemplateUsageTable
'templates' => $this->translate('Templates'),
'objects' => $this->translate('Objects'),
'applyrules' => $this->translate('Apply Rules'),
- // 'setmembers' => $this->translate('Set Members'),
+ 'setmembers' => $this->translate('Set Members'),
];
}
- protected function getTypeSummaryDefinitions()
+ protected function getSummaryTables(string $templateType, Db $connection)
{
+ $auth = Auth::getInstance();
return [
- 'templates' => $this->getSummaryLine('template'),
- 'objects' => $this->getSummaryLine('object'),
- 'applyrules' => $this->getSummaryLine('apply', 'o.service_set_id IS NULL'),
- // TODO: re-enable
- // 'setmembers' => $this->getSummaryLine('apply', 'o.service_set_id IS NOT NULL'),
+ 'templates' => TemplatesTable::create(
+ $templateType,
+ $connection
+ ),
+ 'objects' => ObjectsTable::create($templateType, $connection, $this->auth)
+ ->setBranchUuid($this->branchUuid),
+ 'applyrules' => ApplyRulesTable::create($templateType, $connection)
+ ->setBranchUuid($this->branchUuid),
+ 'setmembers' => ObjectsTableSetMembers::create(
+ $templateType,
+ $connection,
+ $auth
+ )
];
}
}
diff --git a/library/Director/Web/Table/SyncRunTable.php b/library/Director/Web/Table/SyncRunTable.php
index e08aad7..19f2678 100644
--- a/library/Director/Web/Table/SyncRunTable.php
+++ b/library/Director/Web/Table/SyncRunTable.php
@@ -2,22 +2,17 @@
namespace Icinga\Module\Director\Web\Table;
-use gipfl\Format\LocalTimeFormat;
use Icinga\Module\Director\Objects\SyncRule;
use gipfl\IcingaWeb2\Link;
-use gipfl\IcingaWeb2\Table\ZfQueryBasedTable;
-class SyncRunTable extends ZfQueryBasedTable
+class SyncRunTable extends IntlZfQueryBasedTable
{
/** @var SyncRule */
protected $rule;
- protected $timeFormat;
-
public function __construct(SyncRule $rule)
{
parent::__construct($rule->getConnection());
- $this->timeFormat = new LocalTimeFormat();
$this->getAttributes()
->set('data-base-target', '_self')
->add('class', 'history');
@@ -31,7 +26,7 @@ class SyncRunTable extends ZfQueryBasedTable
return $this::tr([
$this::td($this->makeSummary($row)),
$this::td(new Link(
- $this->timeFormat->getTime($time),
+ $this->getTime($time),
'director/syncrule/history',
[
'id' => $row->rule_id,
diff --git a/library/Director/Web/Table/TableWithBranchSupport.php b/library/Director/Web/Table/TableWithBranchSupport.php
index 7c5b15c..9e412c3 100644
--- a/library/Director/Web/Table/TableWithBranchSupport.php
+++ b/library/Director/Web/Table/TableWithBranchSupport.php
@@ -56,6 +56,9 @@ trait TableWithBranchSupport
$result[$alias] = $column;
}
+ if (isset($result['count_services'])) {
+ $result['count_services'] = 'COUNT(DISTINCT COALESCE(o.uuid, bo.uuid))';
+ }
return $result;
}
diff --git a/library/Director/Web/Table/TemplateUsageTable.php b/library/Director/Web/Table/TemplateUsageTable.php
index 66e56ea..376a499 100644
--- a/library/Director/Web/Table/TemplateUsageTable.php
+++ b/library/Director/Web/Table/TemplateUsageTable.php
@@ -2,9 +2,12 @@
namespace Icinga\Module\Director\Web\Table;
+use Icinga\Authentication\Auth;
use Icinga\Exception\ProgrammingError;
+use Icinga\Module\Director\Db;
+use Icinga\Module\Director\Db\Branch\Branch;
+use Icinga\Module\Director\Db\IcingaObjectFilterHelper;
use Icinga\Module\Director\Objects\IcingaObject;
-use Icinga\Module\Director\Resolver\TemplateTree;
use gipfl\IcingaWeb2\Link;
use ipl\Html\Table;
use gipfl\Translation\TranslationHelper;
@@ -13,10 +16,17 @@ class TemplateUsageTable extends Table
{
use TranslationHelper;
+ use TableWithBranchSupport;
+
+ /** @var Auth */
+ protected $auth;
+
protected $defaultAttributes = ['class' => 'pivot'];
protected $objectType;
+ protected $searchColumns = [];
+
public function getTypes()
{
return [
@@ -25,26 +35,22 @@ class TemplateUsageTable extends Table
];
}
- protected function getTypeSummaryDefinitions()
- {
- return [
- 'templates' => $this->getSummaryLine('template'),
- 'objects' => $this->getSummaryLine('object'),
- ];
- }
-
/**
* @param IcingaObject $template
+ * @param Branch|null $branch
+ *
* @return TemplateUsageTable
+ *
+ * @throws ProgrammingError
*/
- public static function forTemplate(IcingaObject $template)
+ public static function forTemplate(IcingaObject $template, Auth $auth, Branch $branch = null)
{
$type = ucfirst($template->getShortTableName());
- $class = __NAMESPACE__ . "\\${type}TemplateUsageTable";
+ $class = __NAMESPACE__ . "\\{$type}TemplateUsageTable";
if (class_exists($class)) {
- return new $class($template);
+ return new $class($template, $auth, $branch);
} else {
- return new static($template);
+ return new static($template, $auth, $branch);
}
}
@@ -58,8 +64,9 @@ class TemplateUsageTable extends Table
];
}
- protected function __construct(IcingaObject $template)
+ protected function __construct(IcingaObject $template, Auth $auth, Branch $branch = null)
{
+ $this->auth = $auth;
if ($template->get('object_type') !== 'template') {
throw new ProgrammingError(
@@ -68,6 +75,7 @@ class TemplateUsageTable extends Table
);
}
+ $this->setBranch($branch);
$this->objectType = $objectType = $template->getShortTableName();
$types = $this->getTypes();
$usage = $this->getUsageSummary($template);
@@ -85,7 +93,7 @@ class TemplateUsageTable extends Table
Table::td(
Link::create(
$count,
- "director/${objectType}template/$type",
+ "director/{$objectType}template/$type",
[
'name' => $template->getObjectName(),
'inheritance' => $inheritance
@@ -98,6 +106,7 @@ class TemplateUsageTable extends Table
}
if ($used) {
+ $this->getHeader()->add(Table::row($this->getColumnsToBeRendered(), null, 'th'));
$this->add($rows);
} else {
$this->add($this->translate('This template is not in use'));
@@ -106,52 +115,51 @@ class TemplateUsageTable extends Table
protected function getUsageSummary(IcingaObject $template)
{
- $id = $template->getAutoincId();
$connection = $template->getConnection();
$db = $connection->getDbAdapter();
- $oType = $this->objectType;
- $tree = new TemplateTree($oType, $connection);
- $ids = $tree->listDescendantIdsFor($template);
- if (empty($ids)) {
- $ids = [0];
+
+ $types = array_keys($this->getTypes());
+ $direct = [];
+ $indirect = [];
+ $templateType = $template->getShortTableName();
+
+ foreach ($this->getSummaryTables($templateType, $connection) as $type => $summaryTable) {
+ $directTable = clone $summaryTable;
+ $inDirectTable = clone $summaryTable;
+
+ $direct[$type] = $db->query(
+ $directTable
+ ->filterTemplate($template, IcingaObjectFilterHelper::INHERIT_DIRECT)
+ ->getQuery()
+ )->rowCount();
+ $indirect[$type] = $db->query(
+ $inDirectTable
+ ->filterTemplate($template, IcingaObjectFilterHelper::INHERIT_INDIRECT)
+ ->getQuery()
+ )->rowCount();
}
- $baseQuery = $db->select()->from(
- ['o' => 'icinga_' . $oType],
- $this->getTypeSummaryDefinitions()
- )->joinLeft(
- ['oi' => "icinga_${oType}_inheritance"],
- "oi.${oType}_id = o.id",
- []
- );
-
- $query = clone($baseQuery);
- $direct = $db->fetchRow(
- $query->where("oi.parent_${oType}_id = ?", $id)
- );
- $query = clone($baseQuery);
- $indirect = $db->fetchRow(
- $query->where("oi.parent_${oType}_id IN (?)", $ids)
- );
- //$indirect->templates = count($ids) - 1;
$total = [];
- $types = array_keys($this->getTypes());
foreach ($types as $type) {
- $total[$type] = $direct->$type + $indirect->$type;
+ $total[$type] = $direct[$type] + $indirect[$type];
}
return (object) [
- 'direct' => $direct,
- 'indirect' => $indirect,
+ 'direct' => (object) $direct,
+ 'indirect' => (object) $indirect,
'total' => (object) $total
];
}
- protected function getSummaryLine($type, $extra = null)
+ protected function getSummaryTables(string $templateType, Db $connection)
{
- if ($extra !== null) {
- $extra = " AND $extra";
- }
- return "COALESCE(SUM(CASE WHEN o.object_type = '${type}'${extra} THEN 1 ELSE 0 END), 0)";
+ return [
+ 'templates' => TemplatesTable::create(
+ $templateType,
+ $connection
+ ),
+ 'objects' => ObjectsTable::create($templateType, $connection, $this->auth)
+ ->setBranchUuid($this->branchUuid)
+ ];
}
}
diff --git a/library/Director/Web/Table/TemplatesTable.php b/library/Director/Web/Table/TemplatesTable.php
index be195b2..d6582fd 100644
--- a/library/Director/Web/Table/TemplatesTable.php
+++ b/library/Director/Web/Table/TemplatesTable.php
@@ -36,8 +36,8 @@ class TemplatesTable extends ZfQueryBasedTable implements FilterableByUsage
{
$type = $this->type;
$this->enableMultiSelect(
- "director/${type}s/edittemplates",
- "director/${type}template",
+ "director/{$type}s/edittemplates",
+ "director/{$type}template",
['name']
);
}
@@ -60,12 +60,12 @@ class TemplatesTable extends ZfQueryBasedTable implements FilterableByUsage
$name,
Html::tag(
'span',
- ['style' => 'font-style: italic'],
+ ['class' => 'font-italic'],
$this->translate(' - not in use -')
)
];
- $url = Url::fromPath("director/${type}template/usage", [
+ $url = Url::fromPath("director/{$type}template/usage", [
'name' => $name
]);
@@ -101,8 +101,8 @@ class TemplatesTable extends ZfQueryBasedTable implements FilterableByUsage
{
$type = $this->getType();
$this->getQuery()->where(
- "(EXISTS (SELECT ${type}_id FROM icinga_${type}_inheritance"
- . " WHERE parent_${type}_id = o.id))"
+ "(EXISTS (SELECT {$type}_id FROM icinga_{$type}_inheritance"
+ . " WHERE parent_{$type}_id = o.id))"
);
}
@@ -110,8 +110,8 @@ class TemplatesTable extends ZfQueryBasedTable implements FilterableByUsage
{
$type = $this->getType();
$this->getQuery()->where(
- "(NOT EXISTS (SELECT ${type}_id FROM icinga_${type}_inheritance"
- . " WHERE parent_${type}_id = o.id))"
+ "(NOT EXISTS (SELECT {$type}_id FROM icinga_{$type}_inheritance"
+ . " WHERE parent_{$type}_id = o.id))"
);
}
@@ -135,8 +135,8 @@ class TemplatesTable extends ZfQueryBasedTable implements FilterableByUsage
protected function prepareQuery()
{
$type = $this->getType();
- $used = "CASE WHEN EXISTS(SELECT 1 FROM icinga_${type}_inheritance oi"
- . " WHERE oi.parent_${type}_id = o.id) THEN 'y' ELSE 'n' END";
+ $used = "CASE WHEN EXISTS(SELECT 1 FROM icinga_{$type}_inheritance oi"
+ . " WHERE oi.parent_{$type}_id = o.id) THEN 'y' ELSE 'n' END";
$columns = [
'object_name' => 'o.object_name',
@@ -145,7 +145,7 @@ class TemplatesTable extends ZfQueryBasedTable implements FilterableByUsage
'is_used' => $used,
];
$query = $this->db()->select()->from(
- ['o' => "icinga_${type}"],
+ ['o' => "icinga_{$type}"],
$columns
)->where(
"o.object_type = 'template'"
diff --git a/library/Director/Web/Tabs/InfraTabs.php b/library/Director/Web/Tabs/InfraTabs.php
index 8a65c4e..ea7a0cc 100644
--- a/library/Director/Web/Tabs/InfraTabs.php
+++ b/library/Director/Web/Tabs/InfraTabs.php
@@ -5,6 +5,7 @@ namespace Icinga\Module\Director\Web\Tabs;
use Icinga\Authentication\Auth;
use gipfl\Translation\TranslationHelper;
use gipfl\IcingaWeb2\Widget\Tabs;
+use Icinga\Module\Director\Auth\Permission;
class InfraTabs extends Tabs
{
@@ -24,21 +25,21 @@ class InfraTabs extends Tabs
{
$auth = $this->auth;
- if ($auth->hasPermission('director/audit')) {
+ if ($auth->hasPermission(Permission::AUDIT)) {
$this->add('activitylog', [
'label' => $this->translate('Activity Log'),
'url' => 'director/config/activities'
]);
}
- if ($auth->hasPermission('director/deploy')) {
+ if ($auth->hasPermission(Permission::DEPLOY)) {
$this->add('deploymentlog', [
'label' => $this->translate('Deployments'),
'url' => 'director/config/deployments'
]);
}
- if ($auth->hasPermission('director/admin')) {
+ if ($auth->hasPermission(Permission::ADMIN)) {
$this->add('infrastructure', [
'label' => $this->translate('Infrastructure'),
'url' => 'director/dashboard',
diff --git a/library/Director/Web/Tabs/MainTabs.php b/library/Director/Web/Tabs/MainTabs.php
index 5ea2e9b..48ffa7c 100644
--- a/library/Director/Web/Tabs/MainTabs.php
+++ b/library/Director/Web/Tabs/MainTabs.php
@@ -5,6 +5,7 @@ namespace Icinga\Module\Director\Web\Tabs;
use gipfl\Translation\TranslationHelper;
use gipfl\IcingaWeb2\Widget\Tabs;
use Icinga\Authentication\Auth;
+use Icinga\Module\Director\Auth\Permission;
use Icinga\Module\Director\Web\Widget\Daemon\BackgroundDaemonState;
use Icinga\Module\Director\Db;
use Icinga\Module\Director\Health;
@@ -26,7 +27,7 @@ class MainTabs extends Tabs
'label' => $this->translate('Overview'),
'url' => 'director'
]);
- if ($this->auth->hasPermission('director/admin')) {
+ if ($this->auth->hasPermission(Permission::ADMIN)) {
$this->add('health', [
'label' => $this->translate('Health'),
'url' => 'director/health'
@@ -39,7 +40,7 @@ class MainTabs extends Tabs
public function render()
{
- if ($this->auth->hasPermission('director/admin')) {
+ if ($this->auth->hasPermission(Permission::ADMIN)) {
if ($this->getActiveName() !== 'health') {
$state = $this->getHealthState();
if ($state->isProblem()) {
diff --git a/library/Director/Web/Tabs/ObjectTabs.php b/library/Director/Web/Tabs/ObjectTabs.php
index cbd3f15..c355304 100644
--- a/library/Director/Web/Tabs/ObjectTabs.php
+++ b/library/Director/Web/Tabs/ObjectTabs.php
@@ -3,6 +3,7 @@
namespace Icinga\Module\Director\Web\Tabs;
use Icinga\Authentication\Auth;
+use Icinga\Module\Director\Auth\Permission;
use Icinga\Module\Director\Objects\IcingaObject;
use gipfl\Translation\TranslationHelper;
use gipfl\IcingaWeb2\Widget\Tabs;
@@ -46,10 +47,10 @@ class ObjectTabs extends Tabs
protected function addTabsForNewObject()
{
$type = $this->type;
- $this->add('add', array(
+ $this->add('add', [
'url' => sprintf('director/%s/add', $type),
'label' => sprintf($this->translate('Add %s'), ucfirst($type)),
- ));
+ ]);
}
protected function addTabsForExistingObject()
@@ -59,14 +60,12 @@ class ObjectTabs extends Tabs
$object = $this->object;
$params = $object->getUrlParams();
- if (! $object->isExternal()
- || in_array($object->getShortTableName(), $this->allowedExternals)
- ) {
- $this->add('modify', array(
+ if (! $object->isExternal() || in_array($object->getShortTableName(), $this->allowedExternals)) {
+ $this->add('modify', [
'url' => sprintf('director/%s', $type),
'urlParams' => $params,
'label' => $this->translate(ucfirst($type))
- ));
+ ]);
}
if ($object->getShortTableName() === 'host') {
$this->add('services', [
@@ -76,19 +75,17 @@ class ObjectTabs extends Tabs
]);
}
- if ($auth->hasPermission('director/showconfig')) {
- if ($object->getShortTableName() !== 'service'
- || $object->get('service_set_id') === null
- ) {
- $this->add('render', array(
+ if ($auth->hasPermission(Permission::SHOW_CONFIG)) {
+ if ($object->getShortTableName() !== 'service' || $object->get('service_set_id') === null) {
+ $this->add('render', [
'url' => sprintf('director/%s/render', $type),
'urlParams' => $params,
'label' => $this->translate('Preview'),
- ));
+ ]);
}
}
- if ($auth->hasPermission('director/audit')) {
+ if ($auth->hasPermission(Permission::AUDIT)) {
$this->add('history', array(
'url' => sprintf('director/%s/history', $type),
'urlParams' => $params,
@@ -96,7 +93,7 @@ class ObjectTabs extends Tabs
));
}
- if ($auth->hasPermission('director/admin') && $this->hasFields()) {
+ if ($auth->hasPermission(Permission::ADMIN) && $this->hasFields()) {
$this->add('fields', array(
'url' => sprintf('director/%s/fields', $type),
'urlParams' => $params,
@@ -117,7 +114,7 @@ class ObjectTabs extends Tabs
if ($object->supportsRanges()) {
$this->add('ranges', [
- 'url' => "director/${type}/ranges",
+ 'url' => "director/{$type}/ranges",
'urlParams' => $params,
'label' => $this->translate('Ranges')
]);
@@ -138,11 +135,11 @@ class ObjectTabs extends Tabs
]);
}
- if ($object->getShortTableName() === 'host' && $auth->hasPermission('director/hosts')) {
+ if ($object->getShortTableName() === 'host' && $auth->hasPermission(Permission::HOSTS)) {
$this->add('agent', [
- 'url' => 'director/host/agent',
+ 'url' => 'director/host/agent',
'urlParams' => $params,
- 'label' => $this->translate('Agent')
+ 'label' => $this->translate('Agent')
]);
}
}
diff --git a/library/Director/Web/Tabs/ObjectsTabs.php b/library/Director/Web/Tabs/ObjectsTabs.php
index 4f9e5a8..9d2a737 100644
--- a/library/Director/Web/Tabs/ObjectsTabs.php
+++ b/library/Director/Web/Tabs/ObjectsTabs.php
@@ -3,6 +3,7 @@
namespace Icinga\Module\Director\Web\Tabs;
use Icinga\Authentication\Auth;
+use Icinga\Module\Director\Auth\Permission;
use Icinga\Module\Director\Objects\IcingaObject;
use gipfl\Translation\TranslationHelper;
use gipfl\IcingaWeb2\Widget\Tabs;
@@ -21,65 +22,66 @@ class ObjectsTabs extends Tabs
$plType = strtolower(preg_replace('/cys$/', 'cies', $shortName . 's'));
$plType = str_replace('_', '-', $plType);
- if ($auth->hasPermission("director/${plType}")) {
- $this->add('index', array(
+ if ($auth->hasPermission("director/{$plType}")) {
+ $this->add('index', [
'url' => sprintf('director/%s', $plType),
'label' => $this->translate(ucfirst($plType)),
- ));
+ ]);
}
if ($object->getShortTableName() === 'command') {
- $this->add('external', array(
- 'url' => sprintf('director/%s', strtolower($plType)),
+ $this->add('external', [
+ 'url' => sprintf('director/%s', strtolower($plType)),
'urlParams' => ['type' => 'external_object'],
- 'label' => $this->translate('External'),
- ));
+ 'label' => $this->translate('External'),
+ ]);
}
- if ($auth->hasPermission('director/admin') || (
+ if ($auth->hasPermission(Permission::ADMIN)
+ || (
$object->getShortTableName() === 'notification'
- && $auth->hasPermission('director/notifications')
+ && $auth->hasPermission(Permission::NOTIFICATIONS)
) || (
$object->getShortTableName() === 'scheduled_downtime'
- && $auth->hasPermission('director/scheduled-downtimes')
+ && $auth->hasPermission(Permission::SCHEDULED_DOWNTIMES)
)) {
if ($object->supportsApplyRules()) {
- $this->add('applyrules', array(
- 'url' => sprintf('director/%s/applyrules', $plType),
+ $this->add('applyrules', [
+ 'url' => sprintf('director/%s/applyrules', $plType),
'label' => $this->translate('Apply')
- ));
+ ]);
}
}
- if ($auth->hasPermission('director/admin') && $type !== 'zone') {
+ if ($auth->hasPermission(Permission::ADMIN) && $type !== 'zone') {
if ($object->supportsImports()) {
- $this->add('templates', array(
- 'url' => sprintf('director/%s/templates', $plType),
+ $this->add('templates', [
+ 'url' => sprintf('director/%s/templates', $plType),
'label' => $this->translate('Templates'),
- ));
+ ]);
}
if ($object->supportsGroups()) {
- $this->add('groups', array(
- 'url' => sprintf('director/%sgroups', $typeUrl),
+ $this->add('groups', [
+ 'url' => sprintf('director/%sgroups', $typeUrl),
'label' => $this->translate('Groups')
- ));
+ ]);
}
}
- if ($auth->hasPermission('director/admin')) {
+ if ($auth->hasPermission(Permission::ADMIN)) {
if ($object->supportsChoices()) {
- $this->add('choices', array(
- 'url' => sprintf('director/templatechoices/%s', $shortName),
+ $this->add('choices', [
+ 'url' => sprintf('director/templatechoices/%s', $shortName),
'label' => $this->translate('Choices')
- ));
+ ]);
}
}
- if ($object->supportsSets() && $auth->hasPermission("director/${typeUrl}sets")) {
- $this->add('sets', array(
- 'url' => sprintf('director/%s/sets', $plType),
+ if ($object->supportsSets() && $auth->hasPermission("director/{$typeUrl}sets")) {
+ $this->add('sets', [
+ 'url' => sprintf('director/%s/sets', $plType),
'label' => $this->translate('Sets')
- ));
+ ]);
}
}
}
diff --git a/library/Director/Web/Tree/TemplateTreeRenderer.php b/library/Director/Web/Tree/TemplateTreeRenderer.php
index e238ded..8b23518 100644
--- a/library/Director/Web/Tree/TemplateTreeRenderer.php
+++ b/library/Director/Web/Tree/TemplateTreeRenderer.php
@@ -71,7 +71,7 @@ class TemplateTreeRenderer extends BaseHtmlElement
} else {
$li->add(Link::create(
$tree['name'],
- "director/${type}template/usage",
+ "director/{$type}template/usage",
array('name' => $tree['name']),
array('class' => 'icon-' .$type)
));
diff --git a/library/Director/Web/Widget/ActivityLogInfo.php b/library/Director/Web/Widget/ActivityLogInfo.php
index 8454b26..2b64fd4 100644
--- a/library/Director/Web/Widget/ActivityLogInfo.php
+++ b/library/Director/Web/Widget/ActivityLogInfo.php
@@ -3,7 +3,10 @@
namespace Icinga\Module\Director\Web\Widget;
use gipfl\Json\JsonString;
+use Icinga\Module\Director\Data\FieldReferenceLoader;
+use Icinga\Module\Director\DirectorObject\Automation\BasketSnapshotFieldResolver;
use Icinga\Module\Director\Objects\DirectorActivityLog;
+use Icinga\Module\Director\Web\Form\IcingaObjectFieldLoader;
use ipl\Html\HtmlDocument;
use ipl\Html\HtmlElement;
use Icinga\Date\DateFormatter;
@@ -83,8 +86,7 @@ class ActivityLogInfo extends HtmlDocument
/** @var Url $url */
$url = $url->without('checksum')->without('show');
$div = Html::tag('div', [
- 'class' => 'pagination-control',
- 'style' => 'float: right; width: 5em'
+ 'class' => ['pagination-control', 'activity-log-control'],
]);
$ul = Html::tag('ul', ['class' => 'nav tab-nav']);
@@ -434,11 +436,30 @@ class ActivityLogInfo extends HtmlDocument
{
if ($object instanceof IcingaService) {
return $this->previewService($object);
+ } elseif ($object instanceof IcingaServiceSet) {
+ return $this->previewServiceSet($object);
} else {
return $object->toSingleIcingaConfig();
}
}
+ /**
+ * Render service set to be previewed
+ *
+ * @param IcingaServiceSet $object
+ *
+ * @return IcingaConfig
+ */
+ protected function previewServiceSet(IcingaServiceSet $object)
+ {
+ $config = $object->toSingleIcingaConfig();
+ foreach ($object->getCachedServices() as $service) {
+ $service->renderToConfig($config);
+ }
+
+ return $config;
+ }
+
protected function previewService(IcingaService $service)
{
if (($set = $service->get('service_set')) !== null) {
@@ -625,10 +646,27 @@ class ActivityLogInfo extends HtmlDocument
$newProps['object_type'] = $props->object_type;
}
- return IcingaObject::createByType(
+ $object = IcingaObject::createByType(
$type,
$newProps,
$this->db
- )->setProperties((array) $props);
+ );
+
+ if ($type === 'icinga_service_set' && isset($props->services)) {
+ $services = [];
+ foreach ($props->services as $service) {
+ $services[$service->object_name] = IcingaObject::createByType(
+ 'icinga_service',
+ (array) $service,
+ $this->db
+ );
+ }
+
+ /** @var IcingaServiceSet $object */
+ $object->setCachedServices($services);
+ unset($props->services);
+ }
+
+ return $object->setProperties((array) $props);
}
}
diff --git a/library/Director/Web/Widget/AdditionalTableActions.php b/library/Director/Web/Widget/AdditionalTableActions.php
index 978f399..7495189 100644
--- a/library/Director/Web/Widget/AdditionalTableActions.php
+++ b/library/Director/Web/Widget/AdditionalTableActions.php
@@ -2,6 +2,7 @@
namespace Icinga\Module\Director\Web\Widget;
+use Icinga\Module\Director\Auth\Permission;
use ipl\Html\Html;
use ipl\Html\HtmlDocument;
use gipfl\IcingaWeb2\Icon;
@@ -35,10 +36,10 @@ class AdditionalTableActions
public function appendTo(HtmlDocument $parent)
{
$links = [];
- if ($this->hasPermission('director/admin')) {
+ if ($this->hasPermission(Permission::ADMIN)) {
$links[] = $this->createDownloadJsonLink();
}
- if ($this->hasPermission('director/showsql')) {
+ if ($this->hasPermission(Permission::SHOW_SQL)) {
$links[] = $this->createShowSqlToggle();
}
diff --git a/library/Director/Web/Widget/BranchedObjectHint.php b/library/Director/Web/Widget/BranchedObjectHint.php
index ec16094..c50f923 100644
--- a/library/Director/Web/Widget/BranchedObjectHint.php
+++ b/library/Director/Web/Widget/BranchedObjectHint.php
@@ -15,33 +15,45 @@ class BranchedObjectHint extends HtmlDocument
{
use TranslationHelper;
- public function __construct(Branch $branch, Auth $auth, BranchedObject $object = null)
+ public function __construct(Branch $branch, Auth $auth, BranchedObject $object = null, $hasPreferredBranch = false)
{
if (! $branch->isBranch()) {
- return;
- }
- $hook = Branch::requireHook();
-
- $name = $branch->getName();
- if (substr($name, 0, 1) === '/') {
- $label = $this->translate('this configuration branch');
+ if ($hasPreferredBranch) {
+ $main = true;
+ $hintMethod = 'warning';
+ $link = $this->translate('the main configuration branch');
+ $deployHint = ' ' . $this->translate('This will be part of the next deployment');
+ } else {
+ return;
+ }
} else {
- $label = $name;
+ $main = false;
+ $hintMethod = 'info';
+ $deployHint = ' ' . $this->translate('This will not be part of any deployment, unless being merged');
+ $hook = Branch::requireHook();
+ $name = $branch->getName();
+ if (substr($name, 0, 1) === '/') {
+ $label = $this->translate('this configuration branch');
+ } else {
+ $label = $name;
+ }
+ $link = $hook->linkToBranch($branch, $auth, $label);
}
- $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)));
+ $this->add(Hint::$hintMethod(Html::sprintf($this->translate(
+ 'This object will be created in %s.'
+ ) . $deployHint, $link)));
return;
}
if (! $object->hasBeenTouchedByBranch()) {
- $this->add(Hint::info(Html::sprintf($this->translate(
- 'Your changes will be stored in %s. The\'ll not be part of any deployment'
- . ' unless being merged'
- ), $link)));
+ $this->add(Hint::$hintMethod(Html::sprintf($this->translate(
+ 'Your changes are going to be stored in %s.'
+ ) . $deployHint, $link)));
+ return;
+ }
+ if ($main) {
return;
}
diff --git a/library/Director/Web/Widget/BranchedObjectsHint.php b/library/Director/Web/Widget/BranchedObjectsHint.php
index d689178..3f00f9e 100644
--- a/library/Director/Web/Widget/BranchedObjectsHint.php
+++ b/library/Director/Web/Widget/BranchedObjectsHint.php
@@ -13,9 +13,14 @@ class BranchedObjectsHint extends HtmlDocument
{
use TranslationHelper;
- public function __construct(Branch $branch, Auth $auth)
+ public function __construct(Branch $branch, Auth $auth, $hasPreferredBranch = false)
{
if (! $branch->isBranch()) {
+ if ($hasPreferredBranch) {
+ $this->add(Hint::warning($this->translate(
+ "You're currently in the master branch, your changes will make part of the next Deployment"
+ )));
+ }
return;
}
$hook = Branch::requireHook();
diff --git a/library/Director/Web/Widget/DeploymentInfo.php b/library/Director/Web/Widget/DeploymentInfo.php
index 110200f..1f87abc 100644
--- a/library/Director/Web/Widget/DeploymentInfo.php
+++ b/library/Director/Web/Widget/DeploymentInfo.php
@@ -2,6 +2,7 @@
namespace Icinga\Module\Director\Web\Widget;
+use Icinga\Module\Director\Auth\Permission;
use ipl\Html\HtmlDocument;
use Icinga\Authentication\Auth;
use Icinga\Module\Director\IcingaConfig\IcingaConfig;
@@ -55,7 +56,7 @@ class DeploymentInfo extends HtmlDocument
'url' => $request->getUrl()
))->activate('deployment');
- if ($dep->config_checksum !== null && $auth->hasPermission('director/showconfig')) {
+ if ($dep->config_checksum !== null && $auth->hasPermission(Permission::SHOW_CONFIG)) {
$tabs->add('config', array(
'label' => $this->translate('Config'),
'url' => 'director/config/files',
@@ -72,7 +73,8 @@ class DeploymentInfo extends HtmlDocument
protected function createInfoTable()
{
$dep = $this->deployment;
- $table = new NameValueTable();
+ $table = (new NameValueTable())
+ ->addAttributes(['class' => 'deployment-details']);
$table->addNameValuePairs([
$this->translate('Deployment time') => $dep->start_time,
$this->translate('Sent to') => $dep->peer_identity,
@@ -135,16 +137,21 @@ class DeploymentInfo extends HtmlDocument
} else {
return [$this->translate('Unknown, failed to collect related information'), new Icon('help')];
}
- } elseif ($dep->startup_succeeded === 'y') {
- return $this->colored('green', [$this->translate('Succeeded'), new Icon('ok')]);
} else {
- return $this->colored('red', [$this->translate('Failed'), new Icon('cancel')]);
- }
- }
+ $div = Html::tag('div')->setSeparator(' ');
- protected function colored($color, array $content)
- {
- return Html::tag('div', ['style' => "color: $color;"], $content)->setSeparator(' ');
+ if ($dep->startup_succeeded === 'y') {
+ $div
+ ->addAttributes(['class' => 'succeeded'])
+ ->add([$this->translate('Succeeded'), new Icon('ok')]);
+ } else {
+ $div
+ ->addAttributes(['class' => 'failed'])
+ ->add([$this->translate('Failed'), new Icon('cancel')]);
+ }
+
+ return $div;
+ }
}
public function render()
diff --git a/library/Director/Web/Widget/IcingaObjectInspection.php b/library/Director/Web/Widget/IcingaObjectInspection.php
index 61f3567..d9cf69d 100644
--- a/library/Director/Web/Widget/IcingaObjectInspection.php
+++ b/library/Director/Web/Widget/IcingaObjectInspection.php
@@ -205,7 +205,7 @@ class IcingaObjectInspection extends BaseHtmlElement
$this->add(Html::tag('p')->add(Html::sprintf(
'The configuration for this object has been rendered by Icinga'
. ' Director %s to %s',
- DateFormatter::timeAgo(strtotime($deployment->start_time, false)),
+ DateFormatter::timeAgo(strtotime($deployment->start_time)),
$this->linkToSourceLocation($deployment, $source)
)));
}