summaryrefslogtreecommitdiffstats
path: root/application/clicommands
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--application/clicommands/BasketCommand.php127
-rw-r--r--application/clicommands/BenchmarkCommand.php152
-rw-r--r--application/clicommands/CommandCommand.php15
-rw-r--r--application/clicommands/CommandsCommand.php14
-rw-r--r--application/clicommands/ConfigCommand.php178
-rw-r--r--application/clicommands/CoreCommand.php16
-rw-r--r--application/clicommands/DaemonCommand.php26
-rw-r--r--application/clicommands/DependencyCommand.php15
-rw-r--r--application/clicommands/EndpointCommand.php19
-rw-r--r--application/clicommands/ExportCommand.php180
-rw-r--r--application/clicommands/HealthCommand.php80
-rw-r--r--application/clicommands/HostCommand.php15
-rw-r--r--application/clicommands/HostgroupCommand.php15
-rw-r--r--application/clicommands/HostgroupsCommand.php14
-rw-r--r--application/clicommands/HostsCommand.php14
-rw-r--r--application/clicommands/HousekeepingCommand.php74
-rw-r--r--application/clicommands/ImportCommand.php62
-rw-r--r--application/clicommands/ImportsourceCommand.php168
-rw-r--r--application/clicommands/JobsCommand.php74
-rw-r--r--application/clicommands/KickstartCommand.php88
-rw-r--r--application/clicommands/MigrationCommand.php66
-rw-r--r--application/clicommands/NotificationCommand.php15
-rw-r--r--application/clicommands/ServiceCommand.php92
-rw-r--r--application/clicommands/ServicegroupCommand.php15
-rw-r--r--application/clicommands/ServicesetCommand.php14
-rw-r--r--application/clicommands/ServicesetsCommand.php15
-rw-r--r--application/clicommands/SyncruleCommand.php195
-rw-r--r--application/clicommands/TimeperiodCommand.php15
-rw-r--r--application/clicommands/UserCommand.php15
-rw-r--r--application/clicommands/UsergroupCommand.php15
-rw-r--r--application/clicommands/ZoneCommand.php15
31 files changed, 1818 insertions, 0 deletions
diff --git a/application/clicommands/BasketCommand.php b/application/clicommands/BasketCommand.php
new file mode 100644
index 0000000..dd2434f
--- /dev/null
+++ b/application/clicommands/BasketCommand.php
@@ -0,0 +1,127 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Date\DateFormatter;
+use Icinga\Module\Director\Cli\Command;
+use Icinga\Module\Director\Core\Json;
+use Icinga\Module\Director\DirectorObject\Automation\Basket;
+use Icinga\Module\Director\DirectorObject\Automation\BasketSnapshot;
+use Icinga\Module\Director\DirectorObject\ObjectPurgeHelper;
+
+/**
+ * Export Director Config Objects
+ */
+class BasketCommand extends Command
+{
+ /**
+ * List configured Baskets
+ *
+ * USAGE
+ *
+ * icingacli director basket list
+ *
+ * OPTIONS
+ */
+ public function listAction()
+ {
+ $db = $this->db()->getDbAdapter();
+ $query = $db->select()
+ ->from('director_basket', 'basket_name')
+ ->order('basket_name');
+ foreach ($db->fetchCol($query) as $name) {
+ echo "$name\n";
+ }
+ }
+
+ /**
+ * JSON-dump for objects related to the given Basket
+ *
+ * USAGE
+ *
+ * icingacli director basket dump --name <basket>
+ *
+ * OPTIONS
+ */
+ public function dumpAction()
+ {
+ $basket = $this->requireBasket();
+ $snapshot = BasketSnapshot::createForBasket($basket, $this->db());
+ echo $snapshot->getJsonDump() . "\n";
+ }
+
+ /**
+ * Take a snapshot for the given Basket
+ *
+ * USAGE
+ *
+ * icingacli director basket snapshot --name <basket>
+ *
+ * OPTIONS
+ */
+ public function snapshotAction()
+ {
+ $basket = $this->requireBasket();
+ $snapshot = BasketSnapshot::createForBasket($basket, $this->db());
+ $snapshot->store();
+ $hexSum = bin2hex($snapshot->get('content_checksum'));
+ printf(
+ "Snapshot '%s' taken for Basket '%s' at %s\n",
+ substr($hexSum, 0, 7),
+ $basket->get('basket_name'),
+ DateFormatter::formatDateTime($snapshot->get('ts_create') / 1000)
+ );
+ }
+
+ /**
+ * Restore a Basket from JSON dump provided on STDIN
+ *
+ * USAGE
+ *
+ * icingacli director basket restore < basket-dump.json
+ *
+ * OPTIONS
+ * --purge <ObjectType>[,<ObjectType] Purge objects of the
+ * Given types. WARNING: this removes ALL objects that are
+ * not shipped with the given basket
+ * --force Purge refuses to purge Objects in case there are
+ * no Objects of a given ObjectType in the provided basket
+ * unless forced to do so
+ */
+ public function restoreAction()
+ {
+ if ($purge = $this->params->get('purge')) {
+ $purge = explode(',', $purge);
+ ObjectPurgeHelper::assertObjectTypesAreEligibleForPurge($purge);
+ }
+ $json = file_get_contents('php://stdin');
+ BasketSnapshot::restoreJson($json, $this->db());
+ if ($purge) {
+ $this->purgeObjectTypes(Json::decode($json), $purge, $this->params->get('force'));
+ }
+ echo "Objects from Basket Snapshot have been restored\n";
+ }
+
+ protected function purgeObjectTypes($objects, array $types, $force = false)
+ {
+ $helper = new ObjectPurgeHelper($this->db());
+ if ($force) {
+ $helper->force();
+ }
+ foreach ($types as $type) {
+ list($className, $typeFilter) = BasketSnapshot::getClassAndObjectTypeForType($type);
+ $helper->purge(
+ isset($objects->$type) ? (array) $objects->$type : [],
+ $className,
+ $typeFilter
+ );
+ }
+ }
+
+ /**
+ */
+ protected function requireBasket()
+ {
+ return Basket::load($this->params->getRequired('name'), $this->db());
+ }
+}
diff --git a/application/clicommands/BenchmarkCommand.php b/application/clicommands/BenchmarkCommand.php
new file mode 100644
index 0000000..6ccd8c8
--- /dev/null
+++ b/application/clicommands/BenchmarkCommand.php
@@ -0,0 +1,152 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Application\Benchmark;
+use Icinga\Data\Filter\Filter;
+use Icinga\Data\Filter\FilterChain;
+use Icinga\Data\Filter\FilterExpression;
+use Icinga\Module\Director\Cli\Command;
+use Icinga\Module\Director\CustomVariable\CustomVariable;
+use Icinga\Module\Director\Data\Db\IcingaObjectFilterRenderer;
+use Icinga\Module\Director\Data\Db\IcingaObjectQuery;
+use Icinga\Module\Director\Objects\HostGroupMembershipResolver;
+use Icinga\Module\Director\Objects\IcingaHost;
+use Icinga\Module\Director\Objects\IcingaHostVar;
+use Icinga\Module\Director\Objects\IcingaVar;
+
+class BenchmarkCommand extends Command
+{
+ public function testflatfilterAction()
+ {
+ $q = new IcingaObjectQuery('host', $this->db());
+ $filter = Filter::fromQueryString(
+ // 'host.vars.snmp_community="*ub*"&(host.vars.location="London"|host.vars.location="Berlin")'
+ // 'host.vars.snmp_community="*ub*"&(host.vars.location="FRA DC"|host.vars.location="NBG DC")'
+ 'host.vars.priority="*igh"&(host.vars.location="FRA DC"|host.vars.location="NBG DC")'
+ );
+ IcingaObjectFilterRenderer::apply($filter, $q);
+ echo $q->getSql() . "\n";
+
+ print_r($q->listNames());
+ }
+
+ public function rerendervarsAction()
+ {
+ $conn = $this->db();
+ $db = $conn->getDbAdapter();
+ $db->beginTransaction();
+ $query = $db->select()->from(
+ array('v' => 'icinga_var'),
+ array(
+ 'v.varname',
+ 'v.varvalue',
+ 'v.checksum',
+ 'v.rendered_checksum',
+ 'v.rendered',
+ 'format' => "('json')",
+ )
+ );
+ Benchmark::measure('Ready to fetch all vars');
+ $rows = $db->fetchAll($query);
+ Benchmark::measure('Got vars, storing flat');
+ foreach ($rows as $row) {
+ $var = CustomVariable::fromDbRow($row);
+ $rendered = $var->render();
+ $checksum = sha1($rendered, true);
+ if ($checksum === $row->rendered_checksum) {
+ continue;
+ }
+
+ $where = $db->quoteInto('checksum = ?', $row->checksum);
+ $db->update(
+ 'icinga_var',
+ array(
+ 'rendered' => $rendered,
+ 'rendered_checksum' => $checksum
+ ),
+ $where
+ );
+ }
+
+ $db->commit();
+ }
+
+ public function flattenvarsAction()
+ {
+ $conn = $this->db();
+ $db = $conn->getDbAdapter();
+ $db->beginTransaction();
+ $query = $db->select()->from(['v' => 'icinga_host_var'], [
+ 'v.host_id',
+ 'v.varname',
+ 'v.varvalue',
+ 'v.format',
+ 'v.checksum'
+ ]);
+ Benchmark::measure('Ready to fetch all vars');
+ $rows = $db->fetchAll($query);
+ Benchmark::measure('Got vars, storing flat');
+
+ foreach ($rows as $row) {
+ $var = CustomVariable::fromDbRow($row);
+ $checksum = $var->checksum();
+ if (! IcingaVar::exists($checksum, $conn)) {
+ IcingaVar::generateForCustomVar($var, $conn);
+ }
+
+ if ($row->checksum === null) {
+ $where = $db->quoteInto('host_id = ?', $row->host_id)
+ . $db->quoteInto(' AND varname = ?', $row->varname);
+ $db->update('icinga_host_var', ['checksum' => $checksum], $where);
+ }
+ }
+
+ $db->commit();
+ }
+
+ public function resolvehostgroupsAction()
+ {
+ $resolver = new HostGroupMembershipResolver($this->db());
+ $resolver->refreshDb();
+ }
+
+ public function filterAction()
+ {
+ $flat = [];
+
+ /** @var FilterChain|FilterExpression $filter */
+ $filter = Filter::fromQueryString(
+ // 'object_name=*ic*2*&object_type=object'
+ 'vars.bpconfig=*'
+ );
+ Benchmark::measure('ready');
+ $objs = IcingaHost::loadAll($this->db());
+ Benchmark::measure('db done');
+
+ foreach ($objs as $host) {
+ $flat[$host->get('id')] = (object) [];
+ foreach ($host->getProperties() as $k => $v) {
+ $flat[$host->get('id')]->$k = $v;
+ }
+ }
+ Benchmark::measure('objects ready');
+
+ $vars = IcingaHostVar::loadAll($this->db());
+ Benchmark::measure('vars loaded');
+ foreach ($vars as $var) {
+ if (! array_key_exists($var->get('host_id'), $flat)) {
+ // Templates?
+ continue;
+ }
+ $flat[$var->get('host_id')]->{'vars.' . $var->get('varname')} = $var->get('varvalue');
+ }
+ Benchmark::measure('vars done');
+
+ foreach ($flat as $host) {
+ if ($filter->matches($host)) {
+ echo $host->object_name . "\n";
+ }
+ }
+ }
+}
diff --git a/application/clicommands/CommandCommand.php b/application/clicommands/CommandCommand.php
new file mode 100644
index 0000000..5c96442
--- /dev/null
+++ b/application/clicommands/CommandCommand.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\ObjectCommand;
+
+/**
+ * Manage Icinga Commands
+ *
+ * Use this command to show, create, modify or delete Icinga Command
+ * objects
+ */
+class CommandCommand extends ObjectCommand
+{
+}
diff --git a/application/clicommands/CommandsCommand.php b/application/clicommands/CommandsCommand.php
new file mode 100644
index 0000000..9a74337
--- /dev/null
+++ b/application/clicommands/CommandsCommand.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\ObjectsCommand;
+
+/**
+ * List Icinga Commands
+ *
+ * Use this command to list Icinga Command objects
+ */
+class CommandsCommand extends ObjectsCommand
+{
+}
diff --git a/application/clicommands/ConfigCommand.php b/application/clicommands/ConfigCommand.php
new file mode 100644
index 0000000..e313aa4
--- /dev/null
+++ b/application/clicommands/ConfigCommand.php
@@ -0,0 +1,178 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Application\Benchmark;
+use Icinga\Module\Director\Cli\Command;
+use Icinga\Module\Director\Core\Json;
+use Icinga\Module\Director\Deployment\ConditionalDeployment;
+use Icinga\Module\Director\Deployment\DeploymentGracePeriod;
+use Icinga\Module\Director\Deployment\DeploymentStatus;
+use Icinga\Module\Director\IcingaConfig\IcingaConfig;
+use Icinga\Module\Director\Import\SyncUtils;
+
+/**
+ * Generate, show and deploy Icinga 2 configuration
+ */
+class ConfigCommand extends Command
+{
+ /**
+ * Re-render the current configuration
+ */
+ public function renderAction()
+ {
+ $profile = $this->params->shift('profile');
+ if ($profile) {
+ $this->enableDbProfiler();
+ }
+
+ $config = new IcingaConfig($this->db());
+ Benchmark::measure('Rendering config');
+ if ($config->hasBeenModified()) {
+ Benchmark::measure('Config rendered, storing to db');
+ $config->store();
+ Benchmark::measure('All done');
+ $checksum = $config->getHexChecksum();
+ printf(
+ "New config with checksum %s has been generated\n",
+ $checksum
+ );
+ } else {
+ $checksum = $config->getHexChecksum();
+ printf(
+ "Config with checksum %s already exists\n",
+ $checksum
+ );
+ }
+
+ if ($profile) {
+ $this->dumpDbProfile();
+ }
+ }
+
+ protected function dumpDbProfile()
+ {
+ $profiler = $this->getDbProfiler();
+
+ $totalTime = $profiler->getTotalElapsedSecs();
+ $queryCount = $profiler->getTotalNumQueries();
+ $longestTime = 0;
+ $longestQuery = null;
+
+ /** @var \Zend_Db_Profiler_Query $query */
+ foreach ($profiler->getQueryProfiles() as $query) {
+ echo $query->getQuery() . "\n";
+ if ($query->getElapsedSecs() > $longestTime) {
+ $longestTime = $query->getElapsedSecs();
+ $longestQuery = $query->getQuery();
+ }
+ }
+
+ echo 'Executed ' . $queryCount . ' queries in ' . $totalTime . ' seconds' . "\n";
+ echo 'Average query length: ' . $totalTime / $queryCount . ' seconds' . "\n";
+ echo 'Queries per second: ' . $queryCount / $totalTime . "\n";
+ echo 'Longest query length: ' . $longestTime . "\n";
+ echo "Longest query: \n" . $longestQuery . "\n";
+ }
+
+ protected function getDbProfiler()
+ {
+ return $this->db()->getDbAdapter()->getProfiler();
+ }
+
+ protected function enableDbProfiler()
+ {
+ return $this->getDbProfiler()->setEnabled(true);
+ }
+
+ /**
+ * Deploy the current configuration
+ *
+ * USAGE
+ *
+ * icingacli director config deploy [--checksum <checksum>] [--force] [--wait <seconds>]
+ * [--grace-period <seconds>]
+ *
+ * OPTIONS
+ *
+ * --checksum <checksum> Optionally deploy a specific configuration
+ * --force Force a deployment, even when the configuration
+ * hasn't changed
+ * --wait <seconds> Optionally wait until Icinga completed it's
+ * restart
+ * --grace-period <seconds> Do not deploy if a deployment took place
+ * less than <seconds> ago
+ */
+ public function deployAction()
+ {
+ $db = $this->db();
+
+ $checksum = $this->params->get('checksum');
+ if ($checksum) {
+ $config = IcingaConfig::load(hex2bin($checksum), $db);
+ } else {
+ $config = IcingaConfig::generate($db);
+ $checksum = $config->getHexChecksum();
+ }
+
+ $deployer = new ConditionalDeployment($db, $this->api());
+ $deployer->force((bool) $this->params->get('force'));
+ if ($graceTime = $this->params->get('grace-period')) {
+ $deployer->setGracePeriod(new DeploymentGracePeriod((int) $graceTime, $db));
+ if ($this->params->get('force')) {
+ fwrite(STDERR, "WARNING: force overrides Grace period\n");
+ }
+ }
+ $deployer->refresh();
+
+ if ($deployment = $deployer->deploy($config)) {
+ if ($deployer->hasBeenForced()) {
+ echo $deployer->getNoDeploymentReason() . ", deploying anyway\n";
+ }
+ printf("Config '%s' has been deployed\n", $checksum);
+ } else {
+ echo $deployer->getNoDeploymentReason() . "\n";
+ return;
+ }
+
+ if ($timeout = $this->getWaitTime()) {
+ $deployed = $deployer->waitForStartupAfterDeploy($deployment, $timeout);
+ if ($deployed !== true) {
+ $this->fail("Waiting for Icinga restart failed '%s': %s\n", $checksum, $deployed);
+ }
+ }
+ }
+
+ /**
+ * Checks the deployments status
+ */
+ public function deploymentstatusAction()
+ {
+ $db = $this->db();
+ $api = $this->api();
+ $status = new DeploymentStatus($db, $api);
+ $result = $status->getDeploymentStatus($this->params->get('configs'), $this->params->get('activities'));
+ if ($key = $this->params->get('key')) {
+ $result = SyncUtils::getSpecificValue($result, $key);
+ }
+
+ if (is_string($result)) {
+ echo "$result\n";
+ } else {
+ echo Json::encode($result, JSON_PRETTY_PRINT) . "\n";
+ }
+ }
+
+ protected function getWaitTime()
+ {
+ if ($timeout = $this->params->get('wait')) {
+ if (!ctype_digit($timeout)) {
+ $this->fail("--wait must be the number of seconds to wait'");
+ }
+
+ return (int) $timeout;
+ }
+
+ return null;
+ }
+}
diff --git a/application/clicommands/CoreCommand.php b/application/clicommands/CoreCommand.php
new file mode 100644
index 0000000..4927aa5
--- /dev/null
+++ b/application/clicommands/CoreCommand.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\Command;
+use Icinga\Module\Director\PlainObjectRenderer;
+
+class CoreCommand extends Command
+{
+ public function constantsAction()
+ {
+ foreach ($this->api()->getConstants() as $name => $value) {
+ printf("const %s = %s\n", $name, PlainObjectRenderer::render($value));
+ }
+ }
+}
diff --git a/application/clicommands/DaemonCommand.php b/application/clicommands/DaemonCommand.php
new file mode 100644
index 0000000..e89e1da
--- /dev/null
+++ b/application/clicommands/DaemonCommand.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\Command;
+use Icinga\Module\Director\Daemon\BackgroundDaemon;
+
+class DaemonCommand extends Command
+{
+ /**
+ * Run the main Director daemon
+ *
+ * USAGE
+ *
+ * icingacli director daemon run [--db-resource <name>]
+ */
+ public function runAction()
+ {
+ $this->app->getModuleManager()->loadEnabledModules();
+ $daemon = new BackgroundDaemon();
+ if ($dbResource = $this->params->get('db-resource')) {
+ $daemon->setDbResourceName($dbResource);
+ }
+ $daemon->run();
+ }
+}
diff --git a/application/clicommands/DependencyCommand.php b/application/clicommands/DependencyCommand.php
new file mode 100644
index 0000000..ff5cbdc
--- /dev/null
+++ b/application/clicommands/DependencyCommand.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\ObjectCommand;
+
+/**
+ * Manage Icinga Dependencies
+ *
+ * Use this command to show, create, modify or delete Icinga Dependency
+ * objects
+ */
+class DependencyCommand extends ObjectCommand
+{
+}
diff --git a/application/clicommands/EndpointCommand.php b/application/clicommands/EndpointCommand.php
new file mode 100644
index 0000000..f61f4fc
--- /dev/null
+++ b/application/clicommands/EndpointCommand.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\ObjectCommand;
+
+/**
+ * Manage Icinga Endpoints
+ *
+ * Use this command to show, create, modify or delete Icinga Endpoint
+ * objects
+ */
+class EndpointCommand extends ObjectCommand
+{
+ public function statusAction()
+ {
+ print_r($this->api()->getStatus());
+ }
+}
diff --git a/application/clicommands/ExportCommand.php b/application/clicommands/ExportCommand.php
new file mode 100644
index 0000000..2b2119d
--- /dev/null
+++ b/application/clicommands/ExportCommand.php
@@ -0,0 +1,180 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\Command;
+use Icinga\Module\Director\DirectorObject\Automation\ImportExport;
+
+/**
+ * Export Director Config Objects
+ */
+class ExportCommand extends Command
+{
+ /**
+ * Export all ImportSource definitions
+ *
+ * USAGE
+ *
+ * icingacli director export importsources [options]
+ *
+ * OPTIONS
+ *
+ * --no-pretty JSON is pretty-printed per default
+ * Use this flag to enforce unformatted JSON
+ */
+ public function importsourcesAction()
+ {
+ $export = new ImportExport($this->db());
+ echo $this->renderJson(
+ $export->serializeAllImportSources(),
+ !$this->params->shift('no-pretty')
+ );
+ }
+
+ /**
+ * Export all SyncRule definitions
+ *
+ * USAGE
+ *
+ * icingacli director export syncrules [options]
+ *
+ * OPTIONS
+ *
+ * --no-pretty JSON is pretty-printed per default
+ * Use this flag to enforce unformatted JSON
+ */
+ public function syncrulesAction()
+ {
+ $export = new ImportExport($this->db());
+ echo $this->renderJson(
+ $export->serializeAllSyncRules(),
+ !$this->params->shift('no-pretty')
+ );
+ }
+
+ /**
+ * Export all Job definitions
+ *
+ * USAGE
+ *
+ * icingacli director export jobs [options]
+ *
+ * OPTIONS
+ *
+ * --no-pretty JSON is pretty-printed per default
+ * Use this flag to enforce unformatted JSON
+ */
+ public function jobsAction()
+ {
+ $export = new ImportExport($this->db());
+ echo $this->renderJson(
+ $export->serializeAllJobs(),
+ !$this->params->shift('no-pretty')
+ );
+ }
+
+ /**
+ * Export all DataField definitions
+ *
+ * USAGE
+ *
+ * icingacli director export datafields [options]
+ *
+ * OPTIONS
+ *
+ * --no-pretty JSON is pretty-printed per default
+ * Use this flag to enforce unformatted JSON
+ */
+ public function datafieldsAction()
+ {
+ $export = new ImportExport($this->db());
+ echo $this->renderJson(
+ $export->serializeAllDataFields(),
+ !$this->params->shift('no-pretty')
+ );
+ }
+
+ /**
+ * Export all DataList definitions
+ *
+ * USAGE
+ *
+ * icingacli director export datalists [options]
+ *
+ * OPTIONS
+ *
+ * --no-pretty JSON is pretty-printed per default
+ * Use this flag to enforce unformatted JSON
+ */
+ public function datalistsAction()
+ {
+ $export = new ImportExport($this->db());
+ echo $this->renderJson(
+ $export->serializeAllDataLists(),
+ !$this->params->shift('no-pretty')
+ );
+ }
+
+ // /**
+ // * Export all IcingaHostGroup definitions
+ // *
+ // * USAGE
+ // *
+ // * icingacli director export hostgroup [options]
+ // *
+ // * OPTIONS
+ // *
+ // * --no-pretty JSON is pretty-printed per default
+ // * Use this flag to enforce unformatted JSON
+ // */
+ // public function hostgroupAction()
+ // {
+ // $export = new ImportExport($this->db());
+ // echo $this->renderJson(
+ // $export->serializeAllHostGroups(),
+ // !$this->params->shift('no-pretty')
+ // );
+ // }
+ //
+ // /**
+ // * Export all IcingaServiceGroup definitions
+ // *
+ // * USAGE
+ // *
+ // * icingacli director export servicegroup [options]
+ // *
+ // * OPTIONS
+ // *
+ // * --no-pretty JSON is pretty-printed per default
+ // * Use this flag to enforce unformatted JSON
+ // */
+ // public function servicegroupAction()
+ // {
+ // $export = new ImportExport($this->db());
+ // echo $this->renderJson(
+ // $export->serializeAllServiceGroups(),
+ // !$this->params->shift('no-pretty')
+ // );
+ // }
+
+ /**
+ * Export all IcingaTemplateChoiceHost definitions
+ *
+ * USAGE
+ *
+ * icingacli director export hosttemplatechoices [options]
+ *
+ * OPTIONS
+ *
+ * --no-pretty JSON is pretty-printed per default
+ * Use this flag to enforce unformatted JSON
+ */
+ public function hosttemplatechoicesAction()
+ {
+ $export = new ImportExport($this->db());
+ echo $this->renderJson(
+ $export->serializeAllHostTemplateChoices(),
+ !$this->params->shift('no-pretty')
+ );
+ }
+}
diff --git a/application/clicommands/HealthCommand.php b/application/clicommands/HealthCommand.php
new file mode 100644
index 0000000..1635c50
--- /dev/null
+++ b/application/clicommands/HealthCommand.php
@@ -0,0 +1,80 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\CheckPlugin\PluginState;
+use Icinga\Module\Director\Cli\Command;
+use Icinga\Module\Director\Health;
+use Icinga\Module\Director\Cli\PluginOutputBeautifier;
+
+/**
+ * Check Icinga Director Health
+ *
+ * Use this command as a CheckPlugin to monitor your Icinga Director health
+ */
+class HealthCommand extends Command
+{
+ /**
+ * Run health checks
+ *
+ * Use this command to run all or a specific set of Health Checks.
+ *
+ * USAGE
+ *
+ * icingacli director health check [options]
+ *
+ * OPTIONS
+ *
+ * --check <name> Run only a specific set of checks
+ * valid names: config, sync, import, jobs, deployment
+ * --db <name> Use a specific Icinga Web DB resource
+ * --watch <seconds> Refresh every <second>. For interactive use only
+ */
+ public function checkAction()
+ {
+ $health = new Health();
+ if ($name = $this->params->get('db')) {
+ $health->setDbResourceName($name);
+ }
+
+ if ($name = $this->params->get('check')) {
+ $check = $health->getCheck($name);
+ echo PluginOutputBeautifier::beautify($check->getOutput(), $this->screen);
+
+ exit($check->getState()->getNumeric());
+ } else {
+ $state = new PluginState('OK');
+ $checks = $health->getAllChecks();
+
+ $output = [];
+ foreach ($checks as $check) {
+ $state->raise($check->getState());
+ $output[] = $check->getOutput();
+ }
+
+ if ($state->getNumeric() === 0) {
+ echo "Icinga Director: everything is fine\n\n";
+ } else {
+ echo "Icinga Director: there are problems\n\n";
+ }
+
+ $out = PluginOutputBeautifier::beautify(implode("\n", $output), $this->screen);
+ echo $out;
+
+ if (! $this->isBeingWatched()) {
+ exit($state->getNumeric());
+ }
+ }
+ }
+
+ /**
+ * Cli should provide this information, as it shifts the parameter
+ *
+ * @return bool
+ */
+ protected function isBeingWatched()
+ {
+ global $argv;
+ return in_array('--watch', $argv);
+ }
+}
diff --git a/application/clicommands/HostCommand.php b/application/clicommands/HostCommand.php
new file mode 100644
index 0000000..21ec5eb
--- /dev/null
+++ b/application/clicommands/HostCommand.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\ObjectCommand;
+
+/**
+ * Manage Icinga Hosts
+ *
+ * Use this command to show, create, modify or delete Icinga Host
+ * objects
+ */
+class HostCommand extends ObjectCommand
+{
+}
diff --git a/application/clicommands/HostgroupCommand.php b/application/clicommands/HostgroupCommand.php
new file mode 100644
index 0000000..88b17d9
--- /dev/null
+++ b/application/clicommands/HostgroupCommand.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\ObjectCommand;
+
+/**
+ * Manage Icinga Hostgroups
+ *
+ * Use this command to show, create, modify or delete Icinga Hostgroups
+ * objects
+ */
+class HostGroupCommand extends ObjectCommand
+{
+}
diff --git a/application/clicommands/HostgroupsCommand.php b/application/clicommands/HostgroupsCommand.php
new file mode 100644
index 0000000..1007a05
--- /dev/null
+++ b/application/clicommands/HostgroupsCommand.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\ObjectsCommand;
+
+/**
+ * Manage Icinga Hostgroups
+ *
+ * Use this command to list Icinga Hostgroup objects
+ */
+class HostgroupsCommand extends ObjectsCommand
+{
+}
diff --git a/application/clicommands/HostsCommand.php b/application/clicommands/HostsCommand.php
new file mode 100644
index 0000000..3008284
--- /dev/null
+++ b/application/clicommands/HostsCommand.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\ObjectsCommand;
+
+/**
+ * Manage Icinga Hosts
+ *
+ * Use this command to list Icinga Host objects
+ */
+class HostsCommand extends ObjectsCommand
+{
+}
diff --git a/application/clicommands/HousekeepingCommand.php b/application/clicommands/HousekeepingCommand.php
new file mode 100644
index 0000000..974e28d
--- /dev/null
+++ b/application/clicommands/HousekeepingCommand.php
@@ -0,0 +1,74 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Exception\MissingParameterException;
+use Icinga\Module\Director\Cli\Command;
+use Icinga\Module\Director\Db\Housekeeping;
+use Icinga\Module\Director\Db\MembershipHousekeeping;
+
+class HousekeepingCommand extends Command
+{
+ protected $housekeeping;
+
+ public function tasksAction()
+ {
+ if ($pending = $this->params->shift('pending')) {
+ $tasks = $this->housekeeping()->getPendingTaskSummary();
+ } else {
+ $tasks = $this->housekeeping()->getTaskSummary();
+ }
+
+ $len = array_reduce(
+ $tasks,
+ function ($max, $task) {
+ return max(
+ $max,
+ strlen($task->title) + strlen($task->name) + 3
+ );
+ }
+ );
+
+ if (count($tasks)) {
+ print "\n";
+ printf(" %-" . $len . "s | %s\n", 'Housekeeping task (name)', 'Count');
+ printf("-%-" . $len . "s-|-------\n", str_repeat('-', $len));
+ }
+
+ foreach ($tasks as $task) {
+ printf(
+ " %-" . $len . "s | %5d\n",
+ sprintf('%s (%s)', $task->title, $task->name),
+ $task->count
+ );
+ }
+
+ if (count($tasks)) {
+ print "\n";
+ }
+ }
+
+ public function runAction()
+ {
+ if (!$job = $this->params->shift()) {
+ throw new MissingParameterException(
+ 'Job is required, say ALL to run all pending jobs'
+ );
+ }
+
+ if ($job === 'ALL') {
+ $this->housekeeping()->runAllTasks();
+ } else {
+ $this->housekeeping()->runTask($job);
+ }
+ }
+
+ protected function housekeeping()
+ {
+ if ($this->housekeeping === null) {
+ $this->housekeeping = new Housekeeping($this->db());
+ }
+
+ return $this->housekeeping;
+ }
+}
diff --git a/application/clicommands/ImportCommand.php b/application/clicommands/ImportCommand.php
new file mode 100644
index 0000000..3edfff2
--- /dev/null
+++ b/application/clicommands/ImportCommand.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\Command;
+use Icinga\Module\Director\DirectorObject\Automation\ImportExport;
+use Icinga\Module\Director\Objects\ImportSource;
+
+/**
+ * Export Director Config Objects
+ */
+class ImportCommand extends Command
+{
+ /**
+ * Import ImportSource definitions
+ *
+ * USAGE
+ *
+ * icingacli director import importsources < importsources.json
+ *
+ * OPTIONS
+ */
+ public function importsourcesAction()
+ {
+ $json = file_get_contents('php://stdin');
+ $import = new ImportExport($this->db());
+ $count = $import->unserializeImportSources(json_decode($json));
+ echo "$count Import Sources have been imported\n";
+ }
+
+ // /**
+ // * Import an ImportSource definition
+ // *
+ // * USAGE
+ // *
+ // * icingacli director import importsource < importsource.json
+ // *
+ // * OPTIONS
+ // */
+ // public function importsourcection()
+ // {
+ // $json = file_get_contents('php://stdin');
+ // $object = ImportSource::import(json_decode($json), $this->db());
+ // $object->store();
+ // printf("Import Source '%s' has been imported\n", $object->getObjectName());
+ // }
+
+ /**
+ * Import SyncRule definitions
+ *
+ * USAGE
+ *
+ * icingacli director import syncrules < syncrules.json
+ */
+ public function syncrulesAction()
+ {
+ $json = file_get_contents('php://stdin');
+ $import = new ImportExport($this->db());
+ $count = $import->unserializeSyncRules(json_decode($json));
+ echo "$count Sync Rules have been imported\n";
+ }
+}
diff --git a/application/clicommands/ImportsourceCommand.php b/application/clicommands/ImportsourceCommand.php
new file mode 100644
index 0000000..477fdf5
--- /dev/null
+++ b/application/clicommands/ImportsourceCommand.php
@@ -0,0 +1,168 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Application\Benchmark;
+use Icinga\Module\Director\Cli\Command;
+use Icinga\Module\Director\Core\Json;
+use Icinga\Module\Director\Hook\ImportSourceHook;
+use Icinga\Module\Director\Objects\ImportSource;
+
+/**
+ * Deal with Director Import Sources
+ *
+ * Use this command to check or trigger your defined Import Sources
+ */
+class ImportsourceCommand extends Command
+{
+ /**
+ * List defined Import Sources
+ *
+ * This shows a table with your defined Import Sources, their IDs and
+ * current state. As triggering Imports requires an ID, this is where
+ * you can look up the desired ID.
+ *
+ * USAGE
+ *
+ * icingacli director importsource list
+ */
+ public function listAction()
+ {
+ $sources = ImportSource::loadAll($this->db());
+ if (empty($sources)) {
+ echo "No Import Source has been defined\n";
+
+ return;
+ }
+
+ printf("%4s | %s\n", 'ID', 'Import Source name');
+ printf("-----+%s\n", str_repeat('-', 64));
+
+ foreach ($sources as $source) {
+ $state = $source->get('import_state');
+ printf("%4d | %s\n", $source->get('id'), $source->get('source_name'));
+ printf(" | -> %s%s\n", $state, $state === 'failing' ? ': ' . $source->get('last_error_message') : '');
+ }
+ }
+
+ /**
+ * Check a given Import Source for changes
+ *
+ * This command fetches data from the given Import Source and compares it
+ * to the most recently imported data.
+ *
+ * USAGE
+ *
+ * icingacli director importsource check --id <id>
+ *
+ * OPTIONS
+ *
+ * --id <id> An Import Source ID. Use the list command to figure out
+ * --benchmark Show timing and memory usage details
+ */
+ public function checkAction()
+ {
+ $source = $this->getImportSource();
+ $source->checkForChanges();
+ $this->showImportStateDetails($source);
+ }
+
+ /**
+ * Fetch current data from a given Import Source
+ *
+ * This command fetches data from the given Import Source and outputs
+ * them as plain JSON
+ *
+ * USAGE
+ *
+ * icingacli director importsource fetch --id <id>
+ *
+ * OPTIONS
+ *
+ * --id <id> An Import Source ID. Use the list command to figure out
+ * --benchmark Show timing and memory usage details
+ */
+ public function fetchAction()
+ {
+ $source = $this->getImportSource();
+ $source->checkForChanges();
+ $hook = ImportSourceHook::forImportSource($source);
+ Benchmark::measure('Ready to fetch data');
+ $data = $hook->fetchData();
+ $source->applyModifiers($data);
+ Benchmark::measure(sprintf('Got %d rows, ready to dump JSON', count($data)));
+ echo Json::encode($data, JSON_PRETTY_PRINT);
+ }
+
+ /**
+ * Trigger an Import Run for a given Import Source
+ *
+ * This command fetches data from the given Import Source and stores it to
+ * the Director DB, so that the next related Sync Rule run can work with
+ * fresh data. In case data didn't change, nothing is going to be stored.
+ *
+ * USAGE
+ *
+ * icingacli director importsource run --id <id>
+ *
+ * OPTIONS
+ *
+ * --id <id> An Import Source ID. Use the list command to figure out
+ * --benchmark Show timing and memory usage details
+ */
+ public function runAction()
+ {
+ $source = $this->getImportSource();
+
+ if ($source->runImport()) {
+ print "New data has been imported\n";
+ $this->showImportStateDetails($source);
+ } else {
+ print "Nothing has been changed, imported data is still up to date\n";
+ }
+ }
+
+ /**
+ * @return ImportSource
+ */
+ protected function getImportSource()
+ {
+ return ImportSource::loadWithAutoIncId(
+ (int) $this->params->getRequired('id'),
+ $this->db()
+ );
+ }
+
+ /**
+ * @param ImportSource $source
+ * @throws \Icinga\Exception\IcingaException
+ */
+ protected function showImportStateDetails(ImportSource $source)
+ {
+ echo $this->getImportStateDescription($source) . "\n";
+ }
+
+ /**
+ * @param ImportSource $source
+ * @return string
+ * @throws \Icinga\Exception\IcingaException
+ */
+ protected function getImportStateDescription(ImportSource $source)
+ {
+ switch ($source->get('import_state')) {
+ case 'unknown':
+ return "It's currently unknown whether we are in sync with this"
+ . ' Import Source. You should either check for changes or'
+ . ' trigger a new Import Run.';
+ case 'in-sync':
+ return 'This Import Source is in sync';
+ case 'pending-changes':
+ return 'There are pending changes for this Import Source. You'
+ . ' should trigger a new Import Run.';
+ case 'failing':
+ return 'This Import Source failed: ' . $source->get('last_error_message');
+ default:
+ return 'This Import Source has an invalid state: ' . $source->get('import_state');
+ }
+ }
+}
diff --git a/application/clicommands/JobsCommand.php b/application/clicommands/JobsCommand.php
new file mode 100644
index 0000000..1c6297f
--- /dev/null
+++ b/application/clicommands/JobsCommand.php
@@ -0,0 +1,74 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Exception;
+use gipfl\Cli\Process;
+use gipfl\Protocol\JsonRpc\Connection;
+use gipfl\Protocol\NetString\StreamWrapper;
+use Icinga\Module\Director\Cli\Command;
+use Icinga\Module\Director\Daemon\JsonRpcLogWriter as JsonRpcLogWriterAlias;
+use Icinga\Module\Director\Daemon\Logger;
+use Icinga\Module\Director\Objects\DirectorJob;
+use React\EventLoop\Factory as Loop;
+use React\EventLoop\LoopInterface;
+use React\Stream\ReadableResourceStream;
+use React\Stream\WritableResourceStream;
+
+class JobsCommand extends Command
+{
+ public function runAction()
+ {
+ $this->app->getModuleManager()->loadEnabledModules();
+ $loop = Loop::create();
+ if ($this->params->get('rpc')) {
+ $this->enableRpc($loop);
+ }
+ if ($this->params->get('rpc') && $jobId = $this->params->get('id')) {
+ $exitCode = 1;
+ $jobId = (int) $jobId;
+ $loop->futureTick(function () use ($jobId, $loop, &$exitCode) {
+ Process::setTitle('icinga::director::job');
+ try {
+ $this->raiseLimits();
+ $job = DirectorJob::loadWithAutoIncId($jobId, $this->db());
+ Process::setTitle('icinga::director::job (' . $job->get('job_name') . ')');
+ if ($job->run()) {
+ $exitCode = 0;
+ } else {
+ $exitCode = 1;
+ }
+ } catch (Exception $e) {
+ Logger::error($e->getMessage());
+ $exitCode = 1;
+ }
+ $loop->futureTick(function () use ($loop) {
+ $loop->stop();
+ });
+ });
+ } else {
+ Logger::error('This command is no longer available. Please check our Upgrading documentation');
+ $exitCode = 1;
+ }
+
+ $loop->run();
+ exit($exitCode);
+ }
+
+ protected function enableRpc(LoopInterface $loop)
+ {
+ // stream_set_blocking(STDIN, 0);
+ // stream_set_blocking(STDOUT, 0);
+ // print_r(stream_get_meta_data(STDIN));
+ // stream_set_write_buffer(STDOUT, 0);
+ // ini_set('implicit_flush', 1);
+ $netString = new StreamWrapper(
+ new ReadableResourceStream(STDIN, $loop),
+ new WritableResourceStream(STDOUT, $loop)
+ );
+ $jsonRpc = new Connection();
+ $jsonRpc->handle($netString);
+
+ Logger::replaceRunningInstance(new JsonRpcLogWriterAlias($jsonRpc));
+ }
+}
diff --git a/application/clicommands/KickstartCommand.php b/application/clicommands/KickstartCommand.php
new file mode 100644
index 0000000..80aa183
--- /dev/null
+++ b/application/clicommands/KickstartCommand.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\Command;
+use Icinga\Module\Director\KickstartHelper;
+
+/**
+ * Kickstart a Director installation
+ *
+ * Once you prepared your DB resource this command retrieves information about
+ * unapplied database migration and helps applying them.
+ */
+class KickstartCommand extends Command
+{
+ /**
+ * Check whether a kickstart run is required
+ *
+ * This is the case when there is a kickstart.ini in your Directors config
+ * directory and no ApiUser in your Director DB.
+ *
+ * This is mostly for automation, so one could create a Puppet manifest
+ * as follows:
+ *
+ * exec { 'Icinga Director Kickstart':
+ * path => '/usr/local/bin:/usr/bin:/bin',
+ * command => 'icingacli director kickstart run',
+ * onlyif => 'icingacli director kickstart required',
+ * require => Exec['Icinga Director DB migration'],
+ * }
+ *
+ * Exit code 0 means that a kickstart run is required, code 2 that it is
+ * not.
+ */
+ public function requiredAction()
+ {
+ if ($this->kickstart()->isConfigured()) {
+ if ($this->kickstart()->isRequired()) {
+ if ($this->isVerbose) {
+ echo "Kickstart has been configured and should be triggered\n";
+ }
+
+ exit(0);
+ } else {
+ echo "Kickstart configured, execution is not required\n";
+ exit(1);
+ }
+ } else {
+ echo "Kickstart has not been configured\n";
+ exit(2);
+ }
+ }
+
+ /**
+ * Trigger the kickstart helper
+ *
+ * This will connect to the endpoint configured in your kickstart.ini,
+ * store the given API user and import existing objects like zones,
+ * endpoints and commands.
+ *
+ * /etc/icingaweb2/modules/director/kickstart.ini could look as follows:
+ *
+ * [config]
+ * endpoint = "master-node.example.com"
+ *
+ * ; Host can be an IP address or a hostname. Equals to endpoint name
+ * ; if not set:
+ * host = "127.0.0.1"
+ *
+ * ; Port is 5665 if none given
+ * port = 5665
+ *
+ * username = "director"
+ * password = "***"
+ *
+ */
+ public function runAction()
+ {
+ $this->raiseLimits();
+ $this->kickstart()->loadConfigFromFile()->run();
+ exit(0);
+ }
+
+ protected function kickstart()
+ {
+ return new KickstartHelper($this->db());
+ }
+}
diff --git a/application/clicommands/MigrationCommand.php b/application/clicommands/MigrationCommand.php
new file mode 100644
index 0000000..6a4d002
--- /dev/null
+++ b/application/clicommands/MigrationCommand.php
@@ -0,0 +1,66 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\Command;
+use Icinga\Module\Director\Db\Migrations;
+
+/**
+ * Handle DB migrations
+ *
+ * This command retrieves information about unapplied database migration and
+ * helps applying them.
+ */
+class MigrationCommand extends Command
+{
+ /**
+ * Check whether there are pending migrations
+ *
+ * This is mostly for automation, so one could create a Puppet manifest
+ * as follows:
+ *
+ * exec { 'Icinga Director DB migration':
+ * command => 'icingacli director migration run',
+ * onlyif => 'icingacli director migration pending',
+ * }
+ *
+ * Exit code 0 means that there are pending migrations, code 1 that there
+ * are no such. Use --verbose for human readable output
+ */
+ public function pendingAction()
+ {
+ if ($count = $this->migrations()->countPendingMigrations()) {
+ if ($this->isVerbose) {
+ if ($count === 1) {
+ echo "There is 1 pending migration\n";
+ } else {
+ printf("There are %d pending migrations\n", $count);
+ }
+ }
+
+ exit(0);
+ } else {
+ if ($this->isVerbose) {
+ echo "There are no pending migrations\n";
+ }
+
+ exit(1);
+ }
+ }
+
+ /**
+ * Run any pending migrations
+ *
+ * All pending migrations will be silently applied
+ */
+ public function runAction()
+ {
+ $this->migrations()->applyPendingMigrations();
+ exit(0);
+ }
+
+ protected function migrations()
+ {
+ return new Migrations($this->db());
+ }
+}
diff --git a/application/clicommands/NotificationCommand.php b/application/clicommands/NotificationCommand.php
new file mode 100644
index 0000000..bb5402a
--- /dev/null
+++ b/application/clicommands/NotificationCommand.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\ObjectCommand;
+
+/**
+ * Manage Icinga Notifications
+ *
+ * Use this command to show, create, modify or delete Icinga Notification
+ * objects
+ */
+class NotificationCommand extends ObjectCommand
+{
+}
diff --git a/application/clicommands/ServiceCommand.php b/application/clicommands/ServiceCommand.php
new file mode 100644
index 0000000..1bd21e7
--- /dev/null
+++ b/application/clicommands/ServiceCommand.php
@@ -0,0 +1,92 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Cli\Params;
+use Icinga\Module\Director\Cli\ObjectCommand;
+use Icinga\Module\Director\DirectorObject\Lookup\ServiceFinder;
+use Icinga\Module\Director\Objects\IcingaHost;
+use Icinga\Module\Director\Resolver\OverrideHelper;
+use InvalidArgumentException;
+
+/**
+ * Manage Icinga Services
+ *
+ * Use this command to show, create, modify or delete Icinga Service
+ * objects
+ */
+class ServiceCommand extends ObjectCommand
+{
+ public function setAction()
+ {
+ if (($host = $this->params->get('host')) && $this->params->shift('allow-overrides')) {
+ if ($this->setServiceProperties($host)) {
+ return;
+ }
+ }
+
+ parent::setAction();
+ }
+
+ protected function setServiceProperties($hostname)
+ {
+ $serviceName = $this->getName();
+ $host = IcingaHost::load($hostname, $this->db());
+ $service = ServiceFinder::find($host, $serviceName);
+ if ($service->requiresOverrides()) {
+ self::checkForOverrideSafety($this->params);
+ $properties = $this->remainingParams();
+ unset($properties['host']);
+ OverrideHelper::applyOverriddenVars($host, $serviceName, $properties);
+ $this->persistChanges($host, 'Host', $hostname . " (Overrides for $serviceName)", 'modified');
+ return true;
+ }
+
+ return false;
+ }
+
+ protected static function checkForOverrideSafety(Params $params)
+ {
+ if ($params->shift('replace')) {
+ throw new InvalidArgumentException('--replace is not available for Variable Overrides');
+ }
+ $appends = self::stripPrefixedProperties($params, 'append-');
+ $remove = self::stripPrefixedProperties($params, 'remove-');
+ OverrideHelper::assertVarsForOverrides($appends);
+ OverrideHelper::assertVarsForOverrides($remove);
+ if (!empty($appends)) {
+ throw new InvalidArgumentException('--append- is not available for Variable Overrides');
+ }
+ if (!empty($remove)) {
+ throw new InvalidArgumentException('--remove- is not available for Variable Overrides');
+ }
+ // Alternative, untested:
+ // $this->appendToArrayProperties($object, $appends);
+ // $this->removeProperties($object, $remove);
+ }
+
+ protected function load($name)
+ {
+ return parent::load($this->makeServiceKey($name));
+ }
+
+ protected function exists($name)
+ {
+ return parent::exists($this->makeServiceKey($name));
+ }
+
+ protected function makeServiceKey($name)
+ {
+ if ($host = $this->params->get('host')) {
+ return [
+ 'object_name' => $name,
+ 'host_id' => IcingaHost::load($host, $this->db())->get('id'),
+ ];
+ } else {
+ return [
+ 'object_name' => $name,
+ 'object_type' => 'template',
+ ];
+ }
+ }
+}
diff --git a/application/clicommands/ServicegroupCommand.php b/application/clicommands/ServicegroupCommand.php
new file mode 100644
index 0000000..1c732d4
--- /dev/null
+++ b/application/clicommands/ServicegroupCommand.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\ObjectCommand;
+
+/**
+ * Manage Icinga Servicegroups
+ *
+ * Use this command to show, create, modify or delete Icinga Servicegroups
+ * objects
+ */
+class ServiceGroupCommand extends ObjectCommand
+{
+}
diff --git a/application/clicommands/ServicesetCommand.php b/application/clicommands/ServicesetCommand.php
new file mode 100644
index 0000000..648a42c
--- /dev/null
+++ b/application/clicommands/ServicesetCommand.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+/**
+ * Manage Icinga Service Sets
+ *
+ * Use this command to show, create, modify or delete Icinga Service
+ * objects
+ */
+class ServicesetCommand extends ServiceCommand
+{
+ protected $type = 'ServiceSet';
+}
diff --git a/application/clicommands/ServicesetsCommand.php b/application/clicommands/ServicesetsCommand.php
new file mode 100644
index 0000000..54669d5
--- /dev/null
+++ b/application/clicommands/ServicesetsCommand.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\ObjectsCommand;
+
+/**
+ * Manage Icinga Service Sets
+ *
+ * Use this command to list Icinga Service Set objects
+ */
+class ServicesetsCommand extends ObjectsCommand
+{
+ protected $type = 'ServiceSet';
+}
diff --git a/application/clicommands/SyncruleCommand.php b/application/clicommands/SyncruleCommand.php
new file mode 100644
index 0000000..37a3f0e
--- /dev/null
+++ b/application/clicommands/SyncruleCommand.php
@@ -0,0 +1,195 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\Command;
+use Icinga\Module\Director\Objects\DirectorActivityLog;
+use Icinga\Module\Director\Objects\IcingaObject;
+use Icinga\Module\Director\Objects\SyncRule;
+use RuntimeException;
+
+/**
+ * Deal with Director Sync Rules
+ *
+ * Use this command to check or trigger your defined Sync Rules
+ */
+class SyncruleCommand extends Command
+{
+ /**
+ * List defined Sync Rules
+ *
+ * This shows a table with your defined Sync Rules, their IDs and
+ * current state. As triggering a Sync requires an ID, this is where
+ * you can look up the desired ID.
+ *
+ * USAGE
+ *
+ * icingacli director syncrule list
+ */
+ public function listAction()
+ {
+ $rules = SyncRule::loadAll($this->db());
+ if (empty($rules)) {
+ echo "No Sync Rule has been defined\n";
+
+ return;
+ }
+
+ printf("%4s | %s\n", 'ID', 'Sync Rule name');
+ printf("-----+%s\n", str_repeat('-', 64));
+
+ foreach ($rules as $rule) {
+ $state = $rule->get('sync_state');
+ printf("%4d | %s\n", $rule->get('id'), $rule->get('rule_name'));
+ printf(" | -> %s%s\n", $state, $state === 'failing' ? ': ' . $rule->get('last_error_message') : '');
+ }
+ }
+
+ /**
+ * Check a given Sync Rule for changes
+ *
+ * This command runs a complete Sync in memory but doesn't persist eventual changes.
+ *
+ * USAGE
+ *
+ * icingacli director syncrule check --id <id>
+ *
+ * OPTIONS
+ *
+ * --id <id> A Sync Rule ID. Use the list command to figure out
+ * --benchmark Show timing and memory usage details
+ */
+ public function checkAction()
+ {
+ $rule = $this->getSyncRule();
+ $hasChanges = $rule->checkForChanges();
+ $this->showSyncStateDetails($rule);
+ if ($hasChanges) {
+ $mods = $this->getExpectedModificationCounts($rule);
+ printf(
+ "Expected modifications: %dx create, %dx modify, %dx delete\n",
+ $mods->modify,
+ $mods->create,
+ $mods->delete
+ );
+ }
+
+ exit($this->getSyncStateExitCode($rule));
+ }
+
+ protected function getExpectedModificationCounts(SyncRule $rule)
+ {
+ $modifications = $rule->getExpectedModifications();
+
+ $create = 0;
+ $modify = 0;
+ $delete = 0;
+
+ /** @var IcingaObject $object */
+ foreach ($modifications as $object) {
+ if ($object->hasBeenLoadedFromDb()) {
+ if ($object->shouldBeRemoved()) {
+ $delete++;
+ } else {
+ $modify++;
+ }
+ } else {
+ $create++;
+ }
+ }
+
+ return (object) [
+ DirectorActivityLog::ACTION_CREATE => $create,
+ DirectorActivityLog::ACTION_MODIFY => $modify,
+ DirectorActivityLog::ACTION_DELETE => $delete,
+ ];
+ }
+
+ /**
+ * Trigger a Sync Run for a given Sync Rule
+ *
+ * This command builds new objects according your Sync Rule, compares them
+ * with existing ones and persists eventual changes.
+ *
+ * USAGE
+ *
+ * icingacli director syncrule run --id <id>
+ *
+ * OPTIONS
+ *
+ * --id <id> A Sync Rule ID. Use the list command to figure out
+ * --benchmark Show timing and memory usage details
+ */
+ public function runAction()
+ {
+ $rule = $this->getSyncRule();
+
+ if ($rule->applyChanges()) {
+ print "New data has been imported\n";
+ $this->showSyncStateDetails($rule);
+ } else {
+ print "Nothing has been changed, imported data is still up to date\n";
+ }
+ }
+
+ /**
+ * @return SyncRule
+ */
+ protected function getSyncRule()
+ {
+ return SyncRule::loadWithAutoIncId(
+ (int) $this->params->getRequired('id'),
+ $this->db()
+ );
+ }
+
+ /**
+ * @param SyncRule $rule
+ */
+ protected function showSyncStateDetails(SyncRule $rule)
+ {
+ echo $this->getSyncStateDescription($rule) . "\n";
+ }
+
+ /**
+ * @param SyncRule $rule
+ * @return string
+ */
+ protected function getSyncStateDescription(SyncRule $rule)
+ {
+ switch ($rule->get('sync_state')) {
+ case 'unknown':
+ return "It's currently unknown whether we are in sync with this rule."
+ . ' You should either check for changes or trigger a new Sync Run.';
+ case 'in-sync':
+ return 'This Sync Rule is in sync';
+ case 'pending-changes':
+ return 'There are pending changes for this Sync Rule. You should'
+ . ' trigger a new Sync Run.';
+ case 'failing':
+ return 'This Sync Rule failed: '. $rule->get('last_error_message');
+ default:
+ throw new RuntimeException('Invalid sync state: ' . $rule->get('sync_state'));
+ }
+ }
+
+ /**
+ * @param SyncRule $rule
+ * @return string
+ */
+ protected function getSyncStateExitCode(SyncRule $rule)
+ {
+ switch ($rule->get('sync_state')) {
+ case 'unknown':
+ return 3;
+ case 'in-sync':
+ return 0;
+ case 'pending-changes':
+ return 1;
+ case 'failing':
+ return 2;
+ default:
+ throw new RuntimeException('Invalid sync state: ' . $rule->get('sync_state'));
+ }
+ }
+}
diff --git a/application/clicommands/TimeperiodCommand.php b/application/clicommands/TimeperiodCommand.php
new file mode 100644
index 0000000..352289a
--- /dev/null
+++ b/application/clicommands/TimeperiodCommand.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\ObjectCommand;
+
+/**
+ * Manage Icinga Timeperiods
+ *
+ * Use this command to show, create, modify or delete Icinga Timeperiod
+ * objects
+ */
+class TimePeriodCommand extends ObjectCommand
+{
+}
diff --git a/application/clicommands/UserCommand.php b/application/clicommands/UserCommand.php
new file mode 100644
index 0000000..9c4c9d4
--- /dev/null
+++ b/application/clicommands/UserCommand.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\ObjectCommand;
+
+/**
+ * Manage Icinga Users
+ *
+ * Use this command to show, create, modify or delete Icinga User
+ * objects
+ */
+class UserCommand extends ObjectCommand
+{
+}
diff --git a/application/clicommands/UsergroupCommand.php b/application/clicommands/UsergroupCommand.php
new file mode 100644
index 0000000..04ba7c3
--- /dev/null
+++ b/application/clicommands/UsergroupCommand.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\ObjectCommand;
+
+/**
+ * Manage Icinga Usergroups
+ *
+ * Use this command to show, create, modify or delete Icinga Usergroup
+ * objects
+ */
+class UsergroupCommand extends ObjectCommand
+{
+}
diff --git a/application/clicommands/ZoneCommand.php b/application/clicommands/ZoneCommand.php
new file mode 100644
index 0000000..a5c45f9
--- /dev/null
+++ b/application/clicommands/ZoneCommand.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Icinga\Module\Director\Clicommands;
+
+use Icinga\Module\Director\Cli\ObjectCommand;
+
+/**
+ * Manage Icinga Zones
+ *
+ * Use this command to show, create, modify or delete Icinga Zone
+ * objects
+ */
+class ZoneCommand extends ObjectCommand
+{
+}