From a0901c4b7f2db488cb4fb3be2dd921a0308f4659 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 14:36:40 +0200 Subject: Adding upstream version 1.0.2. Signed-off-by: Daniel Baumann --- library/Icingadb/Command/IcingaApiCommand.php | 128 ++++++++ library/Icingadb/Command/IcingaCommand.php | 22 ++ .../Instance/ToggleInstanceFeatureCommand.php | 109 +++++++ .../Command/Object/AcknowledgeProblemCommand.php | 140 ++++++++ .../Icingadb/Command/Object/AddCommentCommand.php | 42 +++ library/Icingadb/Command/Object/CommandAuthor.php | 45 +++ .../Command/Object/DeleteCommentCommand.php | 52 +++ .../Command/Object/DeleteDowntimeCommand.php | 58 ++++ .../Icingadb/Command/Object/GetObjectCommand.php | 71 +++++ library/Icingadb/Command/Object/ObjectCommand.php | 51 +++ .../Command/Object/ProcessCheckResultCommand.php | 140 ++++++++ .../Object/PropagateHostDowntimeCommand.php | 42 +++ .../Object/RemoveAcknowledgementCommand.php | 13 + .../Command/Object/ScheduleCheckCommand.php | 86 +++++ .../Command/Object/ScheduleHostDowntimeCommand.php | 42 +++ .../Object/ScheduleServiceDowntimeCommand.php | 196 ++++++++++++ .../Object/SendCustomNotificationCommand.php | 44 +++ .../Command/Object/ToggleObjectFeatureCommand.php | 108 +++++++ .../Icingadb/Command/Object/WithCommentCommand.php | 50 +++ .../Command/Renderer/IcingaApiCommandRenderer.php | 322 +++++++++++++++++++ .../Renderer/IcingaCommandRendererInterface.php | 12 + .../Command/Transport/ApiCommandException.php | 14 + .../Command/Transport/ApiCommandTransport.php | 353 +++++++++++++++++++++ .../Command/Transport/CommandTransport.php | 130 ++++++++ .../Command/Transport/CommandTransportConfig.php | 31 ++ .../Transport/CommandTransportException.php | 14 + .../Transport/CommandTransportInterface.php | 23 ++ 27 files changed, 2338 insertions(+) create mode 100644 library/Icingadb/Command/IcingaApiCommand.php create mode 100644 library/Icingadb/Command/IcingaCommand.php create mode 100644 library/Icingadb/Command/Instance/ToggleInstanceFeatureCommand.php create mode 100644 library/Icingadb/Command/Object/AcknowledgeProblemCommand.php create mode 100644 library/Icingadb/Command/Object/AddCommentCommand.php create mode 100644 library/Icingadb/Command/Object/CommandAuthor.php create mode 100644 library/Icingadb/Command/Object/DeleteCommentCommand.php create mode 100644 library/Icingadb/Command/Object/DeleteDowntimeCommand.php create mode 100644 library/Icingadb/Command/Object/GetObjectCommand.php create mode 100644 library/Icingadb/Command/Object/ObjectCommand.php create mode 100644 library/Icingadb/Command/Object/ProcessCheckResultCommand.php create mode 100644 library/Icingadb/Command/Object/PropagateHostDowntimeCommand.php create mode 100644 library/Icingadb/Command/Object/RemoveAcknowledgementCommand.php create mode 100644 library/Icingadb/Command/Object/ScheduleCheckCommand.php create mode 100644 library/Icingadb/Command/Object/ScheduleHostDowntimeCommand.php create mode 100644 library/Icingadb/Command/Object/ScheduleServiceDowntimeCommand.php create mode 100644 library/Icingadb/Command/Object/SendCustomNotificationCommand.php create mode 100644 library/Icingadb/Command/Object/ToggleObjectFeatureCommand.php create mode 100644 library/Icingadb/Command/Object/WithCommentCommand.php create mode 100644 library/Icingadb/Command/Renderer/IcingaApiCommandRenderer.php create mode 100644 library/Icingadb/Command/Renderer/IcingaCommandRendererInterface.php create mode 100644 library/Icingadb/Command/Transport/ApiCommandException.php create mode 100644 library/Icingadb/Command/Transport/ApiCommandTransport.php create mode 100644 library/Icingadb/Command/Transport/CommandTransport.php create mode 100644 library/Icingadb/Command/Transport/CommandTransportConfig.php create mode 100644 library/Icingadb/Command/Transport/CommandTransportException.php create mode 100644 library/Icingadb/Command/Transport/CommandTransportInterface.php (limited to 'library/Icingadb/Command') diff --git a/library/Icingadb/Command/IcingaApiCommand.php b/library/Icingadb/Command/IcingaApiCommand.php new file mode 100644 index 0000000..f3f0c33 --- /dev/null +++ b/library/Icingadb/Command/IcingaApiCommand.php @@ -0,0 +1,128 @@ +setEndpoint($endpoint) + ->setData($data); + } + + /** + * Get the command data + * + * @return array + */ + public function getData(): array + { + if ($this->data === null) { + throw new \LogicException( + 'You are accessing an unset property. Please make sure to set it beforehand.' + ); + } + + return $this->data; + } + + /** + * Set the command data + * + * @param array $data + * + * @return $this + */ + public function setData(array $data): self + { + $this->data = $data; + + return $this; + } + + /** + * Get the name of the endpoint + * + * @return string + */ + public function getEndpoint(): string + { + if ($this->endpoint === null) { + throw new \LogicException( + 'You are accessing an unset property. Please make sure to set it beforehand.' + ); + } + + return $this->endpoint; + } + + /** + * Set the name of the endpoint + * + * @param string $endpoint + * + * @return $this + */ + public function setEndpoint(string $endpoint): self + { + $this->endpoint = $endpoint; + + return $this; + } + + /** + * Get the HTTP method to use + * + * @return string + */ + public function getMethod(): string + { + return $this->method; + } + + /** + * Set the HTTP method to use + * + * @param string $method All uppercase HTTP method name. Case-sensitive. + * + * @return $this + */ + public function setMethod(string $method): self + { + $this->method = $method; + + return $this; + } +} diff --git a/library/Icingadb/Command/IcingaCommand.php b/library/Icingadb/Command/IcingaCommand.php new file mode 100644 index 0000000..7b5c5cf --- /dev/null +++ b/library/Icingadb/Command/IcingaCommand.php @@ -0,0 +1,22 @@ +feature = $feature; + + return $this; + } + + /** + * Get the feature that is to be enabled or disabled + * + * @return string + */ + public function getFeature(): string + { + if ($this->feature === null) { + throw new \LogicException('You have to set the feature first before getting it.'); + } + + return $this->feature; + } + + /** + * Set whether the feature should be enabled or disabled + * + * @param bool $enabled + * + * @return $this + */ + public function setEnabled(bool $enabled = true): self + { + $this->enabled = $enabled; + + return $this; + } + + /** + * Get whether the feature should be enabled or disabled + * + * @return ?bool + */ + public function getEnabled() + { + return $this->enabled; + } +} diff --git a/library/Icingadb/Command/Object/AcknowledgeProblemCommand.php b/library/Icingadb/Command/Object/AcknowledgeProblemCommand.php new file mode 100644 index 0000000..baae24c --- /dev/null +++ b/library/Icingadb/Command/Object/AcknowledgeProblemCommand.php @@ -0,0 +1,140 @@ +sticky = $sticky; + + return $this; + } + + /** + * Is the acknowledgement sticky? + * + * @return bool + */ + public function getSticky(): bool + { + return $this->sticky; + } + + /** + * Set whether to send a notification about the acknowledgement + * + * @param bool $notify + * + * @return $this + */ + public function setNotify(bool $notify = true): self + { + $this->notify = $notify; + + return $this; + } + + /** + * Get whether to send a notification about the acknowledgement + * + * @return bool + */ + public function getNotify(): bool + { + return $this->notify; + } + + /** + * Set whether the comment associated with the acknowledgement is persistent + * + * @param bool $persistent + * + * @return $this + */ + public function setPersistent(bool $persistent = true): self + { + $this->persistent = $persistent; + + return $this; + } + + /** + * Is the comment associated with the acknowledgement is persistent? + * + * @return bool + */ + public function getPersistent(): bool + { + return $this->persistent; + } + + /** + * Set the time when the acknowledgement should expire + * + * @param int $expireTime + * + * @return $this + */ + public function setExpireTime(int $expireTime): self + { + $this->expireTime = $expireTime; + + return $this; + } + + /** + * Get the time when the acknowledgement should expire + * + * @return ?int + */ + public function getExpireTime() + { + return $this->expireTime; + } +} diff --git a/library/Icingadb/Command/Object/AddCommentCommand.php b/library/Icingadb/Command/Object/AddCommentCommand.php new file mode 100644 index 0000000..c853b25 --- /dev/null +++ b/library/Icingadb/Command/Object/AddCommentCommand.php @@ -0,0 +1,42 @@ +expireTime = $expireTime; + + return $this; + } + + /** + * Get the time when the acknowledgement should expire + * + * @return ?int + */ + public function getExpireTime() + { + return $this->expireTime; + } +} diff --git a/library/Icingadb/Command/Object/CommandAuthor.php b/library/Icingadb/Command/Object/CommandAuthor.php new file mode 100644 index 0000000..f323b63 --- /dev/null +++ b/library/Icingadb/Command/Object/CommandAuthor.php @@ -0,0 +1,45 @@ +author = $author; + + return $this; + } + + /** + * Get the author + * + * @return string + */ + public function getAuthor(): string + { + if ($this->author === null) { + throw new \LogicException( + 'You are accessing an unset property. Please make sure to set it beforehand.' + ); + } + + return $this->author; + } +} diff --git a/library/Icingadb/Command/Object/DeleteCommentCommand.php b/library/Icingadb/Command/Object/DeleteCommentCommand.php new file mode 100644 index 0000000..8bfc2a3 --- /dev/null +++ b/library/Icingadb/Command/Object/DeleteCommentCommand.php @@ -0,0 +1,52 @@ +commentName === null) { + throw new \LogicException( + 'You are accessing an unset property. Please make sure to set it beforehand.' + ); + } + + return $this->commentName; + } + + /** + * Set the name of the comment + * + * @param string $commentName + * + * @return $this + */ + public function setCommentName(string $commentName): self + { + $this->commentName = $commentName; + + return $this; + } +} diff --git a/library/Icingadb/Command/Object/DeleteDowntimeCommand.php b/library/Icingadb/Command/Object/DeleteDowntimeCommand.php new file mode 100644 index 0000000..a0867fa --- /dev/null +++ b/library/Icingadb/Command/Object/DeleteDowntimeCommand.php @@ -0,0 +1,58 @@ +downtimeName === null) { + throw new \LogicException( + 'You are accessing an unset property. Please make sure to set it beforehand.' + ); + } + + return $this->downtimeName; + } + + /** + * Set the name of the downtime (Icinga 2.4+) + * + * Required for removing the downtime via Icinga 2's API. + * + * @param string $downtimeName + * + * @return $this + */ + public function setDowntimeName(string $downtimeName): self + { + $this->downtimeName = $downtimeName; + + return $this; + } +} diff --git a/library/Icingadb/Command/Object/GetObjectCommand.php b/library/Icingadb/Command/Object/GetObjectCommand.php new file mode 100644 index 0000000..372a555 --- /dev/null +++ b/library/Icingadb/Command/Object/GetObjectCommand.php @@ -0,0 +1,71 @@ +object instanceof Service: + return $this->object->host->name . '!' . $this->object->name; + default: + return $this->object->name; + } + } + + /** + * Get the sub-route of the endpoint for this object + * + * @return string + */ + public function getObjectPluralType(): string + { + switch (true) { + case $this->object instanceof Host: + return 'hosts'; + case $this->object instanceof Service: + return 'services'; + default: + throw new LogicException(sprintf('Invalid object type %s provided', get_class($this->object))); + } + } + + /** + * Get the attributes to query + * + * @return ?array + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * Set the attributes to query + * + * @param array $attributes + * + * @return $this + */ + public function setAttributes(array $attributes): self + { + $this->attributes = $attributes; + + return $this; + } +} diff --git a/library/Icingadb/Command/Object/ObjectCommand.php b/library/Icingadb/Command/Object/ObjectCommand.php new file mode 100644 index 0000000..7dae799 --- /dev/null +++ b/library/Icingadb/Command/Object/ObjectCommand.php @@ -0,0 +1,51 @@ +object = $object; + + return $this; + } + + /** + * Get the involved object + * + * @return Model + */ + public function getObject(): Model + { + if ($this->object === null) { + throw new \LogicException( + 'You are accessing an unset property. Please make sure to set it beforehand.' + ); + } + + return $this->object; + } +} diff --git a/library/Icingadb/Command/Object/ProcessCheckResultCommand.php b/library/Icingadb/Command/Object/ProcessCheckResultCommand.php new file mode 100644 index 0000000..3d7e956 --- /dev/null +++ b/library/Icingadb/Command/Object/ProcessCheckResultCommand.php @@ -0,0 +1,140 @@ +status = $status; + + return $this; + } + + /** + * Get the status code of the host or service check result + * + * @return int + */ + public function getStatus(): int + { + if ($this->status === null) { + throw new \LogicException( + 'You are accessing an unset property. Please make sure to set it beforehand.' + ); + } + + return $this->status; + } + + /** + * Set the text output of the host or service check result + * + * @param string $output + * + * @return $this + */ + public function setOutput(string $output): self + { + $this->output = $output; + + return $this; + } + + /** + * Get the text output of the host or service check result + * + * @return ?string + */ + public function getOutput() + { + return $this->output; + } + + /** + * Set the performance data of the host or service check result + * + * @param string|null $performanceData + * + * @return $this + */ + public function setPerformanceData(string $performanceData = null): self + { + $this->performanceData = $performanceData; + + return $this; + } + + /** + * Get the performance data of the host or service check result + * + * @return ?string + */ + public function getPerformanceData() + { + return $this->performanceData; + } +} diff --git a/library/Icingadb/Command/Object/PropagateHostDowntimeCommand.php b/library/Icingadb/Command/Object/PropagateHostDowntimeCommand.php new file mode 100644 index 0000000..88964fb --- /dev/null +++ b/library/Icingadb/Command/Object/PropagateHostDowntimeCommand.php @@ -0,0 +1,42 @@ +triggered = $triggered; + + return $this; + } + + /** + * Get whether the downtime for child hosts are all set to be triggered by this' host downtime + * + * @return bool + */ + public function getTriggered(): bool + { + return $this->triggered; + } +} diff --git a/library/Icingadb/Command/Object/RemoveAcknowledgementCommand.php b/library/Icingadb/Command/Object/RemoveAcknowledgementCommand.php new file mode 100644 index 0000000..49a22a3 --- /dev/null +++ b/library/Icingadb/Command/Object/RemoveAcknowledgementCommand.php @@ -0,0 +1,13 @@ +checkTime = $checkTime; + + return $this; + } + + /** + * Get the time when the next check of a host or service is to be scheduled + * + * @return int Unix timestamp + */ + public function getCheckTime(): int + { + if ($this->checkTime === null) { + throw new \LogicException( + 'You are accessing an unset property. Please make sure to set it beforehand.' + ); + } + + return $this->checkTime; + } + + /** + * Set whether the check is forced + * + * @param bool $forced + * + * @return $this + */ + public function setForced(bool $forced = true): self + { + $this->forced = $forced; + + return $this; + } + + /** + * Get whether the check is forced + * + * @return bool + */ + public function getForced(): bool + { + return $this->forced; + } +} diff --git a/library/Icingadb/Command/Object/ScheduleHostDowntimeCommand.php b/library/Icingadb/Command/Object/ScheduleHostDowntimeCommand.php new file mode 100644 index 0000000..0e4d84f --- /dev/null +++ b/library/Icingadb/Command/Object/ScheduleHostDowntimeCommand.php @@ -0,0 +1,42 @@ +forAllServices = $forAllServices; + + return $this; + } + + /** + * Get whether to schedule a downtime for all services associated with a particular host + * + * @return bool + */ + public function getForAllServices(): bool + { + return $this->forAllServices; + } +} diff --git a/library/Icingadb/Command/Object/ScheduleServiceDowntimeCommand.php b/library/Icingadb/Command/Object/ScheduleServiceDowntimeCommand.php new file mode 100644 index 0000000..3bad28e --- /dev/null +++ b/library/Icingadb/Command/Object/ScheduleServiceDowntimeCommand.php @@ -0,0 +1,196 @@ +start = $start; + + return $this; + } + + /** + * Get the time when the downtime should start + * + * @return int Unix timestamp + */ + public function getStart(): int + { + if ($this->start === null) { + throw new \LogicException( + 'You are accessing an unset property. Please make sure to set it beforehand.' + ); + } + + return $this->start; + } + + /** + * Set the time when the downtime should end + * + * @param int $end Unix timestamp + * + * @return $this + */ + public function setEnd(int $end): self + { + $this->end = $end; + + return $this; + } + + /** + * Get the time when the downtime should end + * + * @return int Unix timestamp + */ + public function getEnd(): int + { + if ($this->start === null) { + throw new \LogicException( + 'You are accessing an unset property. Please make sure to set it beforehand.' + ); + } + + return $this->end; + } + + /** + * Set whether it's a fixed or flexible downtime + * + * @param boolean $fixed + * + * @return $this + */ + public function setFixed(bool $fixed = true): self + { + $this->fixed = $fixed; + + return $this; + } + + /** + * Is the downtime fixed? + * + * @return boolean + */ + public function getFixed(): bool + { + return $this->fixed; + } + + /** + * Set the ID of the downtime which triggers this downtime + * + * @param int $triggerId + * + * @return $this + */ + public function setTriggerId(int $triggerId): self + { + $this->triggerId = $triggerId; + + return $this; + } + + /** + * Get the ID of the downtime which triggers this downtime + * + * @return int|null + */ + public function getTriggerId() + { + return $this->triggerId; + } + + /** + * Set the duration in seconds the downtime must last if it's a flexible downtime + * + * @param int $duration + * + * @return $this + */ + public function setDuration(int $duration): self + { + $this->duration = $duration; + + return $this; + } + + /** + * Get the duration in seconds the downtime must last if it's a flexible downtime + * + * @return int|null + */ + public function getDuration() + { + return $this->duration; + } + + public function getName(): string + { + return 'ScheduleDowntime'; + } +} diff --git a/library/Icingadb/Command/Object/SendCustomNotificationCommand.php b/library/Icingadb/Command/Object/SendCustomNotificationCommand.php new file mode 100644 index 0000000..de90620 --- /dev/null +++ b/library/Icingadb/Command/Object/SendCustomNotificationCommand.php @@ -0,0 +1,44 @@ +forced; + } + + /** + * Set whether to force the notification + * + * @param bool $forced + * + * @return $this + */ + public function setForced(bool $forced = true): self + { + $this->forced = $forced; + + return $this; + } +} diff --git a/library/Icingadb/Command/Object/ToggleObjectFeatureCommand.php b/library/Icingadb/Command/Object/ToggleObjectFeatureCommand.php new file mode 100644 index 0000000..699a113 --- /dev/null +++ b/library/Icingadb/Command/Object/ToggleObjectFeatureCommand.php @@ -0,0 +1,108 @@ +feature = $feature; + + return $this; + } + + /** + * Get the feature that is to be enabled or disabled + * + * @return string + */ + public function getFeature(): string + { + if ($this->feature === null) { + throw new \LogicException( + 'You are accessing an unset property. Please make sure to set it beforehand.' + ); + } + + return $this->feature; + } + + /** + * Set whether the feature should be enabled or disabled + * + * @param bool $enabled + * + * @return $this + */ + public function setEnabled(bool $enabled = true): self + { + $this->enabled = $enabled; + + return $this; + } + + /** + * Get whether the feature should be enabled or disabled + * + * @return ?bool + */ + public function getEnabled() + { + return $this->enabled; + } +} diff --git a/library/Icingadb/Command/Object/WithCommentCommand.php b/library/Icingadb/Command/Object/WithCommentCommand.php new file mode 100644 index 0000000..299c998 --- /dev/null +++ b/library/Icingadb/Command/Object/WithCommentCommand.php @@ -0,0 +1,50 @@ +comment = $comment; + + return $this; + } + + /** + * Get the comment + * + * @return string + */ + public function getComment(): string + { + if ($this->comment === null) { + throw new \LogicException( + 'You are accessing an unset property. Please make sure to set it beforehand.' + ); + } + + return $this->comment; + } +} diff --git a/library/Icingadb/Command/Renderer/IcingaApiCommandRenderer.php b/library/Icingadb/Command/Renderer/IcingaApiCommandRenderer.php new file mode 100644 index 0000000..aa1cdbe --- /dev/null +++ b/library/Icingadb/Command/Renderer/IcingaApiCommandRenderer.php @@ -0,0 +1,322 @@ +app; + } + + /** + * Set the name of the Icinga application object + * + * @param string $app + * + * @return $this + */ + public function setApp(string $app): self + { + $this->app = $app; + + return $this; + } + + /** + * Apply filter to query data + * + * @param array $data + * @param Model $object + * + * @return void + */ + protected function applyFilter(array &$data, Model $object) + { + if ($object instanceof Host) { + $data['host'] = $object->name; + } else { + /** @var Service $object */ + $data['service'] = sprintf('%s!%s', $object->host->name, $object->name); + } + } + + /** + * Render a command + * + * @param IcingaCommand $command + * + * @return IcingaApiCommand + */ + public function render(IcingaCommand $command): IcingaApiCommand + { + $renderMethod = 'render' . $command->getName(); + if (! method_exists($this, $renderMethod)) { + throw new InvalidArgumentException( + sprintf('Can\'t render command. Method %s not found', $renderMethod) + ); + } + + return $this->$renderMethod($command); + } + + public function renderGetObject(GetObjectCommand $command): IcingaApiCommand + { + $endpoint = sprintf( + 'objects/%s/%s', + $command->getObjectPluralType(), + rawurlencode($command->getObjectName()) + ); + + $data = [ + 'all_joins' => 1, + 'attrs' => $command->getAttributes() ?: [] + ]; + + return IcingaApiCommand::create($endpoint, $data)->setMethod('GET'); + } + + public function renderAddComment(AddCommentCommand $command): IcingaApiCommand + { + $endpoint = 'actions/add-comment'; + $data = [ + 'author' => $command->getAuthor(), + 'comment' => $command->getComment() + ]; + + if ($command->getExpireTime() !== null) { + $data['expiry'] = $command->getExpireTime(); + } + + $this->applyFilter($data, $command->getObject()); + return IcingaApiCommand::create($endpoint, $data); + } + + public function renderSendCustomNotification(SendCustomNotificationCommand $command): IcingaApiCommand + { + $endpoint = 'actions/send-custom-notification'; + $data = [ + 'author' => $command->getAuthor(), + 'comment' => $command->getComment(), + 'force' => $command->getForced() + ]; + + $this->applyFilter($data, $command->getObject()); + return IcingaApiCommand::create($endpoint, $data); + } + + public function renderProcessCheckResult(ProcessCheckResultCommand $command): IcingaApiCommand + { + $endpoint = 'actions/process-check-result'; + $data = [ + 'exit_status' => $command->getStatus(), + 'plugin_output' => $command->getOutput(), + 'performance_data' => $command->getPerformanceData() + ]; + + $this->applyFilter($data, $command->getObject()); + return IcingaApiCommand::create($endpoint, $data); + } + + public function renderScheduleCheck(ScheduleCheckCommand $command): IcingaApiCommand + { + $endpoint = 'actions/reschedule-check'; + $data = [ + 'next_check' => $command->getCheckTime(), + 'force' => $command->getForced() + ]; + + $this->applyFilter($data, $command->getObject()); + return IcingaApiCommand::create($endpoint, $data); + } + + public function renderScheduleDowntime(ScheduleServiceDowntimeCommand $command): IcingaApiCommand + { + $endpoint = 'actions/schedule-downtime'; + $data = [ + 'author' => $command->getAuthor(), + 'comment' => $command->getComment(), + 'start_time' => $command->getStart(), + 'end_time' => $command->getEnd(), + 'duration' => $command->getDuration(), + 'fixed' => $command->getFixed(), + 'trigger_name' => $command->getTriggerId() + ]; + + if ($command instanceof PropagateHostDowntimeCommand) { + $data['child_options'] = $command->getTriggered() ? 1 : 2; + } + + if ($command instanceof ScheduleHostDowntimeCommand && $command->getForAllServices()) { + $data['all_services'] = true; + } + + $this->applyFilter($data, $command->getObject()); + return IcingaApiCommand::create($endpoint, $data); + } + + public function renderAcknowledgeProblem(AcknowledgeProblemCommand $command): IcingaApiCommand + { + $endpoint = 'actions/acknowledge-problem'; + $data = [ + 'author' => $command->getAuthor(), + 'comment' => $command->getComment(), + 'sticky' => $command->getSticky(), + 'notify' => $command->getNotify(), + 'persistent' => $command->getPersistent() + ]; + + if ($command->getExpireTime() !== null) { + $data['expiry'] = $command->getExpireTime(); + } + + $this->applyFilter($data, $command->getObject()); + return IcingaApiCommand::create($endpoint, $data); + } + + public function renderToggleObjectFeature(ToggleObjectFeatureCommand $command): IcingaApiCommand + { + switch ($command->getFeature()) { + case ToggleObjectFeatureCommand::FEATURE_ACTIVE_CHECKS: + $attr = 'enable_active_checks'; + break; + case ToggleObjectFeatureCommand::FEATURE_PASSIVE_CHECKS: + $attr = 'enable_passive_checks'; + break; + case ToggleObjectFeatureCommand::FEATURE_NOTIFICATIONS: + $attr = 'enable_notifications'; + break; + case ToggleObjectFeatureCommand::FEATURE_EVENT_HANDLER: + $attr = 'enable_event_handler'; + break; + case ToggleObjectFeatureCommand::FEATURE_FLAP_DETECTION: + $attr = 'enable_flapping'; + break; + default: + throw new InvalidArgumentException($command->getFeature()); + } + + $endpoint = 'objects/'; + $object = $command->getObject(); + if ($object instanceof Host) { + $endpoint .= 'hosts'; + } else { + /** @var Service $object */ + $endpoint .= 'services'; + } + + $data = [ + 'attrs' => [ + $attr => $command->getEnabled() + ] + ]; + + $this->applyFilter($data, $object); + return IcingaApiCommand::create($endpoint, $data); + } + + public function renderDeleteComment(DeleteCommentCommand $command): IcingaApiCommand + { + $endpoint = 'actions/remove-comment'; + $data = [ + 'author' => $command->getAuthor(), + 'comment' => $command->getCommentName() + ]; + + return IcingaApiCommand::create($endpoint, $data); + } + + public function renderDeleteDowntime(DeleteDowntimeCommand $command): IcingaApiCommand + { + $endpoint = 'actions/remove-downtime'; + $data = [ + 'author' => $command->getAuthor(), + 'downtime' => $command->getDowntimeName() + ]; + + return IcingaApiCommand::create($endpoint, $data); + } + + public function renderRemoveAcknowledgement(RemoveAcknowledgementCommand $command): IcingaApiCommand + { + $endpoint = 'actions/remove-acknowledgement'; + $data = ['author' => $command->getAuthor()]; + + $this->applyFilter($data, $command->getObject()); + return IcingaApiCommand::create($endpoint, $data); + } + + public function renderToggleInstanceFeature(ToggleInstanceFeatureCommand $command): IcingaApiCommand + { + $endpoint = 'objects/icingaapplications/' . $this->getApp(); + + switch ($command->getFeature()) { + case ToggleInstanceFeatureCommand::FEATURE_ACTIVE_HOST_CHECKS: + $attr = 'enable_host_checks'; + break; + case ToggleInstanceFeatureCommand::FEATURE_ACTIVE_SERVICE_CHECKS: + $attr = 'enable_service_checks'; + break; + case ToggleInstanceFeatureCommand::FEATURE_EVENT_HANDLERS: + $attr = 'enable_event_handlers'; + break; + case ToggleInstanceFeatureCommand::FEATURE_FLAP_DETECTION: + $attr = 'enable_flapping'; + break; + case ToggleInstanceFeatureCommand::FEATURE_NOTIFICATIONS: + $attr = 'enable_notifications'; + break; + case ToggleInstanceFeatureCommand::FEATURE_PERFORMANCE_DATA: + $attr = 'enable_perfdata'; + break; + default: + throw new InvalidArgumentException($command->getFeature()); + } + + $data = [ + 'attrs' => [ + $attr => $command->getEnabled() + ] + ]; + + return IcingaApiCommand::create($endpoint, $data); + } +} diff --git a/library/Icingadb/Command/Renderer/IcingaCommandRendererInterface.php b/library/Icingadb/Command/Renderer/IcingaCommandRendererInterface.php new file mode 100644 index 0000000..50dd90c --- /dev/null +++ b/library/Icingadb/Command/Renderer/IcingaCommandRendererInterface.php @@ -0,0 +1,12 @@ +renderer = new IcingaApiCommandRenderer(); + } + + /** + * Set the name of the Icinga application object + * + * @param string $app + * + * @return $this + */ + public function setApp(string $app): self + { + $this->renderer->setApp($app); + + return $this; + } + + /** + * Get the API host + * + * @return string + */ + public function getHost(): string + { + if ($this->host === null) { + throw new \LogicException( + 'You are accessing an unset property. Please make sure to set it beforehand.' + ); + } + + return $this->host; + } + + /** + * Set the API host + * + * @param string $host + * + * @return $this + */ + public function setHost(string $host): self + { + $this->host = $host; + + return $this; + } + + /** + * Get the API password + * + * @return string + */ + public function getPassword(): string + { + if ($this->password === null) { + throw new \LogicException( + 'You are accessing an unset property. Please make sure to set it beforehand.' + ); + } + + return $this->password; + } + + /** + * Set the API password + * + * @param string $password + * + * @return $this + */ + public function setPassword(string $password): self + { + $this->password = $password; + + return $this; + } + + /** + * Get the API port + * + * @return int + */ + public function getPort(): int + { + return $this->port; + } + + /** + * Set the API port + * + * @param int $port + * + * @return $this + */ + public function setPort(int $port): self + { + $this->port = $port; + + return $this; + } + + /** + * Get the API username + * + * @return string + */ + public function getUsername(): string + { + if ($this->username === null) { + throw new \LogicException( + 'You are accessing an unset property. Please make sure to set it beforehand.' + ); + } + + return $this->username; + } + + /** + * Set the API username + * + * @param string $username + * + * @return $this + */ + public function setUsername(string $username): self + { + $this->username = $username; + + return $this; + } + + /** + * Get URI for endpoint + * + * @param string $endpoint + * + * @return string + */ + protected function getUriFor(string $endpoint): string + { + return sprintf('https://%s:%u/v1/%s', $this->getHost(), $this->getPort(), $endpoint); + } + + /** + * Send the given command to the icinga2's REST API + * + * @param IcingaApiCommand $command + * + * @return mixed + */ + protected function sendCommand(IcingaApiCommand $command) + { + Logger::debug( + 'Sending Icinga command "%s" to the API "%s:%u"', + $command->getEndpoint(), + $this->getHost(), + $this->getPort() + ); + + $data = $command->getData(); + $payload = Json::encode($data); + AuditHook::logActivity( + 'monitoring/command', + "Issued command {$command->getEndpoint()} with the following payload: $payload", + $data + ); + + $headers = ['Accept' => 'application/json']; + if ($command->getMethod() !== 'POST') { + $headers['X-HTTP-Method-Override'] = $command->getMethod(); + } + + try { + $response = (new Client()) + ->post($this->getUriFor($command->getEndpoint()), [ + 'auth' => [$this->getUsername(), $this->getPassword()], + 'headers' => $headers, + 'json' => $command->getData(), + 'http_errors' => false, + 'verify' => false + ]); + } catch (GuzzleException $e) { + throw new CommandTransportException( + 'Can\'t connect to the Icinga 2 API: %u %s', + $e->getCode(), + $e->getMessage() + ); + } + + try { + $responseData = Json::decode((string) $response->getBody(), true); + } catch (JsonDecodeException $e) { + throw new CommandTransportException( + 'Got invalid JSON response from the Icinga 2 API: %s', + $e->getMessage() + ); + } + + if (! isset($responseData['results']) || empty($responseData['results'])) { + if (isset($responseData['error'])) { + throw new ApiCommandException( + 'Can\'t send external Icinga command: %u %s', + $responseData['error'], + $responseData['status'] + ); + } + + return; + } + + $errorResult = $responseData['results'][0]; + if (isset($errorResult['code']) && ($errorResult['code'] < 200 || $errorResult['code'] >= 300)) { + throw new ApiCommandException( + 'Can\'t send external Icinga command: %u %s', + $errorResult['code'], + $errorResult['status'] + ); + } + + return $responseData['results']; + } + + /** + * Send the Icinga command over the Icinga 2 API + * + * @param IcingaCommand $command + * @param int|null $now + * + * @throws CommandTransportException + * + * @return mixed + */ + public function send(IcingaCommand $command, int $now = null) + { + return $this->sendCommand($this->renderer->render($command)); + } + + /** + * Try to connect to the API + * + * @return void + * + * @throws CommandTransportException In case the connection was not successful + */ + public function probe() + { + try { + $response = (new Client(['timeout' => 15])) + ->get($this->getUriFor(''), [ + 'auth' => [$this->getUsername(), $this->getPassword()], + 'headers' => ['Accept' => 'application/json'], + 'http_errors' => false, + 'verify' => false + ]); + } catch (GuzzleException $e) { + throw new CommandTransportException( + 'Can\'t connect to the Icinga 2 API: %u %s', + $e->getCode(), + $e->getMessage() + ); + } + + try { + $responseData = Json::decode((string) $response->getBody(), true); + } catch (JsonDecodeException $e) { + throw new CommandTransportException( + 'Got invalid JSON response from the Icinga 2 API: %s', + $e->getMessage() + ); + } + + if (! isset($responseData['results']) || empty($responseData['results'])) { + throw new CommandTransportException( + 'Got invalid response from the Icinga 2 API: %s', + JSON::encode($responseData) + ); + } + + $result = array_pop($responseData['results']); + if (! isset($result['user']) || $result['user'] !== $this->getUsername()) { + throw new CommandTransportException( + 'Got invalid response from the Icinga 2 API: %s', + JSON::encode($responseData) + ); + } + } +} diff --git a/library/Icingadb/Command/Transport/CommandTransport.php b/library/Icingadb/Command/Transport/CommandTransport.php new file mode 100644 index 0000000..ea125bc --- /dev/null +++ b/library/Icingadb/Command/Transport/CommandTransport.php @@ -0,0 +1,130 @@ +isEmpty()) { + throw new ConfigurationError( + t('No command transports have been configured in "%s".'), + $config->getConfigFile() + ); + } + + static::$config = $config; + } + + return static::$config; + } + + /** + * Create a transport from config + * + * @param ConfigObject $config + * + * @return ApiCommandTransport + * + * @throws ConfigurationError + */ + public static function createTransport(ConfigObject $config): ApiCommandTransport + { + $config = clone $config; + switch (strtolower($config->transport)) { + case ApiCommandTransport::TRANSPORT: + $transport = new ApiCommandTransport(); + break; + default: + throw new ConfigurationError( + t('Cannot create command transport "%s". Invalid transport defined in "%s". Use one of: %s.'), + $config->transport, + static::getConfig()->getConfigFile(), + join(', ', [ApiCommandTransport::TRANSPORT]) + ); + } + + unset($config->transport); + foreach ($config as $key => $value) { + $method = 'set' . ucfirst($key); + if (! method_exists($transport, $method)) { + // Ignore settings from config that don't have a setter on the transport instead of throwing an + // exception here because the transport should throw an exception if it's not fully set up + // when being about to send a command + continue; + } + + $transport->$method($value); + } + + return $transport; + } + + /** + * Send the given command over an appropriate Icinga command transport + * + * This will try one configured transport after another until the command has been successfully sent. + * + * @param IcingaCommand $command The command to send + * @param int|null $now Timestamp of the command or null for now + * + * @throws CommandTransportException If sending the Icinga command failed + * + * @return mixed + */ + public function send(IcingaCommand $command, int $now = null) + { + $errors = []; + + foreach (static::getConfig() as $name => $transportConfig) { + $transport = static::createTransport($transportConfig); + + try { + $result = $transport->send($command, $now); + } catch (CommandTransportException $e) { + Logger::error($e); + $errors[] = sprintf('%s: %s.', $name, rtrim($e->getMessage(), '.')); + continue; // Try the next transport + } + + return $result; // The command was successfully sent + } + + if (! empty($errors)) { + throw new CommandTransportException(implode("\n", $errors)); + } + + throw new CommandTransportException(t( + 'Failed to send external Icinga command. No transport has been configured' + . ' for this instance. Please contact your Icinga Web administrator.' + )); + } +} diff --git a/library/Icingadb/Command/Transport/CommandTransportConfig.php b/library/Icingadb/Command/Transport/CommandTransportConfig.php new file mode 100644 index 0000000..e17fa04 --- /dev/null +++ b/library/Icingadb/Command/Transport/CommandTransportConfig.php @@ -0,0 +1,31 @@ + [ + 'name' => 'commandtransports', + 'module' => 'icingadb', + 'keyColumn' => 'name' + ] + ]; + + protected $queryColumns = [ + 'transport' => [ + 'name', + 'transport', + + // API options + 'host', + 'port', + 'username', + 'password' + ] + ]; +} diff --git a/library/Icingadb/Command/Transport/CommandTransportException.php b/library/Icingadb/Command/Transport/CommandTransportException.php new file mode 100644 index 0000000..2ca89d9 --- /dev/null +++ b/library/Icingadb/Command/Transport/CommandTransportException.php @@ -0,0 +1,14 @@ +