Skip to content

Commit 1f2c608

Browse files
authored
Allow ignoring sinks init errors (#1915)
* Allow ignoring sinks init errors Fix finding dates timezone issues * CR fixes * CR fixes
1 parent 4a05432 commit 1f2c608

9 files changed

Lines changed: 77 additions & 28 deletions

File tree

docs/notification-routing/configuring-sinks.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,22 @@ Description of each option:
9090
| *parameters* | Slack and webhook_url for MSTeams | | |
9191
+------------------+---------------------------------------------------------+----------------------------------------------------------+-----------------------------------------------+
9292

93+
Ignoring Sinks Initialization Errors
94+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
95+
96+
By default, when Robusta fails to initialize any of the Sinks, it will not start.
97+
98+
On some scenarios, you may want to ignore Sinks initialization errors.
99+
100+
For example, if Robusta is not allowed to connect to Slack, but you still want to receive notifications on the Robusta UI.
101+
102+
In order to enable that, add the below to ``globalConfig`` in your ``generated_values.yaml`` file:
103+
104+
.. code-block:: yaml
105+
106+
globalConfig:
107+
continue_on_sink_errors: True
108+
93109
Learn More
94110
^^^^^^^^^^^^
95111

src/robusta/core/model/cluster_status.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class ActivityStats(BaseModel):
2323
holmesModel: Optional[str]
2424
clusterTimeZone: str
2525
errors: List[str]
26+
sinksInitializationErrors: bool = False
2627

2728

2829
class ClusterStatus(BaseModel):

src/robusta/core/reporting/base.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from robusta.core.reporting.consts import FindingSource, FindingSubjectType, FindingType
1818
from robusta.integrations.kubernetes.api_client_utils import get_namespace_labels
1919
from robusta.utils.scope import BaseScopeMatcher
20+
from robusta.utils.time_utils import current_utc_timestamp
2021

2122

2223
class BaseBlock(BaseModel):
@@ -289,7 +290,7 @@ def __init__(
289290
self.fingerprint = (
290291
fingerprint if fingerprint else self.__calculate_fingerprint(subject, source, aggregation_key)
291292
)
292-
self.starts_at = starts_at if starts_at else datetime.now()
293+
self.starts_at = starts_at if starts_at else current_utc_timestamp()
293294
self.ends_at = ends_at
294295
self.dirty = False
295296

src/robusta/core/sinks/robusta/dal/model_conversion.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from robusta.core.reporting.holmes import HolmesChatResultsBlock, HolmesResultsBlock, ToolCallResult
2727
from robusta.core.sinks.transformer import Transformer
2828
from robusta.utils.parsing import datetime_to_db_str
29+
from robusta.utils.time_utils import current_utc_timestamp
2930

3031

3132
class ModelConversion:
@@ -52,7 +53,7 @@ def to_finding_json(account_id: str, cluster_id: str, finding: Finding):
5253
"account_id": account_id,
5354
"video_links": [link.dict() for link in finding.links], # TD: Migrate column in table.
5455
"starts_at": datetime_to_db_str(finding.starts_at),
55-
"updated_at": datetime_to_db_str(datetime.now()),
56+
"updated_at": datetime_to_db_str(current_utc_timestamp()),
5657
}
5758

5859
if finding.creation_date:

src/robusta/core/sinks/robusta/robusta_sink.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,7 @@ def __update_cluster_status(self):
563563
holmesModel=holmes_model,
564564
clusterTimeZone=str(datetime.now().astimezone().tzinfo),
565565
errors=self.__errors,
566+
sinksInitializationErrors=self.registry.has_sink_initialization_errors(),
566567
)
567568

568569
# checking the status of relay connection

src/robusta/model/config.py

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import logging
22
from collections import defaultdict
3-
from typing import Dict, List, Optional
3+
from typing import Dict, List, Optional, Tuple
44

55
from robusta.core.model.env_vars import PROMETHEUS_ENABLED, RUNNER_VERSION
66
from robusta.core.playbooks.actions_registry import ActionsRegistry
@@ -46,7 +46,8 @@ def construct_new_sinks(
4646
new_sinks_config: List[SinkConfigBase],
4747
existing_sinks: Dict[str, SinkBase],
4848
registry,
49-
) -> Dict[str, SinkBase]:
49+
continue_on_sink_errors: bool = False,
50+
) -> Tuple[Dict[str, SinkBase], bool]:
5051
new_sink_names = [sink_config.get_name() for sink_config in new_sinks_config]
5152
# remove deleted sinks
5253
deleted_sink_names = [sink_name for sink_name in existing_sinks.keys() if sink_name not in new_sink_names]
@@ -56,6 +57,7 @@ def construct_new_sinks(
5657
del existing_sinks[deleted_sink]
5758

5859
new_sinks: Dict[str, SinkBase] = dict()
60+
has_sink_errors = False
5961

6062
# Reload sinks, order does matter and should be loaded & added to the dict by config order.
6163
for sink_config in new_sinks_config:
@@ -68,24 +70,33 @@ def construct_new_sinks(
6870

6971
sink_name = sink_config.get_name()
7072
exists_sink = existing_sinks.get(sink_name, None)
71-
if not exists_sink:
72-
logging.info(f"Adding {type(sink_config)} sink named {sink_name}")
73-
new_sinks[sink_name] = SinkFactory.create_sink(sink_config, registry)
74-
continue
75-
76-
is_global_config_changed = exists_sink.is_global_config_changed()
77-
is_sink_changed = sink_config.get_params() != exists_sink.params or is_global_config_changed
78-
if is_sink_changed:
79-
config_change_msg = "due to global config change" if is_global_config_changed else "due to param change"
80-
logging.info(f"Updating {type(sink_config)} sink named {sink_config.get_name()} {config_change_msg}")
81-
exists_sink.stop()
82-
new_sinks[sink_name] = SinkFactory.create_sink(sink_config, registry)
83-
continue
84-
85-
logging.info("Sink %s not changed", sink_name)
86-
new_sinks[sink_name] = exists_sink
87-
88-
return new_sinks
73+
74+
try:
75+
if not exists_sink:
76+
logging.info(f"Adding {type(sink_config)} sink named {sink_name}")
77+
new_sinks[sink_name] = SinkFactory.create_sink(sink_config, registry)
78+
continue
79+
80+
is_global_config_changed = exists_sink.is_global_config_changed()
81+
is_sink_changed = sink_config.get_params() != exists_sink.params or is_global_config_changed
82+
if is_sink_changed:
83+
config_change_msg = "due to global config change" if is_global_config_changed else "due to param change"
84+
logging.info(f"Updating {type(sink_config)} sink named {sink_config.get_name()} {config_change_msg}")
85+
exists_sink.stop()
86+
new_sinks[sink_name] = SinkFactory.create_sink(sink_config, registry)
87+
continue
88+
89+
logging.info("Sink %s not changed", sink_name)
90+
new_sinks[sink_name] = exists_sink
91+
92+
except Exception as e:
93+
has_sink_errors = True
94+
logging.error(f"Failed to initialize sink {sink_name}: {e}", exc_info=True)
95+
if not continue_on_sink_errors:
96+
raise
97+
# Skip this sink if continue_on_sink_errors is True
98+
99+
return new_sinks, has_sink_errors
89100

90101

91102
class PlaybooksRegistry:
@@ -171,6 +182,7 @@ class Registry:
171182
prometheus_enabled=PROMETHEUS_ENABLED,
172183
)
173184
_pubsub: EventsPubSub = EventsPubSub()
185+
_sink_initialization_errors: bool = False
174186

175187
def set_light_actions(self, light_actions: List[str]):
176188
self._light_actions = light_actions
@@ -231,3 +243,9 @@ def subscribe(self, event_name: str, handler: EventHandler):
231243

232244
def unsubscribe(self, event_name: str, handler: EventHandler):
233245
self._pubsub.unsubscribe(event_name, handler)
246+
247+
def set_sink_initialization_errors(self, has_errors: bool):
248+
self._sink_initialization_errors = has_errors
249+
250+
def has_sink_initialization_errors(self) -> bool:
251+
return self._sink_initialization_errors

src/robusta/runner/config_loader.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,10 +287,16 @@ def __prepare_runtime_config(
287287
sinks_registry: SinksRegistry,
288288
actions_registry: ActionsRegistry,
289289
registry: Registry,
290-
) -> (SinksRegistry, PlaybooksRegistry):
290+
) -> tuple[SinksRegistry, PlaybooksRegistry]:
291291
existing_sinks = sinks_registry.get_all() if sinks_registry else {}
292-
new_sinks = SinksRegistry.construct_new_sinks(runner_config.sinks_config, existing_sinks, registry)
292+
new_sinks, has_sink_errors = SinksRegistry.construct_new_sinks(
293+
runner_config.sinks_config,
294+
existing_sinks,
295+
registry,
296+
runner_config.global_config.get("continue_on_sink_errors", False)
297+
)
293298
sinks_registry = SinksRegistry(new_sinks)
299+
registry.set_sink_initialization_errors(has_sink_errors)
294300

295301
# TODO we will replace it with a more generic mechanism, as part of the triggers separation task
296302
# First, we load the internal playbooks, then add the user activated playbooks

src/robusta/utils/time_utils.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from datetime import datetime, timezone
2+
3+
4+
def current_utc_timestamp() -> datetime:
5+
return datetime.now(timezone.utc)

tests/config/test_sink_registry.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def setup_method(self, method):
2424
self.config_data = yaml.safe_load(CONFIG)
2525
self.runner_config = RunnerConfig(**self.config_data)
2626
self.registry = Registry()
27-
new_sinks = SinksRegistry.construct_new_sinks(self.runner_config.sinks_config, {}, self.registry)
27+
new_sinks, _ = SinksRegistry.construct_new_sinks(self.runner_config.sinks_config, {}, self.registry)
2828
self.sinks_registry = SinksRegistry(new_sinks)
2929

3030

@@ -34,20 +34,20 @@ def test_create_sinks(self):
3434

3535
def test_delete_sinks(self):
3636
del self.runner_config.sinks_config[0]
37-
new_sinks = SinksRegistry.construct_new_sinks(self.runner_config.sinks_config, self.sinks_registry.sinks, self.registry)
37+
new_sinks, _ = SinksRegistry.construct_new_sinks(self.runner_config.sinks_config, self.sinks_registry.sinks, self.registry)
3838
assert list(new_sinks.keys()) == ["my_sink2"]
3939

4040

4141
def test_add_new_sinks(self):
4242
self.config_data["sinks_config"].insert(1, {'mail_sink': {'name': 'my_sink3', 'mailto': 'mailtos://user:password@example.com?from=a@x&to=b@y'}})
4343
self.runner_config = RunnerConfig(**self.config_data)
44-
new_sinks = SinksRegistry.construct_new_sinks(self.runner_config.sinks_config, self.sinks_registry.sinks, self.registry)
44+
new_sinks, _ = SinksRegistry.construct_new_sinks(self.runner_config.sinks_config, self.sinks_registry.sinks, self.registry)
4545
assert list(new_sinks.keys()) == ["my_sink1", "my_sink3", "my_sink2"]
4646

4747

4848
def test_sink_config_changed(self):
4949
new_email = "dev@robusta.dev"
5050
self.runner_config.sinks_config[0].mail_sink.mailto = new_email
51-
new_sinks = SinksRegistry.construct_new_sinks(self.runner_config.sinks_config, self.sinks_registry.sinks, self.registry)
51+
new_sinks, _ = SinksRegistry.construct_new_sinks(self.runner_config.sinks_config, self.sinks_registry.sinks, self.registry)
5252
assert new_sinks["my_sink1"].params.mailto == new_email
5353
assert list(new_sinks.keys()) == ["my_sink1", "my_sink2"]

0 commit comments

Comments
 (0)