- 
                Notifications
    You must be signed in to change notification settings 
- Fork 1
Move Rule evaluation to Source #324
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
5706b44    to
    e948c8f      
    Compare
  
    | Likewise to the Icinga DB PR, I've changed quite a few things as well, so I'll summarize the changes here for you: 
 After applying the extra tags and Icinga 2 removal changes, Icinga Notifications Web will be broken, so you've to work around this with a temporary fix, by removing and commenting out some code parts: Diffdiff --git a/application/controllers/EventRuleController.php b/application/controllers/EventRuleController.php
index c96c88b..3e0d7c2 100644
--- a/application/controllers/EventRuleController.php
+++ b/application/controllers/EventRuleController.php
@@ -10,7 +10,6 @@ use Icinga\Module\Notifications\Common\Links;
 use Icinga\Module\Notifications\Forms\EventRuleForm;
 use Icinga\Module\Notifications\Forms\SaveEventRuleForm;
 use Icinga\Module\Notifications\Model\Rule;
-use Icinga\Module\Notifications\Web\Control\SearchBar\ExtraTagSuggestions;
 use Icinga\Module\Notifications\Widget\EventRuleConfig;
 use Icinga\Web\Notification;
 use Icinga\Web\Session;
@@ -177,9 +176,6 @@ class EventRuleController extends CompatController
      */
     public function completeAction(): void
     {
-        $suggestions = new ExtraTagSuggestions();
-        $suggestions->forRequest($this->getServerRequest());
-        $this->getDocument()->add($suggestions);
     }
diff --git a/application/forms/SourceForm.php b/application/forms/SourceForm.php
index dcfbc63..b5f4523 100644
--- a/application/forms/SourceForm.php
+++ b/application/forms/SourceForm.php
@@ -71,7 +71,7 @@ class SourceForm extends CompatForm
             || ($chosenType === null && $configuredType === null)
             || ($chosenType === null && $configuredType === Source::ICINGA_TYPE_NAME);
-        if ($showIcingaApiConfig) {
+        if (false && $showIcingaApiConfig) {
             // TODO: Shouldn't be necessary: https://github.com/Icinga/ipl-html/issues/131
             $this->clearPopulatedValue('type');
@@ -203,11 +203,11 @@ class SourceForm extends CompatForm
             );
             // Preserves (some) entered data even if the user switches between types
-            $this->addElement('hidden', 'icinga2_base_url');
-            $this->addElement('hidden', 'icinga2_auth_user');
-            $this->addElement('hidden', 'icinga2_insecure_tls');
-            $this->addElement('hidden', 'icinga2_common_name');
-            $this->addElement('hidden', 'icinga2_ca_pem');
+            //$this->addElement('hidden', 'icinga2_base_url');
+            //$this->addElement('hidden', 'icinga2_auth_user');
+            //$this->addElement('hidden', 'icinga2_insecure_tls');
+            //$this->addElement('hidden', 'icinga2_common_name');
+            //$this->addElement('hidden', 'icinga2_ca_pem');
         }
         $this->addElement(
@@ -376,12 +376,12 @@ class SourceForm extends CompatForm
         return [
             'name'                  => $source->name,
             'type'                  => $source->type,
-            'icinga2_base_url'      => $source->icinga2_base_url,
-            'icinga2_auth_user'     => $source->icinga2_auth_user,
-            'icinga2_auth_pass'     => $source->icinga2_auth_pass,
-            'icinga2_ca_pem'        => $source->icinga2_ca_pem,
-            'icinga2_common_name'   => $source->icinga2_common_name,
-            'icinga2_insecure_tls'  => $source->icinga2_insecure_tls
+            //'icinga2_base_url'      => $source->icinga2_base_url,
+            //'icinga2_auth_user'     => $source->icinga2_auth_user,
+            //'icinga2_auth_pass'     => $source->icinga2_auth_pass,
+            //'icinga2_ca_pem'        => $source->icinga2_ca_pem,
+            //'icinga2_common_name'   => $source->icinga2_common_name,
+            //'icinga2_insecure_tls'  => $source->icinga2_insecure_tls
         ];
     }
 }
diff --git a/library/Notifications/Model/ExtraTag.php b/library/Notifications/Model/ExtraTag.php
deleted file mode 100644
index 07a27f8..0000000
--- a/library/Notifications/Model/ExtraTag.php
+++ /dev/null
@@ -1,28 +0,0 @@
-<?php
-
-/* Icinga Notifications Web | (c) 2023 Icinga GmbH | GPLv2 */
-
-namespace Icinga\Module\Notifications\Model;
-
-use Icinga\Module\Notifications\Model\Behavior\ObjectTags;
-use ipl\Orm\Behaviors;
-use ipl\Sql\Connection;
-
-class ExtraTag extends ObjectExtraTag
-{
-    /**
-     * @internal Don't use. This model acts only as relation target and is not supposed to be directly used as query
-     *           target. Use {@see ObjectExtraTag} instead.
-     */
-    public static function on(Connection $_)
-    {
-        throw new \LogicException('Documentation says: DO NOT USE. Can\'t you read?');
-    }
-
-    public function createBehaviors(Behaviors $behaviors): void
-    {
-        parent::createBehaviors($behaviors);
-
-        $behaviors->add(new ObjectTags());
-    }
-}
diff --git a/library/Notifications/Model/ObjectExtraTag.php b/library/Notifications/Model/ObjectExtraTag.php
deleted file mode 100644
index df7beb7..0000000
--- a/library/Notifications/Model/ObjectExtraTag.php
+++ /dev/null
@@ -1,52 +0,0 @@
-<?php
-
-/* Icinga Notifications Web | (c) 2023 Icinga GmbH | GPLv2 */
-
-namespace Icinga\Module\Notifications\Model;
-
-use ipl\Orm\Behavior\Binary;
-use ipl\Orm\Behaviors;
-use ipl\Orm\Model;
-use ipl\Orm\Query;
-use ipl\Orm\Relations;
-
-/**
- * ObjectExtraTag database model
- *
- * @property string $object_id
- * @property string $tag
- * @property string $value
- *
- * @property Query|Objects $object
- */
-class ObjectExtraTag extends Model
-{
-    public function getTableName(): string
-    {
-        return 'object_extra_tag';
-    }
-
-    public function getKeyName(): array
-    {
-        return ['object_id', 'tag'];
-    }
-
-    public function getColumns(): array
-    {
-        return [
-            'object_id',
-            'tag',
-            'value'
-        ];
-    }
-
-    public function createBehaviors(Behaviors $behaviors): void
-    {
-        $behaviors->add(new Binary(['object_id']));
-    }
-
-    public function createRelations(Relations $relations): void
-    {
-        $relations->belongsTo('object', Objects::class);
-    }
-}
diff --git a/library/Notifications/Model/Objects.php b/library/Notifications/Model/Objects.php
index c621056..3f37a27 100644
--- a/library/Notifications/Model/Objects.php
+++ b/library/Notifications/Model/Objects.php
@@ -25,8 +25,6 @@ use ipl\Orm\Relations;
  * @property Query|Event $event
  * @property Query|Incident $incident
  * @property Query|Tag $tag
- * @property Query|ObjectExtraTag $object_extra_tag
- * @property Query|ExtraTag $extra_tag
  * @property Query|Source $source
  * @property array<string, string> $id_tags
  */
@@ -81,10 +79,6 @@ class Objects extends Model
         $relations->hasMany('object_id_tag', ObjectIdTag::class);
         $relations->hasMany('tag', Tag::class);
-        $relations->hasMany('object_extra_tag', ObjectExtraTag::class)
-            ->setJoinType('LEFT');
-        $relations->hasMany('extra_tag', ExtraTag::class)
-            ->setJoinType('LEFT');
         $relations->belongsTo('source', Source::class)->setJoinType('LEFT');
     }
diff --git a/library/Notifications/Model/Source.php b/library/Notifications/Model/Source.php
index 973bbb7..e8059fc 100644
--- a/library/Notifications/Model/Source.php
+++ b/library/Notifications/Model/Source.php
@@ -19,12 +19,6 @@ use ipl\Web\Widget\Icon;
  * @property string $type Type identifier
  * @property string $name The user-defined name
  * @property ?string $listener_password_hash
- * @property ?string $icinga2_base_url
- * @property ?string $icinga2_auth_user
- * @property ?string $icinga2_auth_pass
- * @property ?string $icinga2_ca_pem
- * @property ?string $icinga2_common_name
- * @property string $icinga2_insecure_tls
  * @property DateTime $changed_at
  * @property bool $deleted
  *
@@ -51,12 +45,6 @@ class Source extends Model
             'type',
             'name',
             'listener_password_hash',
-            'icinga2_base_url',
-            'icinga2_auth_user',
-            'icinga2_auth_pass',
-            'icinga2_ca_pem',
-            'icinga2_common_name',
-            'icinga2_insecure_tls',
             'changed_at',
             'deleted'
         ];
diff --git a/library/Notifications/Web/Control/SearchBar/ExtraTagSuggestions.php b/library/Notifications/Web/Control/SearchBar/ExtraTagSuggestions.php
deleted file mode 100644
index 88f52c2..0000000
--- a/library/Notifications/Web/Control/SearchBar/ExtraTagSuggestions.php
+++ /dev/null
@@ -1,41 +0,0 @@
-<?php
-
-/* Icinga Notifications Web | (c) 2023 Icinga GmbH | GPLv2 */
-
-namespace Icinga\Module\Notifications\Web\Control\SearchBar;
-
-use Icinga\Module\Notifications\Common\Database;
-use Icinga\Module\Notifications\Model\ObjectExtraTag;
-use Icinga\Module\Notifications\Util\ObjectSuggestionsCursor;
-use ipl\Web\Control\SearchBar\Suggestions;
-use ipl\Stdlib\Filter;
-use Traversable;
-
-class ExtraTagSuggestions extends Suggestions
-{
-    protected function fetchValueSuggestions($column, $searchTerm, Filter\Chain $searchFilter)
-    {
-        yield;
-    }
-
-    protected function createQuickSearchFilter($searchTerm)
-    {
-        return Filter::any();
-    }
-
-    protected function fetchColumnSuggestions($searchTerm)
-    {
-        $searchColumns = (new ObjectSuggestionsCursor(
-            Database::get(),
-            (new ObjectExtraTag())::on(Database::get())
-                ->columns(['tag'])
-                ->assembleSelect()
-                ->distinct()
-        ));
-
-        // Object Extra Tags
-        foreach ($searchColumns as $column) {
-            yield $column->tag => $column->tag;
-        }
-    }
-}
diff --git a/library/Notifications/Web/Control/SearchBar/ObjectSuggestions.php b/library/Notifications/Web/Control/SearchBar/ObjectSuggestions.php
index 2a4d674..2bd1b82 100644
--- a/library/Notifications/Web/Control/SearchBar/ObjectSuggestions.php
+++ b/library/Notifications/Web/Control/SearchBar/ObjectSuggestions.php
@@ -6,7 +6,6 @@ namespace Icinga\Module\Notifications\Web\Control\SearchBar;
 use Icinga\Module\Notifications\Common\Auth;
 use Icinga\Module\Notifications\Common\Database;
-use Icinga\Module\Notifications\Model\ObjectExtraTag;
 use Icinga\Module\Notifications\Model\ObjectIdTag;
 use Icinga\Module\Notifications\Util\ObjectSuggestionsCursor;
 use ipl\Html\HtmlElement;
@@ -184,9 +183,9 @@ class ObjectSuggestions extends Suggestions
         }
         // Custom variables only after the columns are exhausted and there's actually a chance the user sees them
-        foreach ([new ObjectIdTag(), new ObjectExtraTag()] as $model) {
+        foreach ([new ObjectIdTag()] as $model) {
             $titleAdded = false;
-            /** @var ObjectIdTag|ObjectExtraTag $tag */
+            /** @var ObjectIdTag $tag */
             foreach ($this->queryTags($model, $searchTerm) as $tag) {
                 $isIdTag = $tag instanceof ObjectIdTag;
diff --git a/library/Notifications/Widget/Detail/IncidentDetail.php b/library/Notifications/Widget/Detail/IncidentDetail.php
index 46e6bed..a9c370b 100644
--- a/library/Notifications/Widget/Detail/IncidentDetail.php
+++ b/library/Notifications/Widget/Detail/IncidentDetail.php
@@ -105,19 +105,10 @@ class IncidentDetail extends BaseHtmlElement
     protected function createObjectTag(): array
     {
-        $tags = [];
-        foreach ($this->incident->object->object_extra_tag as $extraTag) {
-            $tags[] = Table::row([$extraTag->tag, $extraTag->value]);
-        }
-
-        if (! $tags) {
-            return $tags;
-        }
-
         return [
             new HtmlElement('h2', null, new Text(t('Object Tags'))),
             (new Table())
-                ->addHtml(...$tags)
+                ->addHtml()
                 ->addAttributes(['class' => 'object-tags-table'])
         ];
     }Also, all the individual commits are self-contained, and includes the relevant commit message with some details about | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should also mention how rules are passed now (would also be helpful to get a glance over it while reviewing).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one is still open. I would like to update the documentation if everything else looks fine.
193fb86    to
    de9b824      
    Compare
  
    | The following diff against Icinga Notification Web "works" if your source has the ID 1. It is based on @yhabteab's diff from above. It should apply on top of today's  CLICK HERE FOR A FREE DOWNLOAD
 diff --git a/application/controllers/EventRuleController.php b/application/controllers/EventRuleController.php
index 9d07d6b..6ffdf3c 100644
--- a/application/controllers/EventRuleController.php
+++ b/application/controllers/EventRuleController.php
@@ -12,7 +12,6 @@ use Icinga\Module\Notifications\Forms\EventRuleConfigElements\NotificationConfig
 use Icinga\Module\Notifications\Forms\EventRuleConfigForm;
 use Icinga\Module\Notifications\Forms\EventRuleForm;
 use Icinga\Module\Notifications\Model\Rule;
-use Icinga\Module\Notifications\Web\Control\SearchBar\ExtraTagSuggestions;
 use Icinga\Web\Notification;
 use Icinga\Web\Session;
 use ipl\Html\Form;
@@ -157,9 +156,6 @@ class EventRuleController extends CompatController
      */
     public function completeAction(): void
     {
-        $suggestions = new ExtraTagSuggestions();
-        $suggestions->forRequest($this->getServerRequest());
-        $this->getDocument()->add($suggestions);
     }
 
     /**
diff --git a/application/forms/EventRuleConfigForm.php b/application/forms/EventRuleConfigForm.php
index 34da1ac..2652535 100644
--- a/application/forms/EventRuleConfigForm.php
+++ b/application/forms/EventRuleConfigForm.php
@@ -291,6 +291,7 @@ class EventRuleConfigForm extends Form
             $db->insert('rule', [
                 'name'          => $this->getValue('name'),
                 'timeperiod_id' => null,
+                'source_id'     => 1, // TODO :^)
                 'object_filter' => $this->getValue('object_filter'),
                 'changed_at'    => (int) (new DateTime())->format("Uv"),
                 'deleted'       => 'n'
diff --git a/application/forms/SourceForm.php b/application/forms/SourceForm.php
index dcfbc63..a57f626 100644
--- a/application/forms/SourceForm.php
+++ b/application/forms/SourceForm.php
@@ -71,144 +71,48 @@ class SourceForm extends CompatForm
             || ($chosenType === null && $configuredType === null)
             || ($chosenType === null && $configuredType === Source::ICINGA_TYPE_NAME);
 
-        if ($showIcingaApiConfig) {
-            // TODO: Shouldn't be necessary: https://github.com/Icinga/ipl-html/issues/131
-            $this->clearPopulatedValue('type');
+        $this->getElement('icinga_or_other')->setValue('other');
 
-            $this->addElement(
-                'hidden',
-                'type',
-                [
-                    'required'  => true,
-                    'disabled'  => true,
-                    'value'     => Source::ICINGA_TYPE_NAME
-                ]
-            );
-            $this->addElement(
-                'text',
-                'icinga2_base_url',
-                [
-                    'required'  => true,
-                    'label'     => $this->translate('API URL')
-                ]
-            );
-            $this->addElement(
-                'text',
-                'icinga2_auth_user',
-                [
-                    'required'      => true,
-                    'label'         => $this->translate('API Username'),
-                    'autocomplete'  => 'off'
-                ]
-            );
-            $this->addElement(
-                'password',
-                'icinga2_auth_pass',
-                [
-                    'required'      => true,
-                    'label'         => $this->translate('API Password'),
-                    'autocomplete'  => 'new-password'
-                ]
-            );
-            $this->addElement(
-                'checkbox',
-                'icinga2_insecure_tls',
-                [
-                    'class'             => 'autosubmit',
-                    'label'             => $this->translate('Verify API Certificate'),
-                    'checkedValue'      => 'n',
-                    'uncheckedValue'    => 'y',
-                    'value'             => true
-                ]
-            );
-
-            /** @var CheckboxElement $insecureBox */
-            $insecureBox = $this->getElement('icinga2_insecure_tls');
-            if ($insecureBox->isChecked()) {
-                $this->addElement(
-                    'text',
-                    'icinga2_common_name',
-                    [
-                        'label'         => $this->translate('Common Name'),
-                        'description'   => $this->translate(
-                            'The CN of the API certificate. Only required if it differs from the FQDN of the API URL'
-                        )
-                    ]
-                );
-
-                $this->addElement(
-                    'textarea',
-                    'icinga2_ca_pem',
-                    [
-                        'cols'          => 64,
-                        'rows'          => 28,
-                        'required'      => true,
-                        'validators'    => [new X509CertValidator()],
-                        'label'         => $this->translate('CA Certificate'),
-                        'description'   => $this->translate('The certificate of the Icinga CA')
-                    ]
-                );
-            }
-        } else {
-            $this->getElement('icinga_or_other')->setValue('other');
-
-            $this->addElement(
-                'text',
-                'type',
-                [
-                    'required'      => true,
-                    'label'         => $this->translate('Type Name'),
-                    'validators'    => [new CallbackValidator(function (string $value, CallbackValidator $validator) {
-                        if ($value === Source::ICINGA_TYPE_NAME) {
-                            $validator->addMessage($this->translate('This name is reserved and cannot be used'));
-
-                            return false;
-                        }
-
-                        return true;
-                    })]
-                ]
-            );
-            $this->addElement(
-                'password',
-                'listener_password',
-                [
-                    'ignore'        => true,
-                    'required'      => $this->sourceId === null,
-                    'label'         => $this->sourceId !== null
-                        ? $this->translate('New Password')
-                        : $this->translate('Password'),
-                    'autocomplete'  => 'new-password',
-                    'validators'    => [['name' => 'StringLength', 'options' => ['min' => 16]]]
-                ]
-            );
-            $this->addElement(
-                'password',
-                'listener_password_dupe',
-                [
-                    'ignore'        => true,
-                    'required'      => $this->sourceId === null,
-                    'label'         => $this->translate('Repeat Password'),
-                    'autocomplete'  => 'new-password',
-                    'validators'    => [new CallbackValidator(function (string $value, CallbackValidator $validator) {
-                        if ($value !== $this->getValue('listener_password')) {
-                            $validator->addMessage($this->translate('Passwords do not match'));
-
-                            return false;
-                        }
-
-                        return true;
-                    })]
-                ]
-            );
-
-            // Preserves (some) entered data even if the user switches between types
-            $this->addElement('hidden', 'icinga2_base_url');
-            $this->addElement('hidden', 'icinga2_auth_user');
-            $this->addElement('hidden', 'icinga2_insecure_tls');
-            $this->addElement('hidden', 'icinga2_common_name');
-            $this->addElement('hidden', 'icinga2_ca_pem');
-        }
+        $this->addElement(
+            'text',
+            'type',
+            [
+                'required'      => true,
+                'label'         => $this->translate('Type Name'),
+            ]
+        );
+        $this->addElement(
+            'password',
+            'listener_password',
+            [
+                'ignore'        => true,
+                'required'      => $this->sourceId === null,
+                'label'         => $this->sourceId !== null
+                    ? $this->translate('New Password')
+                    : $this->translate('Password'),
+                'autocomplete'  => 'new-password',
+                'validators'    => [['name' => 'StringLength', 'options' => ['min' => 16]]]
+            ]
+        );
+        $this->addElement(
+            'password',
+            'listener_password_dupe',
+            [
+                'ignore'        => true,
+                'required'      => $this->sourceId === null,
+                'label'         => $this->translate('Repeat Password'),
+                'autocomplete'  => 'new-password',
+                'validators'    => [new CallbackValidator(function (string $value, CallbackValidator $validator) {
+                    if ($value !== $this->getValue('listener_password')) {
+                        $validator->addMessage($this->translate('Passwords do not match'));
+
+                        return false;
+                    }
+
+                    return true;
+                })]
+            ]
+        );
 
         $this->addElement(
             'submit',
@@ -376,12 +280,6 @@ class SourceForm extends CompatForm
         return [
             'name'                  => $source->name,
             'type'                  => $source->type,
-            'icinga2_base_url'      => $source->icinga2_base_url,
-            'icinga2_auth_user'     => $source->icinga2_auth_user,
-            'icinga2_auth_pass'     => $source->icinga2_auth_pass,
-            'icinga2_ca_pem'        => $source->icinga2_ca_pem,
-            'icinga2_common_name'   => $source->icinga2_common_name,
-            'icinga2_insecure_tls'  => $source->icinga2_insecure_tls
         ];
     }
 }
diff --git a/library/Notifications/Model/ExtraTag.php b/library/Notifications/Model/ExtraTag.php
deleted file mode 100644
index 07a27f8..0000000
--- a/library/Notifications/Model/ExtraTag.php
+++ /dev/null
@@ -1,28 +0,0 @@
-<?php
-
-/* Icinga Notifications Web | (c) 2023 Icinga GmbH | GPLv2 */
-
-namespace Icinga\Module\Notifications\Model;
-
-use Icinga\Module\Notifications\Model\Behavior\ObjectTags;
-use ipl\Orm\Behaviors;
-use ipl\Sql\Connection;
-
-class ExtraTag extends ObjectExtraTag
-{
-    /**
-     * @internal Don't use. This model acts only as relation target and is not supposed to be directly used as query
-     *           target. Use {@see ObjectExtraTag} instead.
-     */
-    public static function on(Connection $_)
-    {
-        throw new \LogicException('Documentation says: DO NOT USE. Can\'t you read?');
-    }
-
-    public function createBehaviors(Behaviors $behaviors): void
-    {
-        parent::createBehaviors($behaviors);
-
-        $behaviors->add(new ObjectTags());
-    }
-}
diff --git a/library/Notifications/Model/ObjectExtraTag.php b/library/Notifications/Model/ObjectExtraTag.php
deleted file mode 100644
index df7beb7..0000000
--- a/library/Notifications/Model/ObjectExtraTag.php
+++ /dev/null
@@ -1,52 +0,0 @@
-<?php
-
-/* Icinga Notifications Web | (c) 2023 Icinga GmbH | GPLv2 */
-
-namespace Icinga\Module\Notifications\Model;
-
-use ipl\Orm\Behavior\Binary;
-use ipl\Orm\Behaviors;
-use ipl\Orm\Model;
-use ipl\Orm\Query;
-use ipl\Orm\Relations;
-
-/**
- * ObjectExtraTag database model
- *
- * @property string $object_id
- * @property string $tag
- * @property string $value
- *
- * @property Query|Objects $object
- */
-class ObjectExtraTag extends Model
-{
-    public function getTableName(): string
-    {
-        return 'object_extra_tag';
-    }
-
-    public function getKeyName(): array
-    {
-        return ['object_id', 'tag'];
-    }
-
-    public function getColumns(): array
-    {
-        return [
-            'object_id',
-            'tag',
-            'value'
-        ];
-    }
-
-    public function createBehaviors(Behaviors $behaviors): void
-    {
-        $behaviors->add(new Binary(['object_id']));
-    }
-
-    public function createRelations(Relations $relations): void
-    {
-        $relations->belongsTo('object', Objects::class);
-    }
-}
diff --git a/library/Notifications/Model/Objects.php b/library/Notifications/Model/Objects.php
index c621056..3f37a27 100644
--- a/library/Notifications/Model/Objects.php
+++ b/library/Notifications/Model/Objects.php
@@ -25,8 +25,6 @@ use ipl\Orm\Relations;
  * @property Query|Event $event
  * @property Query|Incident $incident
  * @property Query|Tag $tag
- * @property Query|ObjectExtraTag $object_extra_tag
- * @property Query|ExtraTag $extra_tag
  * @property Query|Source $source
  * @property array<string, string> $id_tags
  */
@@ -81,10 +79,6 @@ class Objects extends Model
 
         $relations->hasMany('object_id_tag', ObjectIdTag::class);
         $relations->hasMany('tag', Tag::class);
-        $relations->hasMany('object_extra_tag', ObjectExtraTag::class)
-            ->setJoinType('LEFT');
-        $relations->hasMany('extra_tag', ExtraTag::class)
-            ->setJoinType('LEFT');
 
         $relations->belongsTo('source', Source::class)->setJoinType('LEFT');
     }
diff --git a/library/Notifications/Model/Rule.php b/library/Notifications/Model/Rule.php
index 8e69a01..00cd5f8 100644
--- a/library/Notifications/Model/Rule.php
+++ b/library/Notifications/Model/Rule.php
@@ -16,6 +16,7 @@ use ipl\Orm\Relations;
  * @property int $id
  * @property string $name
  * @property ?int $timeperiod_id
+ * @property ?int $source_id
  * @property ?string $object_filter
  * @property DateTime $changed_at
  * @property bool $deleted
@@ -41,6 +42,7 @@ class Rule extends Model
         return [
             'name',
             'timeperiod_id',
+            'source_id',
             'object_filter',
             'changed_at',
             'deleted'
@@ -51,6 +53,7 @@ class Rule extends Model
     {
         return [
             'name'          => t('Name'),
+            'source_id'     => t('Source ID'),
             'timeperiod_id' => t('Timeperiod ID'),
             'object_filter' => t('Object Filter'),
             'changed_at'    => t('Changed At')
diff --git a/library/Notifications/Model/Source.php b/library/Notifications/Model/Source.php
index 973bbb7..8558ace 100644
--- a/library/Notifications/Model/Source.php
+++ b/library/Notifications/Model/Source.php
@@ -19,12 +19,6 @@ use ipl\Web\Widget\Icon;
  * @property string $type Type identifier
  * @property string $name The user-defined name
  * @property ?string $listener_password_hash
- * @property ?string $icinga2_base_url
- * @property ?string $icinga2_auth_user
- * @property ?string $icinga2_auth_pass
- * @property ?string $icinga2_ca_pem
- * @property ?string $icinga2_common_name
- * @property string $icinga2_insecure_tls
  * @property DateTime $changed_at
  * @property bool $deleted
  *
@@ -33,7 +27,7 @@ use ipl\Web\Widget\Icon;
 class Source extends Model
 {
     /** @var string The type name used by Icinga sources */
-    public const ICINGA_TYPE_NAME = 'icinga2';
+    public const ICINGA_TYPE_NAME = 'icingadb';
 
     public function getTableName(): string
     {
@@ -51,12 +45,6 @@ class Source extends Model
             'type',
             'name',
             'listener_password_hash',
-            'icinga2_base_url',
-            'icinga2_auth_user',
-            'icinga2_auth_pass',
-            'icinga2_ca_pem',
-            'icinga2_common_name',
-            'icinga2_insecure_tls',
             'changed_at',
             'deleted'
         ];
diff --git a/library/Notifications/Web/Control/SearchBar/ExtraTagSuggestions.php b/library/Notifications/Web/Control/SearchBar/ExtraTagSuggestions.php
deleted file mode 100644
index 88f52c2..0000000
--- a/library/Notifications/Web/Control/SearchBar/ExtraTagSuggestions.php
+++ /dev/null
@@ -1,41 +0,0 @@
-<?php
-
-/* Icinga Notifications Web | (c) 2023 Icinga GmbH | GPLv2 */
-
-namespace Icinga\Module\Notifications\Web\Control\SearchBar;
-
-use Icinga\Module\Notifications\Common\Database;
-use Icinga\Module\Notifications\Model\ObjectExtraTag;
-use Icinga\Module\Notifications\Util\ObjectSuggestionsCursor;
-use ipl\Web\Control\SearchBar\Suggestions;
-use ipl\Stdlib\Filter;
-use Traversable;
-
-class ExtraTagSuggestions extends Suggestions
-{
-    protected function fetchValueSuggestions($column, $searchTerm, Filter\Chain $searchFilter)
-    {
-        yield;
-    }
-
-    protected function createQuickSearchFilter($searchTerm)
-    {
-        return Filter::any();
-    }
-
-    protected function fetchColumnSuggestions($searchTerm)
-    {
-        $searchColumns = (new ObjectSuggestionsCursor(
-            Database::get(),
-            (new ObjectExtraTag())::on(Database::get())
-                ->columns(['tag'])
-                ->assembleSelect()
-                ->distinct()
-        ));
-
-        // Object Extra Tags
-        foreach ($searchColumns as $column) {
-            yield $column->tag => $column->tag;
-        }
-    }
-}
diff --git a/library/Notifications/Web/Control/SearchBar/ObjectSuggestions.php b/library/Notifications/Web/Control/SearchBar/ObjectSuggestions.php
index 2a4d674..2bd1b82 100644
--- a/library/Notifications/Web/Control/SearchBar/ObjectSuggestions.php
+++ b/library/Notifications/Web/Control/SearchBar/ObjectSuggestions.php
@@ -6,7 +6,6 @@ namespace Icinga\Module\Notifications\Web\Control\SearchBar;
 
 use Icinga\Module\Notifications\Common\Auth;
 use Icinga\Module\Notifications\Common\Database;
-use Icinga\Module\Notifications\Model\ObjectExtraTag;
 use Icinga\Module\Notifications\Model\ObjectIdTag;
 use Icinga\Module\Notifications\Util\ObjectSuggestionsCursor;
 use ipl\Html\HtmlElement;
@@ -184,9 +183,9 @@ class ObjectSuggestions extends Suggestions
         }
 
         // Custom variables only after the columns are exhausted and there's actually a chance the user sees them
-        foreach ([new ObjectIdTag(), new ObjectExtraTag()] as $model) {
+        foreach ([new ObjectIdTag()] as $model) {
             $titleAdded = false;
-            /** @var ObjectIdTag|ObjectExtraTag $tag */
+            /** @var ObjectIdTag $tag */
             foreach ($this->queryTags($model, $searchTerm) as $tag) {
                 $isIdTag = $tag instanceof ObjectIdTag;
 
diff --git a/library/Notifications/Widget/Detail/IncidentDetail.php b/library/Notifications/Widget/Detail/IncidentDetail.php
index 46e6bed..a9c370b 100644
--- a/library/Notifications/Widget/Detail/IncidentDetail.php
+++ b/library/Notifications/Widget/Detail/IncidentDetail.php
@@ -105,19 +105,10 @@ class IncidentDetail extends BaseHtmlElement
 
     protected function createObjectTag(): array
     {
-        $tags = [];
-        foreach ($this->incident->object->object_extra_tag as $extraTag) {
-            $tags[] = Table::row([$extraTag->tag, $extraTag->value]);
-        }
-
-        if (! $tags) {
-            return $tags;
-        }
-
         return [
             new HtmlElement('h2', null, new Text(t('Object Tags'))),
             (new Table())
-                ->addHtml(...$tags)
+                ->addHtml()
                 ->addAttributes(['class' => 'object-tags-table'])
         ];
     } | 
This is a first version to move the rule evaluation from Icinga Notifications into the source, starting with Icinga DB. For an end user or another computer, the /process-event API endpoint was slightly modified. Two new HTTP request headers were introduced to tell which rules match and to share the state. Another new /event-rules API endpoint allows querying all rules.
Instead just use a regular non-json-serializable field similar to how we already do it for `Time` and `SourceId`.
Instead of having to determine the rules version with each incoming request by the listener, we can just make use the already existing incremental update mechanism to keep the version up-to-date. This way, the version will only be updated when a rule is added, changed, or deleted, so there will be no performance impact on the listener side. Apart from that, the listener responds and verifies the rules version on a per-source basis now. As required by the internal document, event rules will be tied to their source because only this source knows how to evaluate them. Therefore, the database `rule` table has been extended to include a `source_id` column, and consequently, each source will only receive the rules that are relevant to it. Thus, initially, when a source submits an event, it will likely be rejected but at the same time receive the current rules, so it can retry the event submission with the correct event rules. This way, no extra HTTP request is needed to fetch the rules, as we will always respond with the newest ones whenever we detect that they're using an outdated event rules config.
I think, this better describes the use case than using the `424` HTTP status code.
WIP because we might move the code elsewhere.
Synchronize codebase against latest version of Icinga/icinga-go-library#145, containing package changes.
Move the bcrypt password comparison up to the Source and introduce a cache there. This reduces the expensive bcrypt calls by a lot, reducing the authorization delay from round about two seconds to single-digit milliseconds on my machine. On a similar note, we should consider changing the API to allow creating some token after an initial password authorization instead of passing the authentication data all the time.
- Update IGL to include rules and rule versions in the event. Thus, being available in JSON and no longer in a HTTP header. - Simplify schema rule version incrementation logic by ignoring the edge case of sources w/o rules. In this case, the rule will not be reset, but further incremented. - Fix import names. - Abort processing incoming events for unknown rules. This could only occur if there is a race between changing rules and a processed event, as rules are initially checked. - Erase race condition between mutex locks and unlocks for HTTP rule endpoint and ensure debug dump rules has a mutex all the time. - Change the SQL CHECK for the listener_password_hash to allow different versions of bcrypt. The "$2y$" thingy is PHP specific to highlight "old" bcrypt passwords being affected by a PHP implementation bug, not a bcrypt specification bug or change.
de9b824    to
    508ea9c      
    Compare
  
    - event.Event: Extend baseEv.Event instead of a pointer. - object: Merge IdTagRow and TagRow. - objects.go: Remove mysterious trailing whitespace.
With the latest IGL change, relative Event URLs from Sources are possible. The Icinga Notifications daemon can now complete those URLs. Furthermore, Event IDs are now strings in the IGL, easing future changes. This was reflected here as well.
Prior, each SourceRulesInfo.Version started effectively at one. Consider the following: Icinga Notifications loads its rules, having version one. There is no change and Icinga Notifications was stopped. Now, after altering the configuration in Icinga Notifications Web, and restarting Icinga Notifications, the new rules will be loaded, but the version will obviously be one again. From a source's point of view, nothing has changed - the version is still one. However, the update would be lost. With this change, the version starts at MAX_INT64-UNIX_TIME, relative to the time when Icinga Notifications loads the first rule for a source. As described in the documentation changed in this commit, this will result in different initial version values, where with progressing time the values will decrease, reducing the possibility for conflicts as with rule changes the version increases.
| Drop COLUMN icinga2_ca_pem, | ||
| Drop COLUMN icinga2_common_name, | ||
| Drop COLUMN icinga2_insecure_tls, | ||
| ALTER COLUMN listener_password_hash SET NOT NULL; | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This column was previously nullable, and I already have an entry with a null value. This means that I cannot perform this upgrade. The following is missing:
UPDATE source SET listener_password_hash = '$2_$%' WHERE listener_password_hash IS NULL;There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly, this unveils a bigger issue in the migration. With dropping the icinga2 source and changing the ck_source_bcrypt_listener_password_hash check constraint, setups previously using icinga2 sources cannot be migrated without loss.
Since this very support is being removed in this PR and manual intervention is required, I am going to delete such rows.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this very support is being removed in this PR and manual intervention is required, I am going to delete such rows.
Doesn't sound like the best idea, as you'd basically have to wipe everything then given the foreign key constraints.
Two other options:
- Make it a manual migration step that you have to set password hashes for all before applying the schema upgrade. Adding that constraint should then be the first step in the migration so that if the manual step wasn't done, it fails immediately.
- Allow it to stay NULL for the time being, which means the source remains in a non-working state until a password was set for it. That would probably be less annoying migration path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Option 2 sounds most reasonable to me. Requires minimal changes to the code (if at all) and has the most graceful migration path.
The RulesInfo type was simplified. Rules are no longer a custom struct, but just represented by the map key and a filter expression string.
Move the public facing pkg.plugin and pkg.rpc packages to the Icinga Go Library to ease external reuse and channel development.
| Sources map[int64]*Source | ||
|  | ||
| RuleSet // RuleSet contains the currently loaded rules and their version. | ||
|  | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure about RuleSet. That type seems to be only used for embedding it here. What's the idea behind that? I'd suggest keeping it more like the others, i.e. keep the Rules field as-is and add the second additional lookup structure directly to this struct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I cannot answer about the initial motivation, but changing this to a field results in some very long access paths.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What exactly do you mean by "very long access paths"?
I'm mainly wondering what the point for RuleSet is, i.e. I didn't find a pointer to a RuleSet anywhere or something that copies the whole thing. The alternative would be to just have Rules and RulesBySource defined directly in here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The point is that I didn't want to bloat the ConfigSet struct with even mor fields that are actually all rule related, thus I've used RuleSet to group all the fields concerning event rules. -
| if e.Type == baseEv.TypeUnknown { | ||
| return fmt.Errorf("invalid event: unsupported or empty event type %q", e.Type) | ||
| } | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unsupported type? Can that even happen? Isn't the effect of moving that check to the UnmarshalJSON() method that the whole JSON parsing operation would fail (so that you wouldn't even end up here in that case)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unsure if this can happen. Which UnmarshalJSON method are you referring to? In my understanding, this method is called for submitted events in Listener.ProcessEvent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was referring to that one: https://github.com/Icinga/icinga-go-library/blob/6600889adc38ffe2088948411c852cb387b5762f/notifications/event/type.go#L42-L61
So far, it was parsed into a string, allowing any string value, but now it's parsed into a type that does its own validation. So if it was indeed a unknown type, that should already trigger an error before getting here and this should only happen for "type":null (or it missing entirely in the JSON).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that should already trigger an error before getting here and this should only happen for
"type":null(or it missing entirely in the JSON).
Since the ParseType() function doesn't have such constraint t != TypeUnknown, the JSON string can also contain such strings "type":"unknown"  and would pass the validation on the Type. However, I don't get what your actual point is, are you suggesting to remove this check entierely or something else? Why would you want this basic validation to depend on something else when you can easily bypass the validation on the Type struct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, I don't get what your actual point is, are you suggesting to remove this check entierely or something else?
Whether the error message actually describes the situation that's checked for. Maybe something like "invalid event: missing type" would better describe it. Using e.Type in the error message is also questionable given that it would be normalized to TypeUnknown already. (I'm also not completely sure what %q will do in this scenario, i.e. whether it will print "unknown" or - worse - try to interpret the underlying integer representation as a character.
Since the
ParseType()function doesn't have such constraintt != TypeUnknown, the JSON string can also contain such strings"type":"unknown"
So {"type":null} and {"type":"unknown"} will parse the same? Not sure if that even sounds like desirable behavior, "unknown" probably shouldn't be considered a valid value if TypeUnknown is supposed to represent JSON null.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm also not completely sure what
%qwill do in this scenario, i.e. whether it will print"unknown"or - worse - try to interpret the underlying integer representation as a character.
Is never going to happen! The Type struct implements the String method.
So
{"type":null}and{"type":"unknown"}will parse the same? Not sure if that even sounds like desirable behavior,"unknown"probably shouldn't be considered a valid value ifTypeUnknownis supposed to represent JSONnull.
Then both the ParseType and ParseSeverity functions need to be fixed (add the t/s != TypeUnknown/SeverityNone constraint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either way: is there any reason why this shouldn't just do return errors.New("invalid event: missing type")?
This clearly looks like a copy & paste remainder from the old implementation, having e.Type as part of the log message inside if e.Type == baseEv.TypeUnknown (that condition was different before) just makes little sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either way: is there any reason why this shouldn't just do
return errors.New("invalid event: missing type")?
I already gave you the reason for that. Why do you want to blindly trust on the validation from the Type itself and assume that it'd be a missing type, when one can just construct even some invalid ones like this Type(100)? The condition is just wrong for checking for TypeUnkknown, instead we should maybe introduce a IsValid() method on the Type struct that reports whether the type represented by t is within the valid range of the type.
Again, I'm just answering your questions since I wrote that code, but you can change it however you want if this isn't a good enough reason for you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either way: is there any reason why this shouldn't just do
return errors.New("invalid event: missing type")?I already gave you the reason for that.
Where?
Why do you want to blindly trust on the validation from the
Typeitself and assume that it'd be a missing type,
Blindly trusting sounds like I'd want to remove the check, which I do not. All I'm trying to say so far is that the current message is bullshit. Is anyone trying to argue with that?
when one can just construct even some invalid ones like this
Type(100)? The condition is just wrong for checking forTypeUnkknown, instead we should maybe introduce aIsValid()method on theTypestruct that reports whether the type represented bytis within the valid range of the type.
Now that's another issue which boils down to the question what this code here is supposed to do: is it supposed to detect any possible inconsistency, including these possibly resulting from manually constructing the object? Or just verify the additional constraints that aren't check during JSON parsing? The latter is how this function is currently used (by the way, all the errors returned use the JSON field names, not the struct field names). For this, just ensuring that e.Type != baseEv.TypeUnknown is enough, because that checks that a field was set, which encoding/json can't check by itself. If the field was set, I find it reasonable to assume that parsing it ensured that it's set to a valid type.
Again, I'm just answering your questions since I wrote that code, but you can change it however you want if this isn't a good enough reason for you.
Okay, but who will address this now? For me, it'd be enough if @oxzi just fixed the error message as I suggested (after agreeing that this change makes sense).
If you (@yhabteab) think a validation function for the Type type, this can also be added after this PR, in particular, this saves this PR from requiring another cross-repo change.
| "net/http" | ||
| "time" | ||
|  | ||
| "github.com/icinga/icinga-go-library/database" | ||
| "github.com/icinga/icinga-go-library/logging" | ||
| baseEv "github.com/icinga/icinga-go-library/notifications/event" | ||
| baseSource "github.com/icinga/icinga-go-library/notifications/source" | ||
| "github.com/icinga/icinga-notifications/internal" | ||
| "github.com/icinga/icinga-notifications/internal/config" | ||
| "github.com/icinga/icinga-notifications/internal/daemon" | ||
| "github.com/icinga/icinga-notifications/internal/event" | ||
| "github.com/icinga/icinga-notifications/internal/incident" | ||
| "go.uber.org/zap" | ||
| "net/http" | ||
| "time" | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The existing imports shouldn't be regrouped.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"👀" but unchanged? 👀
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I have planned addressing this when everything else is done as otherwise this just might creep in again.
| Drop COLUMN icinga2_ca_pem, | ||
| Drop COLUMN icinga2_common_name, | ||
| Drop COLUMN icinga2_insecure_tls, | ||
| ALTER COLUMN listener_password_hash SET NOT NULL; | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this very support is being removed in this PR and manual intervention is required, I am going to delete such rows.
Doesn't sound like the best idea, as you'd basically have to wipe everything then given the foreign key constraints.
Two other options:
- Make it a manual migration step that you have to set password hashes for all before applying the schema upgrade. Adding that constraint should then be the first step in the migration so that if the manual step wasn't done, it fails immediately.
- Allow it to stay NULL for the time being, which means the source remains in a non-working state until a password was set for it. That would probably be less annoying migration path.
Move version into own product type, holding an unique major value and an incrementable minor version. Also rework some other small things brought up in the review.
This reverts commit 79951c4.
- UnixMilli does not require UTC, UnixMilli() always returns UTC. - Don't reject event submissions when no rules exist. - Use strconv instead of fmt for a single number.
This is a first version to move the rule evaluation from Icinga Notifications into the source, starting with Icinga DB.
For an end user or another computer, the /process-event API endpoint was slightly modified. Two new HTTP request headers were introduced to tell which rules match and to share the state.
Another new /event-rules API endpoint allows querying all rules.
Other PRs are
At the moment, there is no real support in Icinga Notifications Web. Thus, start by creating a rule through the current web interface and update the data in the relational database. Consider the following: