robusta-api 0.46.0__tar.gz → 0.47.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {robusta_api-0.46.0 → robusta_api-0.47.0}/PKG-INFO +1 -1
- {robusta_api-0.46.0 → robusta_api-0.47.0}/pyproject.toml +1 -1
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/_version.py +1 -1
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/reporting/blocks.py +116 -22
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/sink_base.py +11 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/slack/slack_sink.py +10 -1
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/slack/sender.py +150 -11
- {robusta_api-0.46.0 → robusta_api-0.47.0}/LICENSE +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/api/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/clients/robusta_client.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/discovery/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/discovery/discovery.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/discovery/resource_names.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/discovery/top_service_resolver.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/discovery/utils.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/exceptions.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/model/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/model/base_params.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/model/cluster_status.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/model/env_vars.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/model/events.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/model/helm_release.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/model/jobs.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/model/k8s_operation_type.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/model/namespaces.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/model/nodes.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/model/openshift_group.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/model/pods.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/model/runner_config.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/model/services.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/persistency/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/persistency/in_memory.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/persistency/scheduled_jobs_states_dal.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/actions_registry.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/base_trigger.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/common.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/container_playbook_utils.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/crash_reporter.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/generation.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/internal/CRD_API_DOCUMENTATION.md +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/internal/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/internal/ai_integration.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/internal/crds.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/internal/discovery_events.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/job_utils.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/node_enrichment_utils.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/node_playbook_utils.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/oom_killer_utils.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/playbook_utils.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/playbooks_event_handler.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/playbooks_event_handler_impl.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/pod_utils/crashloop_utils.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/pod_utils/imagepull_utils.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/pod_utils/pending_pod_utils.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/prometheus_enrichment_utils.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/trigger.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/pubsub/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/pubsub/event_emitter.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/pubsub/event_subscriber.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/pubsub/events_pubsub.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/reporting/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/reporting/action_requests.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/reporting/base.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/reporting/callbacks.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/reporting/consts.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/reporting/custom_rendering.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/reporting/finding_subjects.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/reporting/holmes.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/reporting/url_helpers.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/reporting/utils.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/schedule/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/schedule/model.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/schedule/scheduler.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/common/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/common/channel_transformer.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/common/html_tools.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/datadog/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/datadog/datadog_sink.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/datadog/datadog_sink_params.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/discord/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/discord/discord_sink.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/discord/discord_sink_params.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/file/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/file/file_sink.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/file/file_sink_params.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/file/object_traverser.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/google_chat/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/google_chat/google_chat.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/google_chat/google_chat_params.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/incidentio/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/incidentio/incidentio_api.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/incidentio/incidentio_client.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/incidentio/incidentio_sink.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/incidentio/incidentio_sink_params.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/jira/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/jira/jira_sink.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/jira/jira_sink_params.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/kafka/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/kafka/kafka_sink.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/kafka/kafka_sink_params.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/mail/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/mail/mail_sink.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/mail/mail_sink_params.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/mattermost/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/mattermost/mattermost_sink.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/mattermost/mattermost_sink_params.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/msteams/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/msteams/msteams_sink.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/msteams/msteams_sink_params.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/msteams/msteams_webhook_tranformer.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/opsgenie/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/opsgenie/opsgenie_sink.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/opsgenie/opsgenie_sink_params.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/pagerduty/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/pagerduty/pagerduty_sink.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/pagerduty/pagerduty_sink_params.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/pushover/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/pushover/pushover_client.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/pushover/pushover_sink.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/pushover/pushover_sink_params.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/robusta/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/robusta/dal/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/robusta/dal/model_conversion.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/robusta/dal/supabase_dal.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/robusta/discovery_metrics.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/robusta/prometheus_discovery_utils.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/robusta/robusta_sink.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/robusta/robusta_sink_params.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/robusta/rrm/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/robusta/rrm/account_resource_fetcher.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/robusta/rrm/base_resource_manager.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/robusta/rrm/prometheus_alert_resource_manager.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/robusta/rrm/rrm.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/robusta/rrm/types.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/rocketchat/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/rocketchat/rocketchat_sink.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/rocketchat/rocketchat_sink_params.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/servicenow/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/servicenow/servicenow_sink.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/servicenow/servicenow_sink_params.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/sink_base_params.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/sink_config.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/sink_factory.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/slack/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/slack/preview/slack_sink_preview.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/slack/preview/slack_sink_preview_params.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/slack/slack_sink_params.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/slack/templates/header.j2 +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/slack/templates/template_loader.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/telegram/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/telegram/telegram_client.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/telegram/telegram_sink.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/telegram/telegram_sink_params.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/timing.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/transformer.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/victorops/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/victorops/victorops_sink.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/victorops/victorops_sink_params.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/webex/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/webex/webex_sink.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/webex/webex_sink_params.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/webhook/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/webhook/webhook_sink.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/webhook/webhook_sink_params.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/yamessenger/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/yamessenger/yamessenger_client.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/yamessenger/yamessenger_sink.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/yamessenger/yamessenger_sink_params.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/zulip/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/zulip/zulip_sink.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/sinks/zulip/zulip_sink_params.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/stream/utils.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/triggers/container_oom_killed_trigger.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/triggers/custom_triggers.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/triggers/error_event_trigger.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/triggers/helm_releases_triggers.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/triggers/job_failed_trigger.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/triggers/multi_resources_trigger.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/triggers/oom_killed_trigger_base.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/triggers/pod_crash_loop_trigger.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/triggers/pod_evicted_trigger.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/triggers/pod_image_pull_backoff.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/triggers/pod_oom_killed_trigger.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/argocd/argocd_client.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/common/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/common/requests.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/discord/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/discord/sender.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/git/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/git/git_repo.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/git/well_known_hosts.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/google_chat/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/google_chat/sender.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/grafana.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/helper.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/jira/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/jira/client.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/jira/sender.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/kubernetes/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/kubernetes/api_client_utils.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/kubernetes/autogenerated/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/kubernetes/autogenerated/events.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/kubernetes/autogenerated/models.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/kubernetes/autogenerated/triggers.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/kubernetes/autogenerated/v1/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/kubernetes/autogenerated/v1/models.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/kubernetes/base_event.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/kubernetes/base_triggers.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/kubernetes/custom_crds.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/kubernetes/custom_models.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/kubernetes/model_not_found_exception.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/kubernetes/process_utils.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/kubernetes/templates.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/mail/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/mail/sender.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/mattermost/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/mattermost/client.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/mattermost/sender.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/msteams/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/msteams/msteams_adaptive_card_files.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/msteams/msteams_adaptive_card_files_image.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/msteams/msteams_adaptive_card_files_text.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/msteams/msteams_elements/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/msteams/msteams_elements/msteams_action.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/msteams/msteams_elements/msteams_base.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/msteams/msteams_elements/msteams_card.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/msteams/msteams_elements/msteams_column.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/msteams/msteams_elements/msteams_container.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/msteams/msteams_elements/msteams_images.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/msteams/msteams_elements/msteams_table.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/msteams/msteams_elements/msteams_text_block.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/msteams/msteams_mark_down_fix_url.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/msteams/msteams_msg.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/msteams/sender.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/openshift/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/openshift/token.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/prometheus/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/prometheus/models.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/prometheus/trigger.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/prometheus/utils.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/receiver.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/resource_analysis/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/resource_analysis/cpu_analyzer.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/resource_analysis/memory_analyzer.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/resource_analysis/node_cpu_analyzer.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/resource_analysis/prometheus_analyzer.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/rocketchat/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/rocketchat/sender.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/scheduled/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/scheduled/event.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/scheduled/models.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/scheduled/playbook_scheduler.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/scheduled/playbook_scheduler_manager.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/scheduled/playbook_scheduler_manager_impl.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/scheduled/trigger.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/servicenow/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/servicenow/sender.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/slack/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/webex/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/webex/sender.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/zulip/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/integrations/zulip/sender.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/model/alert_relabel_config.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/model/config.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/model/playbook_action.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/model/playbook_definition.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/patch/patch.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/runner/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/runner/config_loader.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/runner/log_init.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/runner/main.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/runner/not_found_exception.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/runner/object_updater.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/runner/process_setup.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/runner/ssl_utils.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/runner/telemetry.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/runner/telemetry_service.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/runner/web.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/runner/web_api.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/utils/__init__.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/utils/auth_provider.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/utils/base64_utils.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/utils/cluster_provider_discovery.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/utils/common.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/utils/decorators.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/utils/docs.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/utils/documented_pydantic.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/utils/error_codes.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/utils/file_system_watcher.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/utils/function_hashes.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/utils/json_schema.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/utils/parsing.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/utils/rate_limiter.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/utils/scope.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/utils/server_start.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/utils/service_discovery.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/utils/silence_utils.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/utils/stack_tracer.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/utils/task_queue.py +0 -0
- {robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/utils/time_utils.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# this is updated by .github/workflows/release.yaml
|
|
2
|
-
__version__ = "0.
|
|
2
|
+
__version__ = "0.47.0"
|
|
@@ -38,6 +38,9 @@ from robusta.core.reporting.custom_rendering import render_value
|
|
|
38
38
|
|
|
39
39
|
BLOCK_SIZE_LIMIT = 2997 # due to slack block size limit of 3000
|
|
40
40
|
|
|
41
|
+
# Don't shrink a text column below this, or its values become unreadable.
|
|
42
|
+
TABLE_MIN_COLUMN_WIDTH = 4
|
|
43
|
+
|
|
41
44
|
|
|
42
45
|
class MarkdownBlock(BaseBlock):
|
|
43
46
|
"""
|
|
@@ -364,25 +367,53 @@ class TableBlock(BaseBlock):
|
|
|
364
367
|
if table_format:
|
|
365
368
|
self.metadata["format"] = TableBlockFormat.vertical.value
|
|
366
369
|
|
|
370
|
+
@classmethod
|
|
371
|
+
def __is_number(cls, value) -> bool:
|
|
372
|
+
try:
|
|
373
|
+
float(str(value))
|
|
374
|
+
return True
|
|
375
|
+
except (TypeError, ValueError):
|
|
376
|
+
return False
|
|
377
|
+
|
|
378
|
+
@classmethod
|
|
379
|
+
def __numeric_column_indices(cls, rendered_rows, num_columns: int) -> set:
|
|
380
|
+
# Numeric columns (e.g. counters) are kept at full width.
|
|
381
|
+
numeric_indices = set()
|
|
382
|
+
for idx in range(num_columns):
|
|
383
|
+
non_empty = [str(row[idx]) for row in rendered_rows if idx < len(row) and str(row[idx]).strip()]
|
|
384
|
+
if non_empty and all(cls.__is_number(v) for v in non_empty):
|
|
385
|
+
numeric_indices.add(idx)
|
|
386
|
+
return numeric_indices
|
|
387
|
+
|
|
367
388
|
@classmethod
|
|
368
389
|
def __calc_max_width(cls, headers, rendered_rows, table_max_width: int) -> List[int]:
|
|
369
|
-
#
|
|
370
|
-
|
|
371
|
-
columns_max_widths = [
|
|
390
|
+
# Keep the total table width within the max, otherwise it renders corrupted.
|
|
391
|
+
num_columns = max(len(headers), max((len(row) for row in rendered_rows), default=0))
|
|
392
|
+
columns_max_widths = [0] * num_columns
|
|
393
|
+
for idx, header in enumerate(headers):
|
|
394
|
+
columns_max_widths[idx] = len(str(header))
|
|
372
395
|
for row in rendered_rows:
|
|
373
396
|
for idx, val in enumerate(row):
|
|
374
397
|
columns_max_widths[idx] = max(len(str(val)), columns_max_widths[idx])
|
|
375
398
|
|
|
376
|
-
if sum(columns_max_widths)
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
399
|
+
if sum(columns_max_widths) <= table_max_width:
|
|
400
|
+
return columns_max_widths
|
|
401
|
+
|
|
402
|
+
# Only shrink text columns, so numeric columns never get wrapped. If every
|
|
403
|
+
# column is numeric, leave the widths as-is rather than wrapping numbers.
|
|
404
|
+
numeric_indices = cls.__numeric_column_indices(rendered_rows, num_columns)
|
|
405
|
+
shrinkable = [idx for idx in range(num_columns) if idx not in numeric_indices]
|
|
406
|
+
if not shrinkable:
|
|
407
|
+
return columns_max_widths
|
|
408
|
+
|
|
409
|
+
# Water-fill: trim the widest shrinkable column by one char at a time (fair
|
|
410
|
+
# distribution), never below TABLE_MIN_COLUMN_WIDTH.
|
|
411
|
+
while sum(columns_max_widths) > table_max_width:
|
|
412
|
+
candidates = [idx for idx in shrinkable if columns_max_widths[idx] > TABLE_MIN_COLUMN_WIDTH]
|
|
413
|
+
if not candidates:
|
|
414
|
+
break
|
|
415
|
+
widest = max(candidates, key=lambda idx: columns_max_widths[idx])
|
|
416
|
+
columns_max_widths[widest] -= 1
|
|
386
417
|
|
|
387
418
|
return columns_max_widths
|
|
388
419
|
|
|
@@ -410,25 +441,88 @@ class TableBlock(BaseBlock):
|
|
|
410
441
|
|
|
411
442
|
return "\n".join(lines[:lines_to_include]) + truncator
|
|
412
443
|
|
|
444
|
+
@staticmethod
|
|
445
|
+
def default_omission_note(omitted_rows: List[List[str]]) -> str:
|
|
446
|
+
return f"... {len(omitted_rows)} more rows not shown"
|
|
447
|
+
|
|
448
|
+
def __render_within_budget(self, max_chars: int, table_max_width: int, table_fmt: str, omission_note) -> str:
|
|
449
|
+
"""Render the table, dropping whole rows to fit max_chars.
|
|
450
|
+
|
|
451
|
+
Values themselves are never cut - long ones wrap onto extra lines - so rows must be
|
|
452
|
+
dropped as complete units. Cutting by physical line would leave a wrapped value's
|
|
453
|
+
continuation behind and show a partial value as if it were the whole thing.
|
|
454
|
+
"""
|
|
455
|
+
rows = self.__to_strings_rows(self.render_rows())
|
|
456
|
+
# Column widths are computed once from all rows, so dropping rows never reflows
|
|
457
|
+
# the columns and the rendered size shrinks monotonically with the row count.
|
|
458
|
+
col_max_width = self.__calc_max_width(self.headers, rows, table_max_width)
|
|
459
|
+
|
|
460
|
+
def render(subset: List[List[str]]) -> str:
|
|
461
|
+
if not subset: # tabulate raises IndexError on maxcolwidths with no rows
|
|
462
|
+
return tabulate(subset, headers=self.headers, tablefmt=table_fmt)
|
|
463
|
+
return tabulate(subset, headers=self.headers, tablefmt=table_fmt, maxcolwidths=col_max_width)
|
|
464
|
+
|
|
465
|
+
full = render(rows)
|
|
466
|
+
if len(full) <= max_chars:
|
|
467
|
+
return full
|
|
468
|
+
|
|
469
|
+
# Largest number of whole rows that fits alongside the "N more rows" note.
|
|
470
|
+
best = None
|
|
471
|
+
low, high = 0, len(rows)
|
|
472
|
+
while low <= high:
|
|
473
|
+
mid = (low + high) // 2
|
|
474
|
+
candidate = render(rows[:mid])
|
|
475
|
+
if mid < len(rows):
|
|
476
|
+
candidate = f"{candidate}\n{omission_note(rows[mid:])}"
|
|
477
|
+
if len(candidate) <= max_chars:
|
|
478
|
+
best = candidate
|
|
479
|
+
low = mid + 1
|
|
480
|
+
else:
|
|
481
|
+
high = mid - 1
|
|
482
|
+
|
|
483
|
+
# Fall back to the line-based trim if not even a header-only table fits.
|
|
484
|
+
return best if best is not None else self.__trim_rows(full, max_chars)
|
|
485
|
+
|
|
413
486
|
@classmethod
|
|
414
487
|
def __to_strings_rows(cls, rows):
|
|
415
488
|
# This is just to assert all row column values are strings. Tabulate might fail on other types
|
|
416
489
|
return [list(map(lambda column_value: str(column_value), row)) for row in rows]
|
|
417
490
|
|
|
418
|
-
def to_markdown(self, max_chars=None, add_table_header: bool = True) -> MarkdownBlock:
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
if max_chars is
|
|
425
|
-
|
|
426
|
-
|
|
491
|
+
def to_markdown(self, max_chars=None, add_table_header: bool = True, omission_note=None) -> MarkdownBlock:
|
|
492
|
+
""":param omission_note: builds the note shown when rows are dropped to fit the size limit.
|
|
493
|
+
Receives the dropped rows, so callers can summarise what was left out (e.g. residual counts)."""
|
|
494
|
+
# Stay under BLOCK_SIZE_LIMIT even when no caller-supplied limit: MarkdownBlock
|
|
495
|
+
# would otherwise blindly cut the text and lose the closing ``` fence, which
|
|
496
|
+
# makes the whole table render as unformatted (non-monospace) text.
|
|
497
|
+
budget = BLOCK_SIZE_LIMIT - 1 if max_chars is None else min(max_chars, BLOCK_SIZE_LIMIT - 1)
|
|
498
|
+
|
|
499
|
+
table_header = f"{self.table_name}\n" if self.table_name and add_table_header else ""
|
|
500
|
+
opening_fence, suffix = "```\n", "\n```"
|
|
501
|
+
# The table name is caller-supplied and can be arbitrarily long, so trim it rather than
|
|
502
|
+
# let the envelope alone push the block past the limit and cost us the closing fence.
|
|
503
|
+
table_header = table_header[: max(0, budget - len(opening_fence) - len(suffix))]
|
|
504
|
+
prefix = f"{table_header}{opening_fence}"
|
|
505
|
+
|
|
506
|
+
content_budget = budget - len(prefix) - len(suffix)
|
|
507
|
+
# With no room left for content, emit an empty (but properly closed) block rather than
|
|
508
|
+
# an overflow marker that wouldn't fit either.
|
|
509
|
+
table_contents = (
|
|
510
|
+
self.__render_within_budget(
|
|
511
|
+
content_budget,
|
|
512
|
+
PRINTED_TABLE_MAX_WIDTH,
|
|
513
|
+
"presto",
|
|
514
|
+
omission_note or self.default_omission_note,
|
|
515
|
+
)
|
|
516
|
+
if content_budget > 0
|
|
517
|
+
else ""
|
|
518
|
+
)
|
|
427
519
|
|
|
428
520
|
return MarkdownBlock(f"{prefix}{table_contents}{suffix}")
|
|
429
521
|
|
|
430
522
|
def to_table_string(self, table_max_width: int = PRINTED_TABLE_MAX_WIDTH, table_fmt: str = "presto") -> str:
|
|
431
523
|
rendered_rows = self.__to_strings_rows(self.render_rows())
|
|
524
|
+
if not rendered_rows: # tabulate raises IndexError on maxcolwidths with no rows
|
|
525
|
+
return tabulate(rendered_rows, headers=self.headers, tablefmt=table_fmt)
|
|
432
526
|
col_max_width = self.__calc_max_width(self.headers, rendered_rows, table_max_width)
|
|
433
527
|
return tabulate(
|
|
434
528
|
rendered_rows,
|
|
@@ -36,6 +36,11 @@ class NotificationSummary(BaseModel):
|
|
|
36
36
|
start_ts: float = Field(default_factory=lambda: time.time()) # Timestamp of the first notification
|
|
37
37
|
# Keys for the table are determined by grouping.notification_mode.summary.by
|
|
38
38
|
summary_table: DefaultDict[KeyT, List[int]] = None
|
|
39
|
+
# The full summary table is attached as a file when it doesn't fit in the message. Files
|
|
40
|
+
# can't be edited in place, so the previous one is deleted whenever a new one is uploaded.
|
|
41
|
+
attachment_file_id: Optional[str] = None
|
|
42
|
+
attachment_permalink: Optional[str] = None
|
|
43
|
+
attachment_ts: float = 0 # when the attachment was last refreshed
|
|
39
44
|
|
|
40
45
|
def register_notification(self, summary_key: KeyT, resolved: bool, interval: int):
|
|
41
46
|
now_ts = time.time()
|
|
@@ -45,6 +50,12 @@ class NotificationSummary(BaseModel):
|
|
|
45
50
|
self.summary_table = defaultdict(lambda: [0, 0])
|
|
46
51
|
self.start_ts = now_ts
|
|
47
52
|
self.message_id = None
|
|
53
|
+
# Forget the previous interval's attachment entirely. Its summary message stays in
|
|
54
|
+
# the channel and keeps linking that file, so it must not be deleted later - only
|
|
55
|
+
# files superseded within the same message may be.
|
|
56
|
+
self.attachment_file_id = None
|
|
57
|
+
self.attachment_permalink = None
|
|
58
|
+
self.attachment_ts = 0
|
|
48
59
|
self.summary_table[summary_key][idx] += 1
|
|
49
60
|
|
|
50
61
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import json
|
|
1
2
|
import logging
|
|
3
|
+
from urllib.parse import urlencode
|
|
2
4
|
|
|
3
5
|
from robusta.core.model.env_vars import ROBUSTA_UI_DOMAIN
|
|
4
6
|
from robusta.core.reporting.base import Finding, FindingStatus
|
|
@@ -89,6 +91,7 @@ class SlackSink(SinkBase):
|
|
|
89
91
|
investigate_uri=investigate_uri,
|
|
90
92
|
grouping_interval=self.params.grouping.interval,
|
|
91
93
|
channel=resolved_channel,
|
|
94
|
+
summary_state=notification_summary,
|
|
92
95
|
)
|
|
93
96
|
notification_summary.message_id = slack_thread_ts
|
|
94
97
|
should_send_notification = self.params.grouping.notification_mode.summary.threaded
|
|
@@ -104,7 +107,13 @@ class SlackSink(SinkBase):
|
|
|
104
107
|
)
|
|
105
108
|
|
|
106
109
|
def get_timeline_uri(self, account_id: str, cluster_name: str) -> str:
|
|
107
|
-
|
|
110
|
+
# The timeline reads its filters as JSON-encoded query params, and expects "clusters"
|
|
111
|
+
# (a list) rather than a single "cluster" - passing the latter left the link unfiltered.
|
|
112
|
+
params = {
|
|
113
|
+
"account_id": account_id,
|
|
114
|
+
"clusters": json.dumps([cluster_name]),
|
|
115
|
+
}
|
|
116
|
+
return f"{ROBUSTA_UI_DOMAIN}/graphs?{urlencode(params)}"
|
|
108
117
|
|
|
109
118
|
def __replace_callback_with_string(self, slack_message, block_id, message_string):
|
|
110
119
|
"""
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import copy
|
|
2
2
|
import logging
|
|
3
|
+
import time
|
|
3
4
|
import ssl
|
|
4
5
|
import tempfile
|
|
5
6
|
import re
|
|
6
7
|
from datetime import datetime, timedelta
|
|
7
8
|
from itertools import chain
|
|
8
|
-
from typing import Any, Dict, List, Optional, Set, Union
|
|
9
|
+
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
|
9
10
|
import certifi
|
|
10
11
|
import humanize
|
|
11
12
|
from dateutil import tz
|
|
@@ -56,6 +57,11 @@ ACTION_TRIGGER_PLAYBOOK = "trigger_playbook"
|
|
|
56
57
|
ACTION_LINK = "link"
|
|
57
58
|
SlackBlock = Dict[str, Any]
|
|
58
59
|
MAX_BLOCK_CHARS = 3000
|
|
60
|
+
# A file can hold a far wider table than a message can.
|
|
61
|
+
SUMMARY_ATTACHMENT_TABLE_WIDTH = 250
|
|
62
|
+
# The summary message is rewritten on every notification; don't re-upload the attachment
|
|
63
|
+
# that often.
|
|
64
|
+
SUMMARY_ATTACHMENT_REFRESH_SECONDS = 60
|
|
59
65
|
MENTION_PATTERN = re.compile(r"<[^>]+>")
|
|
60
66
|
|
|
61
67
|
|
|
@@ -860,6 +866,98 @@ class SlackSender:
|
|
|
860
866
|
output_blocks=slack_blocks,
|
|
861
867
|
)
|
|
862
868
|
|
|
869
|
+
@staticmethod
|
|
870
|
+
def __summary_table_block(table_block: TableBlock) -> Tuple[BaseBlock, int]:
|
|
871
|
+
"""Render the summary table, noting the totals of any rows dropped to fit the size limit.
|
|
872
|
+
|
|
873
|
+
Without the residual counts the numbers in the table wouldn't add up to the notification
|
|
874
|
+
count in the message header, making the summary look wrong.
|
|
875
|
+
|
|
876
|
+
Returns the block and the number of rows that didn't fit.
|
|
877
|
+
"""
|
|
878
|
+
if len(table_block.headers) <= 2:
|
|
879
|
+
return table_block, 0 # rendered as a bullet list rather than a table, see __to_slack_table
|
|
880
|
+
|
|
881
|
+
omitted_count = 0
|
|
882
|
+
|
|
883
|
+
def omission_note(omitted_rows: List[List[str]]) -> str:
|
|
884
|
+
nonlocal omitted_count
|
|
885
|
+
omitted_count = len(omitted_rows)
|
|
886
|
+
fired = resolved = 0
|
|
887
|
+
for row in omitted_rows:
|
|
888
|
+
try:
|
|
889
|
+
fired += int(row[-2])
|
|
890
|
+
resolved += int(row[-1])
|
|
891
|
+
except (IndexError, ValueError): # not the expected numeric columns - just count rows
|
|
892
|
+
return TableBlock.default_omission_note(omitted_rows)
|
|
893
|
+
return f"... {len(omitted_rows)} more groups ({fired} fired, {resolved} resolved) not shown"
|
|
894
|
+
|
|
895
|
+
block = table_block.to_markdown(omission_note=omission_note)
|
|
896
|
+
return block, omitted_count
|
|
897
|
+
|
|
898
|
+
def __refresh_summary_attachment(
|
|
899
|
+
self, table_block: TableBlock, summary_state, channel_id: Optional[str], thread_ts: Optional[str]
|
|
900
|
+
) -> Tuple[Optional[str], Optional[str]]:
|
|
901
|
+
"""Attach the complete table as a file, since the message can only show part of it.
|
|
902
|
+
|
|
903
|
+
Slack files are immutable, but the summary message is rewritten on every notification, so
|
|
904
|
+
a fresh file is uploaded rather than edited. That is throttled - re-uploading on every
|
|
905
|
+
single notification would be wasteful.
|
|
906
|
+
|
|
907
|
+
Returns the permalink and the id of the file it supersedes. The caller must only delete
|
|
908
|
+
that file once the message actually points at the new one, otherwise a failed update
|
|
909
|
+
leaves the message linking a file that no longer exists.
|
|
910
|
+
"""
|
|
911
|
+
if summary_state is None:
|
|
912
|
+
return None, None
|
|
913
|
+
|
|
914
|
+
if not channel_id or not thread_ts:
|
|
915
|
+
# The file is shared into the summary message's own thread, so it needs that message
|
|
916
|
+
# to exist first. This only defers the attachment past the very first summary - the
|
|
917
|
+
# message is updated on the next notification, and the file appears then.
|
|
918
|
+
logging.debug("Summary message not posted yet, deferring the attachment")
|
|
919
|
+
return None, None
|
|
920
|
+
|
|
921
|
+
now = time.time()
|
|
922
|
+
if summary_state.attachment_permalink and now - summary_state.attachment_ts < SUMMARY_ATTACHMENT_REFRESH_SECONDS:
|
|
923
|
+
return summary_state.attachment_permalink, None
|
|
924
|
+
|
|
925
|
+
# A much wider table fits in a file than in a message, so nothing wraps there.
|
|
926
|
+
contents = table_block.to_table_string(table_max_width=SUMMARY_ATTACHMENT_TABLE_WIDTH)
|
|
927
|
+
file_block = FileBlock("alerts-summary.txt", contents.encode("utf-8"))
|
|
928
|
+
try:
|
|
929
|
+
resp = self.slack_client.files_upload_v2(
|
|
930
|
+
# Without a channel the file is only linkable, not shared: the permalink opens
|
|
931
|
+
# an empty preview that other members can't read or download. Sharing it into
|
|
932
|
+
# the summary's thread rather than the channel keeps the channel readable when
|
|
933
|
+
# there are many groups - otherwise every group adds a file message to it.
|
|
934
|
+
channel=channel_id,
|
|
935
|
+
thread_ts=thread_ts,
|
|
936
|
+
title=file_block.filename,
|
|
937
|
+
filename=file_block.filename,
|
|
938
|
+
content=file_block.contents,
|
|
939
|
+
)
|
|
940
|
+
permalink = resp["file"]["permalink"]
|
|
941
|
+
new_file_id = resp["file"]["id"]
|
|
942
|
+
except Exception:
|
|
943
|
+
logging.exception("Failed to upload the full summary table to Slack")
|
|
944
|
+
return summary_state.attachment_permalink, None
|
|
945
|
+
|
|
946
|
+
superseded_file_id = summary_state.attachment_file_id
|
|
947
|
+
summary_state.attachment_file_id = new_file_id
|
|
948
|
+
summary_state.attachment_permalink = permalink
|
|
949
|
+
summary_state.attachment_ts = now
|
|
950
|
+
return permalink, superseded_file_id
|
|
951
|
+
|
|
952
|
+
def __delete_superseded_attachment(self, file_id: Optional[str]) -> None:
|
|
953
|
+
if not file_id:
|
|
954
|
+
return
|
|
955
|
+
# Best effort - a leftover file is preferable to failing the summary update.
|
|
956
|
+
try:
|
|
957
|
+
self.slack_client.files_delete(file=file_id)
|
|
958
|
+
except Exception as e:
|
|
959
|
+
logging.warning(f"Could not delete the superseded summary attachment: {e}")
|
|
960
|
+
|
|
863
961
|
def send_or_update_summary_message(
|
|
864
962
|
self,
|
|
865
963
|
group_by_classification_header: List[str],
|
|
@@ -873,12 +971,23 @@ class SlackSender:
|
|
|
873
971
|
investigate_uri: str = None,
|
|
874
972
|
grouping_interval: int = None, # in seconds
|
|
875
973
|
channel: str = None, # pre-resolved channel (when channel_override uses labels/annotations)
|
|
974
|
+
summary_state=None, # NotificationSummary, for tracking the attachment across updates
|
|
876
975
|
):
|
|
877
976
|
"""Create or update a summary message with tabular information about the amount of events
|
|
878
977
|
fired/resolved and a header describing the event group that this information concerns."""
|
|
879
978
|
rows = []
|
|
880
979
|
n_total_alerts = 0
|
|
881
|
-
|
|
980
|
+
# Sort by firing count, then resolved count (both descending), so that when the table
|
|
981
|
+
# is too long to fit in a message the rows that get dropped are the least significant
|
|
982
|
+
# ones rather than whichever groups happen to sort last alphabetically. The key is only
|
|
983
|
+
# a final tie-break, to keep the order stable between updates of the same message. It is
|
|
984
|
+
# normalised to strings first: keys hold raw attribute values, which may mix None with
|
|
985
|
+
# strings (e.g. "workload" is None for a finding with no service) and aren't comparable.
|
|
986
|
+
def sort_key(item):
|
|
987
|
+
group_key, (fired, resolved) = item
|
|
988
|
+
return -fired, -resolved, tuple(str(value) for value in group_key)
|
|
989
|
+
|
|
990
|
+
for key, value in sorted(summary_table.items(), key=sort_key):
|
|
882
991
|
# key is a tuple of attribute names; value is a 2-element list with
|
|
883
992
|
# the number of firing and resolved notifications.
|
|
884
993
|
row = list(str(e) for e in chain(key, value))
|
|
@@ -924,21 +1033,17 @@ class SlackSender:
|
|
|
924
1033
|
else:
|
|
925
1034
|
blocks.append(MarkdownBlock(text=cluster_txt))
|
|
926
1035
|
|
|
1036
|
+
summary_block, omitted_count = self.__summary_table_block(table_block)
|
|
927
1037
|
blocks.extend(
|
|
928
1038
|
[
|
|
929
1039
|
MarkdownBlock(f"*Matching criteria*: {group_by_criteria_str}"),
|
|
930
1040
|
MarkdownBlock(text=time_text),
|
|
931
|
-
|
|
1041
|
+
summary_block,
|
|
932
1042
|
]
|
|
933
1043
|
)
|
|
934
1044
|
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
output_blocks = []
|
|
939
|
-
for block in blocks:
|
|
940
|
-
output_blocks.extend(self.__to_slack(block, sink_params.name))
|
|
941
|
-
|
|
1045
|
+
# Resolve the target before building the rest of the message, so that bailing out
|
|
1046
|
+
# doesn't leave an uploaded attachment behind.
|
|
942
1047
|
if not channel:
|
|
943
1048
|
# Fallback: resolve channel without labels/annotations (only cluster_name will work)
|
|
944
1049
|
channel = ChannelTransformer.template(
|
|
@@ -948,6 +1053,7 @@ class SlackSender:
|
|
|
948
1053
|
{},
|
|
949
1054
|
{},
|
|
950
1055
|
)
|
|
1056
|
+
channel_name = channel
|
|
951
1057
|
if msg_ts is not None:
|
|
952
1058
|
method = self.slack_client.chat_update
|
|
953
1059
|
kwargs = {"ts": msg_ts}
|
|
@@ -964,14 +1070,47 @@ class SlackSender:
|
|
|
964
1070
|
method = self.slack_client.chat_postMessage
|
|
965
1071
|
kwargs = {}
|
|
966
1072
|
|
|
1073
|
+
attachment_permalink = None
|
|
1074
|
+
superseded_file_id = None
|
|
1075
|
+
if omitted_count:
|
|
1076
|
+
# Too many groups to show inline - attach the complete table as a file. The link has
|
|
1077
|
+
# to sit outside the table's code block, Slack doesn't render links inside one.
|
|
1078
|
+
# Only on the update path do we have both the channel id and the summary message to
|
|
1079
|
+
# thread the file under - `channel` has already been swapped for the id there.
|
|
1080
|
+
attachment_permalink, superseded_file_id = self.__refresh_summary_attachment(
|
|
1081
|
+
table_block, summary_state, channel if msg_ts is not None else None, msg_ts
|
|
1082
|
+
)
|
|
1083
|
+
if attachment_permalink:
|
|
1084
|
+
blocks.append(MarkdownBlock(text=f"<{attachment_permalink}|See all {len(rows)} groups> (attached)"))
|
|
1085
|
+
|
|
1086
|
+
if threaded:
|
|
1087
|
+
blocks.append(MarkdownBlock(text="See thread for individual alerts"))
|
|
1088
|
+
|
|
1089
|
+
output_blocks = []
|
|
1090
|
+
for block in blocks:
|
|
1091
|
+
output_blocks.extend(self.__to_slack(block, sink_params.name))
|
|
1092
|
+
|
|
1093
|
+
message_text = "Summary for: " + ", ".join(group_by_classification_header)
|
|
1094
|
+
if attachment_permalink:
|
|
1095
|
+
# Uploaded files have to be referenced from the message text, not just the blocks,
|
|
1096
|
+
# for Slack to actually share them (see prepare_slack_text).
|
|
1097
|
+
message_text = f"{message_text}\n<{attachment_permalink}|alerts-summary.txt>"
|
|
1098
|
+
|
|
967
1099
|
try:
|
|
968
1100
|
resp = method(
|
|
969
1101
|
channel=channel,
|
|
970
|
-
text=
|
|
1102
|
+
text=message_text,
|
|
971
1103
|
blocks=output_blocks,
|
|
972
1104
|
display_as_bot=True,
|
|
973
1105
|
**kwargs,
|
|
974
1106
|
)
|
|
1107
|
+
# Updating this message later needs the channel id, and a summary-only sink never
|
|
1108
|
+
# sends anything else that would record it (individual alerts, which do, are only
|
|
1109
|
+
# sent in threaded mode). Without this the message can never be updated, and a new
|
|
1110
|
+
# summary gets posted for every notification instead.
|
|
1111
|
+
self.channel_name_to_id[channel_name] = resp["channel"]
|
|
1112
|
+
# Only now that the message points at the new file is the old one safe to remove.
|
|
1113
|
+
self.__delete_superseded_attachment(superseded_file_id)
|
|
975
1114
|
return resp["ts"]
|
|
976
1115
|
except Exception as e:
|
|
977
1116
|
logging.exception(f"error sending message to slack\n{e}\nchannel={channel}\n")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/discovery/top_service_resolver.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/persistency/scheduled_jobs_states_dal.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/container_playbook_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/internal/ai_integration.py
RENAMED
|
File without changes
|
|
File without changes
|
{robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/internal/discovery_events.py
RENAMED
|
File without changes
|
|
File without changes
|
{robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/node_enrichment_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/playbooks_event_handler.py
RENAMED
|
File without changes
|
{robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/playbooks_event_handler_impl.py
RENAMED
|
File without changes
|
{robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/pod_utils/crashloop_utils.py
RENAMED
|
File without changes
|
{robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/pod_utils/imagepull_utils.py
RENAMED
|
File without changes
|
{robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/pod_utils/pending_pod_utils.py
RENAMED
|
File without changes
|
{robusta_api-0.46.0 → robusta_api-0.47.0}/src/robusta/core/playbooks/prometheus_enrichment_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|