Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions doc/10-Channels.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,16 +256,6 @@ or if the channel is missing required configuration values.
"tags": {
"host": "dummy-816",
"service": "random fortune"
},
"extra_tags": {
"hostgroup/app-mobile": "",
"hostgroup/department-dev": "",
"hostgroup/env-prod": "",
"hostgroup/location-rome": "",
"servicegroup/app-storage": "",
"servicegroup/department-ps": "",
"servicegroup/env-prod": "",
"servicegroup/location-rome": ""
}
},
"incident": {
Expand Down
58 changes: 38 additions & 20 deletions doc/20-HTTP-API.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ The authentication is performed via HTTP Basic Authentication using the source's
When upgrading a setup from an earlier version, these usernames are still valid, but can be changed in Icinga Notifications Web.

Events sent to Icinga Notifications are expected to match rules that describe further event escalations.
These rules can be created in the web interface.
Next to an array of `rule_ids`, a `rules_version` must be provided to ensure that the source has no outdated state.
These rules can be configured in Icinga Notifications Web and should be designed to match the `relations` of the
submitted events. When submitting an event without the expected relations to evaluate the rules, Icinga Notifications
will reject the request with a `422 Unprocessable Entity` status code and a message describing the missing relations
when the `XX-Icinga-Reject-If-Relations-Incomplete` header is set to `true`. Otherwise, the request will be accepted
nonetheless, when either there's an existing incident for the event's objects, the ongoing event causes a new incident
to be opened, or the source have at least one event rule without a configured object filter.

When the submitted `rules_version` is either outdated or empty, the `/process-event` endpoint returns an HTTP 412 response.
The response's body is a JSON-encoded version of the
[`RulesInfo`](https://github.com/Icinga/icinga-go-library/blob/main/notifications/source/client.go),
containing the latest `rules_version` together with all rules for this source.
After reevaluating these rules, one can resubmit the event with the updated `rules_version`.
An example request to submit an event looks like this:

```
curl -v -u 'source-2:insecureinsecure' -d '@-' 'http://localhost:5680/process-event' <<EOF
Expand All @@ -37,22 +37,40 @@ curl -v -u 'source-2:insecureinsecure' -d '@-' 'http://localhost:5680/process-ev
"host": "dummy-809",
"service": "random fortune"
},
"extra_tags": {
"hostgroup/app-container": null,
"hostgroup/department-dev": null,
"hostgroup/env-qa": null,
"hostgroup/location-rome": null,
"servicegroup/app-mail": null,
"servicegroup/department-nms": null,
"servicegroup/env-prod": null,
"servicegroup/location-berlin": null
},
"type": "state",
"severity": "crit",
"username": "",
"message": "Something went somewhere very wrong.",
"rules_version": "23",
"rule_ids": ["0"]
"relations": {
"host": {
"name": "dummy-809",
"display_name": "My Dummy Host",
"vars": {
"os": "linux"
}
},
"services": [
{
"name": "random fortune",
"display_name": "Random Fortune Service",
"vars": {
"env": "production",
"team": "devops"
}
}
],
"hostgroups": [
{
"name": "linux-servers",
"display_name": "Linux Servers"
}
],
"servicegroups": [
{
"name": "production-services",
"display_name": "Production Services"
}
]
}
}
EOF
```
Expand Down
9 changes: 5 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,31 @@ require (
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6
github.com/emersion/go-smtp v0.24.0
github.com/google/uuid v1.6.0
github.com/icinga/icinga-go-library v0.9.0
github.com/icinga/icinga-go-library v0.9.1-0.20260512133613-5024937aed30
github.com/jhillyerd/enmime v1.3.0
github.com/jmoiron/sqlx v1.4.0
github.com/okzk/sdnotify v0.0.0-20180710141335-d9becc38acbd
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.11.1
github.com/teambition/rrule-go v1.8.2
github.com/theory/jsonpath v0.12.0
go.uber.org/zap v1.28.0
golang.org/x/crypto v0.51.0
golang.org/x/sync v0.20.0
)

require (
filippo.io/edwards25519 v1.1.1 // indirect
filippo.io/edwards25519 v1.2.0 // indirect
github.com/caarlos0/env/v11 v11.4.0 // indirect
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/go-sql-driver/mysql v1.9.3 // indirect
github.com/go-sql-driver/mysql v1.10.0 // indirect
github.com/goccy/go-yaml v1.13.0 // indirect
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f // indirect
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 // indirect
github.com/jessevdk/go-flags v1.6.1 // indirect
github.com/lib/pq v1.11.2 // indirect
github.com/lib/pq v1.12.3 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
Expand Down
27 changes: 18 additions & 9 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
filippo.io/edwards25519 v1.1.1 h1:YpjwWWlNmGIDyXOn8zLzqiD+9TyIlPhGFG96P39uBpw=
filippo.io/edwards25519 v1.1.1/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
github.com/caarlos0/env/v11 v11.4.0 h1:Kcb6t5kIIr4XkoQC9AF2j+8E1Jsrl3Wz/hhm1LtoGAc=
github.com/caarlos0/env/v11 v11.4.0/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/ztvmWKFcI7UGb5/HQT7B+i3a2myKgI=
Expand All @@ -24,8 +24,8 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw=
github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk=
github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/goccy/go-yaml v1.13.0 h1:0Wtp0FZLd7Sm8gERmR9S6Iczzb3vItJj7NaHmFg8pTs=
Expand All @@ -36,8 +36,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/icinga/icinga-go-library v0.9.0 h1:U0zpgpRIjO2gEwlTkHCHGgvW+ZuZeb2W7R6OGcnkGTI=
github.com/icinga/icinga-go-library v0.9.0/go.mod h1:7vvur6e1MOsM50oeYBYLkxA7H1F1ZCS0anZfG11kYgY=
github.com/icinga/icinga-go-library v0.9.1-0.20260512133613-5024937aed30 h1:Era1Y7O0CxzXK74H4qCaR7jwUjmBFK2jckUZOpTyK7Y=
github.com/icinga/icinga-go-library v0.9.1-0.20260512133613-5024937aed30/go.mod h1:L6zwhdk7XDWkeO/56QpTHHOyv700yflJdpcZzbckwQ8=
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 h1:iCHtR9CQyktQ5+f3dMVZfwD2KWJUgm7M0gdL9NGr8KA=
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4=
Expand All @@ -46,11 +46,15 @@ github.com/jhillyerd/enmime v1.3.0 h1:LV5kzfLidiOr8qRGIpYYmUZCnhrPbcFAnAFUnWn99r
github.com/jhillyerd/enmime v1.3.0/go.mod h1:6c6jg5HdRRV2FtvVL69LjiX1M8oE0xDX9VEhV3oy4gs=
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs=
github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
Expand All @@ -72,6 +76,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/ssgreg/journald v1.0.0 h1:0YmTDPJXxcWDPba12qNMdO6TxvfkFSYpFIJ31CwmLcU=
github.com/ssgreg/journald v1.0.0/go.mod h1:RUckwmTM8ghGWPslq2+ZBZzbb9/2KgjzYZ4JEP+oRt0=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
Expand All @@ -80,6 +86,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/teambition/rrule-go v1.8.2 h1:lIjpjvWTj9fFUZCmuoVDrKVOtdiyzbzc93qTmRVe/J8=
github.com/teambition/rrule-go v1.8.2/go.mod h1:Ieq5AbrKGciP1V//Wq8ktsTXwSwJHDD5mD/wLBGl3p4=
github.com/theory/jsonpath v0.12.0 h1:NQeuE0ohHHhss0DoxU9Xu2IpTTrlx9x4mv4F3pcmDME=
github.com/theory/jsonpath v0.12.0/go.mod h1:vl8nfJyq9MKMbcAiKv+7N9W3jDCH8qPr0mZoZj8wRk8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
Expand All @@ -102,7 +110,8 @@ golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
7 changes: 3 additions & 4 deletions internal/channel/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,9 @@ func (c *Channel) Notify(contact *recipient.Contact, i contracts.Incident, ev *e
req := &plugin.NotificationRequest{
Contact: contactStruct,
Object: &plugin.Object{
Name: object.DisplayName(),
Url: ev.URL,
Tags: object.Tags,
ExtraTags: object.ExtraTags,
Name: object.DisplayName(),
Url: ev.URL,
Tags: object.Tags,
},
Incident: &plugin.Incident{
Id: i.ID(),
Expand Down
123 changes: 38 additions & 85 deletions internal/config/rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,78 +4,32 @@ import (
"fmt"
"github.com/icinga/icinga-notifications/internal/rule"
"slices"
"time"
)

// SourceRuleVersion for SourceRulesInfo, consisting of two numbers, one static and one incrementable.
type SourceRuleVersion struct {
Major int64
Minor int64
}

// NewSourceRuleVersion creates a new source version based on the current timestamp and a zero counter.
func NewSourceRuleVersion() SourceRuleVersion {
return SourceRuleVersion{
Major: time.Now().UnixMilli(),
Minor: 0,
// GetRulesFilterColumnsForSource returns a set of all filter columns used in the rules of the given source.
//
// The second return value indicates whether there are any rules without an object filter, in which case the events
// from the provided src should be processed nonetheless, even if they don't carry all the required filter columns
// unless it was explicitly requested to reject such events by the client.
func (r *RuntimeConfig) GetRulesFilterColumnsForSource(src *Source) (rule.FilterAttrsType, bool) {
r.RLock()
defer r.RUnlock()

var columns rule.FilterAttrsType
var hasRulesWithoutFilter bool
for _, id := range src.RuleIDs() {
eventRule, ok := r.Rules[id]
if !ok {
continue
}
columns = append(columns, eventRule.FilterColumns...)
hasRulesWithoutFilter = hasRulesWithoutFilter || eventRule.ObjectFilter == nil
}
}

// Increment the version counter.
func (sourceVersion *SourceRuleVersion) Increment() {
sourceVersion.Minor++
}

// String implements fmt.Stringer and returns a pretty-printable representation.
func (sourceVersion *SourceRuleVersion) String() string {
return fmt.Sprintf("%x-%x", sourceVersion.Major, sourceVersion.Minor)
}

// SourceRulesInfo holds information about the rules associated with a specific source.
type SourceRulesInfo struct {
// Version is the version of the rules for the source.
//
// Multiple source's versions are independent of another.
Version SourceRuleVersion

// RuleIDs is a list of rule IDs associated with a specific source.
//
// It is used to quickly access the rules for a specific source without iterating over all rules.
RuleIDs []int64
return columns, hasRulesWithoutFilter
}

// applyPendingRules synchronizes changed rules.
func (r *RuntimeConfig) applyPendingRules() {
// Keep track of sources the rules were updated for, so we can update their version later.
updatedSources := make(map[int64]struct{})

if r.RulesBySource == nil {
r.RulesBySource = make(map[int64]*SourceRulesInfo)
}

addToRulesBySource := func(elem *rule.Rule) {
if sourceInfo, ok := r.RulesBySource[elem.SourceID]; ok {
sourceInfo.RuleIDs = append(sourceInfo.RuleIDs, elem.ID)
} else {
r.RulesBySource[elem.SourceID] = &SourceRulesInfo{
Version: NewSourceRuleVersion(),
RuleIDs: []int64{elem.ID},
}
}

updatedSources[elem.SourceID] = struct{}{}
}

delFromRulesBySource := func(elem *rule.Rule) {
if sourceInfo, ok := r.RulesBySource[elem.SourceID]; ok {
sourceInfo.RuleIDs = slices.DeleteFunc(sourceInfo.RuleIDs, func(id int64) bool {
return id == elem.ID
})
}

updatedSources[elem.SourceID] = struct{}{}
}

incrementalApplyPending(
r,
&r.Rules, &r.configChange.Rules,
Expand All @@ -89,9 +43,11 @@ func (r *RuntimeConfig) applyPendingRules() {
}

newElement.Escalations = make(map[int64]*rule.Escalation)

addToRulesBySource(newElement)

// If the source this rule belongs to is already known, add this rule to the source's rule list.
// Otherwise, the rule will be added to that list when its source is being loaded.
if src, ok := r.Sources[newElement.SourceID]; ok {
src.appendRuleID(newElement.ID)
}
return nil
},
func(curElement, update *rule.Rule) error {
Expand All @@ -110,35 +66,32 @@ func (r *RuntimeConfig) applyPendingRules() {
}

if curElement.SourceID != update.SourceID {
delFromRulesBySource(curElement)
if src, ok := r.Sources[curElement.SourceID]; ok {
src.deleteRuleID(curElement.ID)
}
if src, ok := r.Sources[update.SourceID]; ok {
src.appendRuleID(curElement.ID)
Comment thread
oxzi marked this conversation as resolved.
}
curElement.SourceID = update.SourceID
addToRulesBySource(curElement)
}

// ObjectFilterExpr is being initialized by config.IncrementalConfigurableInitAndValidatable.
// ObjectFilter{,Expr} are being initialized by config.IncrementalConfigurableInitAndValidatable.
curElement.ObjectFilter = update.ObjectFilter
curElement.ObjectFilterExpr = update.ObjectFilterExpr

updatedSources[curElement.SourceID] = struct{}{}
curElement.FilterColumns = update.FilterColumns

return nil
},
func(delElement *rule.Rule) error {
delFromRulesBySource(delElement)

// If the source this rule belongs to is already known, remove this rule from the source's rule list.
// Otherwise, there's nothing more to do!
if src, ok := r.Sources[delElement.SourceID]; ok {
src.deleteRuleID(delElement.ID)
}
return nil
},
)

// After applying the rules, we need to update the version of the sources that were modified.
// This is done to ensure that the version is incremented whenever a rule is added, modified,
// or deleted only once per applyPendingRules call, even if multiple rules from the same source
// were changed.
for sourceID := range updatedSources {
if sourceInfo, ok := r.RulesBySource[sourceID]; ok {
sourceInfo.Version.Increment()
}
}

incrementalApplyPending(
r,
&r.ruleEscalations, &r.configChange.ruleEscalations,
Expand Down
Loading
Loading