robusta-api 0.51.0__tar.gz → 0.52.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.
Files changed (316) hide show
  1. {robusta_api-0.51.0 → robusta_api-0.52.0}/PKG-INFO +5 -5
  2. {robusta_api-0.51.0 → robusta_api-0.52.0}/pyproject.toml +7 -7
  3. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/_version.py +1 -1
  4. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/model/runner_config.py +2 -0
  5. robusta_api-0.52.0/src/robusta/core/sinks/common/destination_override.py +67 -0
  6. robusta_api-0.52.0/src/robusta/core/sinks/msteams_graph/__init__.py +5 -0
  7. robusta_api-0.52.0/src/robusta/core/sinks/msteams_graph/msteams_graph_sink.py +64 -0
  8. robusta_api-0.52.0/src/robusta/core/sinks/msteams_graph/msteams_graph_sink_params.py +42 -0
  9. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/sink_factory.py +2 -0
  10. robusta_api-0.52.0/src/robusta/core/sinks/webex/webex_sink.py +38 -0
  11. robusta_api-0.52.0/src/robusta/integrations/msteams/graph/graph_client.py +119 -0
  12. robusta_api-0.52.0/src/robusta/integrations/msteams/graph/graph_msg.py +167 -0
  13. robusta_api-0.52.0/src/robusta/integrations/msteams/graph/sender.py +41 -0
  14. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/msteams/msteams_msg.py +63 -36
  15. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/msteams/sender.py +16 -59
  16. robusta_api-0.52.0/src/robusta/utils/__init__.py +0 -0
  17. robusta_api-0.51.0/src/robusta/core/sinks/webex/webex_sink.py +0 -80
  18. {robusta_api-0.51.0 → robusta_api-0.52.0}/LICENSE +0 -0
  19. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/__init__.py +0 -0
  20. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/api/__init__.py +0 -0
  21. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/clients/robusta_client.py +0 -0
  22. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/__init__.py +0 -0
  23. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/discovery/__init__.py +0 -0
  24. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/discovery/discovery.py +0 -0
  25. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/discovery/resource_names.py +0 -0
  26. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/discovery/top_service_resolver.py +0 -0
  27. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/discovery/utils.py +0 -0
  28. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/exceptions.py +0 -0
  29. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/model/__init__.py +0 -0
  30. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/model/base_params.py +0 -0
  31. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/model/cluster_status.py +0 -0
  32. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/model/env_vars.py +0 -0
  33. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/model/events.py +0 -0
  34. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/model/helm_release.py +0 -0
  35. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/model/jobs.py +0 -0
  36. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/model/k8s_operation_type.py +0 -0
  37. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/model/namespaces.py +0 -0
  38. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/model/nodes.py +0 -0
  39. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/model/openshift_group.py +0 -0
  40. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/model/pods.py +0 -0
  41. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/model/services.py +0 -0
  42. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/persistency/__init__.py +0 -0
  43. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/persistency/in_memory.py +0 -0
  44. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/persistency/scheduled_jobs_states_dal.py +0 -0
  45. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/playbooks/__init__.py +0 -0
  46. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/playbooks/actions_registry.py +0 -0
  47. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/playbooks/base_trigger.py +0 -0
  48. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/playbooks/common.py +0 -0
  49. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/playbooks/container_playbook_utils.py +0 -0
  50. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/playbooks/crash_reporter.py +0 -0
  51. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/playbooks/generation.py +0 -0
  52. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/playbooks/internal/CRD_API_DOCUMENTATION.md +0 -0
  53. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/playbooks/internal/__init__.py +0 -0
  54. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/playbooks/internal/ai_integration.py +0 -0
  55. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/playbooks/internal/crds.py +0 -0
  56. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/playbooks/internal/discovery_events.py +0 -0
  57. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/playbooks/job_utils.py +0 -0
  58. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/playbooks/node_enrichment_utils.py +0 -0
  59. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/playbooks/node_playbook_utils.py +0 -0
  60. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/playbooks/oom_killer_utils.py +0 -0
  61. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/playbooks/playbook_utils.py +0 -0
  62. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/playbooks/playbooks_event_handler.py +0 -0
  63. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/playbooks/playbooks_event_handler_impl.py +0 -0
  64. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/playbooks/pod_utils/crashloop_utils.py +0 -0
  65. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/playbooks/pod_utils/imagepull_utils.py +0 -0
  66. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/playbooks/pod_utils/pending_pod_utils.py +0 -0
  67. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/playbooks/prometheus_enrichment_utils.py +0 -0
  68. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/playbooks/trigger.py +0 -0
  69. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/pubsub/__init__.py +0 -0
  70. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/pubsub/event_emitter.py +0 -0
  71. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/pubsub/event_subscriber.py +0 -0
  72. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/pubsub/events_pubsub.py +0 -0
  73. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/reporting/__init__.py +0 -0
  74. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/reporting/action_requests.py +0 -0
  75. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/reporting/base.py +0 -0
  76. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/reporting/blocks.py +0 -0
  77. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/reporting/callbacks.py +0 -0
  78. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/reporting/charts.py +0 -0
  79. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/reporting/consts.py +0 -0
  80. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/reporting/custom_rendering.py +0 -0
  81. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/reporting/finding_subjects.py +0 -0
  82. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/reporting/holmes.py +0 -0
  83. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/reporting/url_helpers.py +0 -0
  84. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/reporting/utils.py +0 -0
  85. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/schedule/__init__.py +0 -0
  86. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/schedule/model.py +0 -0
  87. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/schedule/scheduler.py +0 -0
  88. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/__init__.py +0 -0
  89. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/common/__init__.py +0 -0
  90. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/common/channel_transformer.py +0 -0
  91. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/common/html_tools.py +0 -0
  92. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/datadog/__init__.py +0 -0
  93. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/datadog/datadog_sink.py +0 -0
  94. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/datadog/datadog_sink_params.py +0 -0
  95. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/discord/__init__.py +0 -0
  96. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/discord/discord_sink.py +0 -0
  97. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/discord/discord_sink_params.py +0 -0
  98. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/file/__init__.py +0 -0
  99. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/file/file_sink.py +0 -0
  100. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/file/file_sink_params.py +0 -0
  101. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/file/object_traverser.py +0 -0
  102. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/google_chat/__init__.py +0 -0
  103. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/google_chat/google_chat.py +0 -0
  104. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/google_chat/google_chat_params.py +0 -0
  105. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/incidentio/__init__.py +0 -0
  106. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/incidentio/incidentio_api.py +0 -0
  107. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/incidentio/incidentio_client.py +0 -0
  108. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/incidentio/incidentio_sink.py +0 -0
  109. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/incidentio/incidentio_sink_params.py +0 -0
  110. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/jira/__init__.py +0 -0
  111. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/jira/jira_sink.py +0 -0
  112. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/jira/jira_sink_params.py +0 -0
  113. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/kafka/__init__.py +0 -0
  114. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/kafka/kafka_sink.py +0 -0
  115. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/kafka/kafka_sink_params.py +0 -0
  116. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/mail/__init__.py +0 -0
  117. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/mail/mail_sink.py +0 -0
  118. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/mail/mail_sink_params.py +0 -0
  119. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/mattermost/__init__.py +0 -0
  120. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/mattermost/mattermost_sink.py +0 -0
  121. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/mattermost/mattermost_sink_params.py +0 -0
  122. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/msteams/__init__.py +0 -0
  123. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/msteams/msteams_sink.py +0 -0
  124. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/msteams/msteams_sink_params.py +0 -0
  125. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/msteams/msteams_webhook_tranformer.py +0 -0
  126. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/opsgenie/__init__.py +0 -0
  127. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/opsgenie/opsgenie_sink.py +0 -0
  128. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/opsgenie/opsgenie_sink_params.py +0 -0
  129. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/pagerduty/__init__.py +0 -0
  130. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/pagerduty/pagerduty_sink.py +0 -0
  131. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/pagerduty/pagerduty_sink_params.py +0 -0
  132. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/pushover/__init__.py +0 -0
  133. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/pushover/pushover_client.py +0 -0
  134. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/pushover/pushover_sink.py +0 -0
  135. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/pushover/pushover_sink_params.py +0 -0
  136. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/robusta/__init__.py +0 -0
  137. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/robusta/dal/__init__.py +0 -0
  138. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/robusta/dal/model_conversion.py +0 -0
  139. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/robusta/dal/supabase_dal.py +0 -0
  140. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/robusta/discovery_metrics.py +0 -0
  141. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/robusta/prometheus_discovery_utils.py +0 -0
  142. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/robusta/robusta_sink.py +0 -0
  143. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/robusta/robusta_sink_params.py +0 -0
  144. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/robusta/rrm/__init__.py +0 -0
  145. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/robusta/rrm/account_resource_fetcher.py +0 -0
  146. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/robusta/rrm/base_resource_manager.py +0 -0
  147. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/robusta/rrm/prometheus_alert_resource_manager.py +0 -0
  148. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/robusta/rrm/rrm.py +0 -0
  149. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/robusta/rrm/types.py +0 -0
  150. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/rocketchat/__init__.py +0 -0
  151. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/rocketchat/rocketchat_sink.py +0 -0
  152. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/rocketchat/rocketchat_sink_params.py +0 -0
  153. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/servicenow/__init__.py +0 -0
  154. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/servicenow/servicenow_sink.py +0 -0
  155. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/servicenow/servicenow_sink_params.py +0 -0
  156. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/sink_base.py +0 -0
  157. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/sink_base_params.py +0 -0
  158. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/sink_config.py +0 -0
  159. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/slack/__init__.py +0 -0
  160. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/slack/preview/slack_sink_preview.py +0 -0
  161. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/slack/preview/slack_sink_preview_params.py +0 -0
  162. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/slack/slack_sink.py +0 -0
  163. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/slack/slack_sink_params.py +0 -0
  164. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/slack/templates/header.j2 +0 -0
  165. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/slack/templates/template_loader.py +0 -0
  166. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/telegram/__init__.py +0 -0
  167. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/telegram/telegram_client.py +0 -0
  168. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/telegram/telegram_sink.py +0 -0
  169. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/telegram/telegram_sink_params.py +0 -0
  170. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/timing.py +0 -0
  171. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/transformer.py +0 -0
  172. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/victorops/__init__.py +0 -0
  173. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/victorops/victorops_sink.py +0 -0
  174. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/victorops/victorops_sink_params.py +0 -0
  175. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/webex/__init__.py +0 -0
  176. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/webex/webex_sink_params.py +0 -0
  177. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/webhook/__init__.py +0 -0
  178. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/webhook/webhook_sink.py +0 -0
  179. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/webhook/webhook_sink_params.py +0 -0
  180. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/yamessenger/__init__.py +0 -0
  181. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/yamessenger/yamessenger_client.py +0 -0
  182. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/yamessenger/yamessenger_sink.py +0 -0
  183. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/yamessenger/yamessenger_sink_params.py +0 -0
  184. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/zulip/__init__.py +0 -0
  185. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/zulip/zulip_sink.py +0 -0
  186. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/sinks/zulip/zulip_sink_params.py +0 -0
  187. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/stream/utils.py +0 -0
  188. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/triggers/container_oom_killed_trigger.py +0 -0
  189. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/triggers/custom_triggers.py +0 -0
  190. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/triggers/error_event_trigger.py +0 -0
  191. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/triggers/helm_releases_triggers.py +0 -0
  192. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/triggers/job_failed_trigger.py +0 -0
  193. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/triggers/multi_resources_trigger.py +0 -0
  194. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/triggers/oom_killed_trigger_base.py +0 -0
  195. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/triggers/pod_crash_loop_trigger.py +0 -0
  196. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/triggers/pod_evicted_trigger.py +0 -0
  197. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/triggers/pod_image_pull_backoff.py +0 -0
  198. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/core/triggers/pod_oom_killed_trigger.py +0 -0
  199. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/__init__.py +0 -0
  200. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/argocd/argocd_client.py +0 -0
  201. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/common/__init__.py +0 -0
  202. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/common/requests.py +0 -0
  203. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/discord/__init__.py +0 -0
  204. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/discord/sender.py +0 -0
  205. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/git/__init__.py +0 -0
  206. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/git/git_repo.py +0 -0
  207. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/git/well_known_hosts.py +0 -0
  208. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/google_chat/__init__.py +0 -0
  209. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/google_chat/sender.py +0 -0
  210. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/grafana.py +0 -0
  211. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/helper.py +0 -0
  212. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/jira/__init__.py +0 -0
  213. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/jira/client.py +0 -0
  214. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/jira/sender.py +0 -0
  215. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/kubernetes/__init__.py +0 -0
  216. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/kubernetes/api_client_utils.py +0 -0
  217. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/kubernetes/autogenerated/__init__.py +0 -0
  218. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/kubernetes/autogenerated/events.py +0 -0
  219. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/kubernetes/autogenerated/models.py +0 -0
  220. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/kubernetes/autogenerated/triggers.py +0 -0
  221. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/kubernetes/autogenerated/v1/__init__.py +0 -0
  222. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/kubernetes/autogenerated/v1/models.py +0 -0
  223. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/kubernetes/base_event.py +0 -0
  224. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/kubernetes/base_triggers.py +0 -0
  225. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/kubernetes/custom_crds.py +0 -0
  226. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/kubernetes/custom_models.py +0 -0
  227. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/kubernetes/model_not_found_exception.py +0 -0
  228. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/kubernetes/process_utils.py +0 -0
  229. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/kubernetes/templates.py +0 -0
  230. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/mail/__init__.py +0 -0
  231. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/mail/sender.py +0 -0
  232. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/mattermost/__init__.py +0 -0
  233. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/mattermost/client.py +0 -0
  234. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/mattermost/sender.py +0 -0
  235. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/msteams/__init__.py +0 -0
  236. {robusta_api-0.51.0/src/robusta/integrations/msteams/msteams_elements → robusta_api-0.52.0/src/robusta/integrations/msteams/graph}/__init__.py +0 -0
  237. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/msteams/msteams_adaptive_card_files.py +0 -0
  238. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/msteams/msteams_adaptive_card_files_image.py +0 -0
  239. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/msteams/msteams_adaptive_card_files_text.py +0 -0
  240. {robusta_api-0.51.0/src/robusta/integrations/prometheus → robusta_api-0.52.0/src/robusta/integrations/msteams/msteams_elements}/__init__.py +0 -0
  241. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/msteams/msteams_elements/msteams_action.py +0 -0
  242. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/msteams/msteams_elements/msteams_base.py +0 -0
  243. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/msteams/msteams_elements/msteams_card.py +0 -0
  244. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/msteams/msteams_elements/msteams_column.py +0 -0
  245. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/msteams/msteams_elements/msteams_container.py +0 -0
  246. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/msteams/msteams_elements/msteams_images.py +0 -0
  247. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/msteams/msteams_elements/msteams_table.py +0 -0
  248. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/msteams/msteams_elements/msteams_text_block.py +0 -0
  249. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/msteams/msteams_mark_down_fix_url.py +0 -0
  250. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/openshift/__init__.py +0 -0
  251. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/openshift/token.py +0 -0
  252. {robusta_api-0.51.0/src/robusta/integrations/resource_analysis → robusta_api-0.52.0/src/robusta/integrations/prometheus}/__init__.py +0 -0
  253. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/prometheus/models.py +0 -0
  254. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/prometheus/trigger.py +0 -0
  255. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/prometheus/utils.py +0 -0
  256. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/receiver.py +0 -0
  257. {robusta_api-0.51.0/src/robusta/integrations/rocketchat → robusta_api-0.52.0/src/robusta/integrations/resource_analysis}/__init__.py +0 -0
  258. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/resource_analysis/cpu_analyzer.py +0 -0
  259. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/resource_analysis/memory_analyzer.py +0 -0
  260. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/resource_analysis/node_cpu_analyzer.py +0 -0
  261. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/resource_analysis/prometheus_analyzer.py +0 -0
  262. {robusta_api-0.51.0/src/robusta/integrations/scheduled → robusta_api-0.52.0/src/robusta/integrations/rocketchat}/__init__.py +0 -0
  263. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/rocketchat/sender.py +0 -0
  264. {robusta_api-0.51.0/src/robusta/integrations/servicenow → robusta_api-0.52.0/src/robusta/integrations/scheduled}/__init__.py +0 -0
  265. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/scheduled/event.py +0 -0
  266. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/scheduled/models.py +0 -0
  267. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/scheduled/playbook_scheduler.py +0 -0
  268. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/scheduled/playbook_scheduler_manager.py +0 -0
  269. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/scheduled/playbook_scheduler_manager_impl.py +0 -0
  270. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/scheduled/trigger.py +0 -0
  271. {robusta_api-0.51.0/src/robusta/integrations/webex → robusta_api-0.52.0/src/robusta/integrations/servicenow}/__init__.py +0 -0
  272. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/servicenow/sender.py +0 -0
  273. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/slack/__init__.py +0 -0
  274. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/slack/sender.py +0 -0
  275. {robusta_api-0.51.0/src/robusta/runner → robusta_api-0.52.0/src/robusta/integrations/webex}/__init__.py +0 -0
  276. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/webex/sender.py +0 -0
  277. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/zulip/__init__.py +0 -0
  278. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/integrations/zulip/sender.py +0 -0
  279. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/model/alert_relabel_config.py +0 -0
  280. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/model/config.py +0 -0
  281. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/model/playbook_action.py +0 -0
  282. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/model/playbook_definition.py +0 -0
  283. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/patch/patch.py +0 -0
  284. {robusta_api-0.51.0/src/robusta/utils → robusta_api-0.52.0/src/robusta/runner}/__init__.py +0 -0
  285. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/runner/config_loader.py +0 -0
  286. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/runner/log_init.py +0 -0
  287. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/runner/main.py +0 -0
  288. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/runner/not_found_exception.py +0 -0
  289. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/runner/object_updater.py +0 -0
  290. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/runner/process_setup.py +0 -0
  291. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/runner/ssl_utils.py +0 -0
  292. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/runner/telemetry.py +0 -0
  293. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/runner/telemetry_service.py +0 -0
  294. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/runner/web.py +0 -0
  295. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/runner/web_api.py +0 -0
  296. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/utils/archive.py +0 -0
  297. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/utils/auth_provider.py +0 -0
  298. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/utils/base64_utils.py +0 -0
  299. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/utils/cluster_provider_discovery.py +0 -0
  300. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/utils/common.py +0 -0
  301. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/utils/decorators.py +0 -0
  302. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/utils/docs.py +0 -0
  303. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/utils/documented_pydantic.py +0 -0
  304. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/utils/error_codes.py +0 -0
  305. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/utils/file_system_watcher.py +0 -0
  306. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/utils/function_hashes.py +0 -0
  307. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/utils/json_schema.py +0 -0
  308. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/utils/parsing.py +0 -0
  309. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/utils/rate_limiter.py +0 -0
  310. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/utils/scope.py +0 -0
  311. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/utils/server_start.py +0 -0
  312. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/utils/service_discovery.py +0 -0
  313. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/utils/silence_utils.py +0 -0
  314. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/utils/stack_tracer.py +0 -0
  315. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/utils/task_queue.py +0 -0
  316. {robusta_api-0.51.0 → robusta_api-0.52.0}/src/robusta/utils/time_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: robusta-api
3
- Version: 0.51.0
3
+ Version: 0.52.0
4
4
  Summary:
5
5
  Author: Arik Alon
6
6
  Author-email: arikalon1@users.noreply.github.com
@@ -45,7 +45,7 @@ Requires-Dist: prometrix (==0.2.11)
45
45
  Requires-Dist: pyasn1 (>=0.6.4)
46
46
  Requires-Dist: pydantic (>=2.11.7,<3.0.0)
47
47
  Requires-Dist: pydash (==8.0.0)
48
- Requires-Dist: pyjwt (>=2.13.0,<3.0.0)
48
+ Requires-Dist: pyjwt (>=2.14.0,<3.0.0)
49
49
  Requires-Dist: pymsteams (>=0.1.16,<0.2.0)
50
50
  Requires-Dist: python-json-logger (>=3.0.0,<4.0.0)
51
51
  Requires-Dist: pytz (>=2021.3,<2022.0)
@@ -56,14 +56,14 @@ Requires-Dist: requests (>=2.33.0,<3.0.0)
56
56
  Requires-Dist: resvg-py (==0.3.4)
57
57
  Requires-Dist: rocketchat-api (>=1.30.0,<2.0.0)
58
58
  Requires-Dist: sentry-sdk[flask] (>=2.13.0,<3.0.0) ; extra == "all"
59
- Requires-Dist: setuptools (>=80.9.0,<81.0.0)
59
+ Requires-Dist: setuptools (>=83.0.0,<84.0.0)
60
60
  Requires-Dist: slack-sdk (>=3.23.0,<4.0.0)
61
61
  Requires-Dist: supabase (==2.28.1) ; extra == "all"
62
62
  Requires-Dist: tabulate (>=0.8.10,<0.9.0) ; extra == "all"
63
63
  Requires-Dist: tenacity (>=9.1.2,<10.0.0)
64
64
  Requires-Dist: toml (>=0.10.2,<0.11.0)
65
- Requires-Dist: urllib3 (>=2.7.0,<3.0.0)
66
- Requires-Dist: virtualenv (>=20.36.1,<21.0.0)
65
+ Requires-Dist: urllib3 (>=2.8.0,<3.0.0)
66
+ Requires-Dist: virtualenv (>=21.7.13,<22.0.0)
67
67
  Requires-Dist: watchdog (>=2.1.0,<3.0.0) ; extra == "all"
68
68
  Requires-Dist: watchgod (>=0.7,<0.8)
69
69
  Requires-Dist: webexteamssdk (>=1.6.1,<2.0.0)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "robusta-api"
3
- version = "0.51.0"
3
+ version = "0.52.0"
4
4
  description = ""
5
5
  authors = ["Arik Alon <arikalon1@users.noreply.github.com>"]
6
6
  packages = [
@@ -18,7 +18,7 @@ include_trailing_comma = true
18
18
 
19
19
  [tool.poetry.dependencies]
20
20
  python = ">=3.10, <3.12"
21
- setuptools = "^80.9.0"
21
+ setuptools = "^83.0.0"
22
22
  colorlog = "^5.0.1"
23
23
  python-json-logger = "^3.0.0"
24
24
  pydantic = "^2.11.7"
@@ -83,9 +83,9 @@ requests = "^2.33.0"
83
83
  # Bump to fix certifi CVE flagged by customer scan; date-versioned, so use >= rather than caret
84
84
  certifi = ">=2026.4.22"
85
85
  regex = "2024.5.15"
86
- # Pin to fix CVE-2026-48526 (High) + CVE-2026-48522/48523/48525 (Medium): PyJWT
87
- pyjwt = "^2.13.0"
88
- urllib3 = "^2.7.0"
86
+ # Pin to fix CVE-2026-102268 (Critical) + CVE-2026-102266/102267/102271-102273 (High): PyJWT
87
+ pyjwt = "^2.14.0"
88
+ urllib3 = "^2.8.0"
89
89
  httpx = "0.27.2"
90
90
  # realtime (supabase dependency) requires websockets.asyncio, added in websockets 13
91
91
  websockets = ">=13"
@@ -98,8 +98,8 @@ pyasn1 = ">=0.6.4"
98
98
  anyio = "^4.14.2"
99
99
  # Pin to fix CVE-2026-22701 (transitive via virtualenv/pre-commit)
100
100
  filelock = ">=3.20.3"
101
- # Pin to fix CVE-2026-22702 (transitive via pre-commit)
102
- virtualenv = "^20.36.1"
101
+ # Pin to fix CVE-2026-102925/102930/102937 (transitive via pre-commit)
102
+ virtualenv = "^21.7.13"
103
103
  # Pillow is used directly (MS Teams PNG->JPEG image conversion) and by reportlab.
104
104
  # Pin to fix CVE-2026-54058,
105
105
  # CVE-2026-54059, CVE-2026-54060, CVE-2026-55379, CVE-2026-55380, CVE-2026-59197,
@@ -1,2 +1,2 @@
1
1
  # this is updated by .github/workflows/release.yaml
2
- __version__ = "0.51.0"
2
+ __version__ = "0.52.0"
@@ -12,6 +12,7 @@ from robusta.core.sinks.jira.jira_sink_params import JiraSinkConfigWrapper
12
12
  from robusta.core.sinks.kafka.kafka_sink_params import KafkaSinkConfigWrapper
13
13
  from robusta.core.sinks.mattermost.mattermost_sink_params import MattermostSinkConfigWrapper
14
14
  from robusta.core.sinks.msteams.msteams_sink_params import MsTeamsSinkConfigWrapper
15
+ from robusta.core.sinks.msteams_graph.msteams_graph_sink_params import MsTeamsGraphSinkConfigWrapper
15
16
  from robusta.core.sinks.opsgenie.opsgenie_sink_params import OpsGenieSinkConfigWrapper
16
17
  from robusta.core.sinks.pagerduty.pagerduty_sink_params import PagerdutyConfigWrapper
17
18
  from robusta.core.sinks.robusta.robusta_sink_params import RobustaSinkConfigWrapper
@@ -60,6 +61,7 @@ class RunnerConfig(BaseModel):
60
61
  DataDogSinkConfigWrapper,
61
62
  KafkaSinkConfigWrapper,
62
63
  MsTeamsSinkConfigWrapper,
64
+ MsTeamsGraphSinkConfigWrapper,
63
65
  RocketchatSinkConfigWrapper,
64
66
  OpsGenieSinkConfigWrapper,
65
67
  TelegramSinkConfigWrapper,
@@ -0,0 +1,67 @@
1
+ import logging
2
+ from typing import Dict, Optional, Tuple
3
+
4
+ from robusta.core.reporting.base import Finding
5
+ from robusta.core.sinks.common.channel_transformer import ChannelTransformer
6
+ from robusta.integrations.kubernetes.api_client_utils import get_namespace_annotations, get_namespace_labels
7
+
8
+ # Sentinel passed as default_channel to ChannelTransformer.template() so we can detect
9
+ # the "any token missing" case from the outside. ChannelTransformer returns the default
10
+ # we pass in only when the override is empty (we filter that case ourselves) or when a
11
+ # referenced label/annotation key is missing — both of which we treat as unresolved here.
12
+ _UNRESOLVED = "__robusta_destination_unresolved__"
13
+
14
+
15
+ def resolve_destination_override(
16
+ finding: Finding,
17
+ cluster_name: str,
18
+ default_destination: str,
19
+ destination_override: Optional[str],
20
+ namespace_destination_override: Optional[str],
21
+ send_to_default_if_missing: bool,
22
+ ) -> Optional[str]:
23
+ """Pick the destination (room, channel, ...) for a finding.
24
+
25
+ Evaluated in order:
26
+ 1. namespace_destination_override, resolved against the finding namespace's labels/annotations
27
+ 2. destination_override, resolved against the finding subject's labels/annotations
28
+ 3. default_destination, or None (drop) when an override is configured, didn't resolve,
29
+ and send_to_default_if_missing is False
30
+ """
31
+ if namespace_destination_override and finding.subject.namespace:
32
+ ns_labels, ns_annotations = _get_namespace_metadata(finding.subject.namespace)
33
+ resolved = ChannelTransformer.template(
34
+ namespace_destination_override,
35
+ _UNRESOLVED,
36
+ cluster_name,
37
+ ns_labels,
38
+ ns_annotations,
39
+ )
40
+ if resolved != _UNRESOLVED:
41
+ return resolved
42
+
43
+ if destination_override:
44
+ resolved = ChannelTransformer.template(
45
+ destination_override,
46
+ _UNRESOLVED,
47
+ cluster_name,
48
+ finding.subject.labels or {},
49
+ finding.subject.annotations or {},
50
+ )
51
+ if resolved != _UNRESOLVED:
52
+ return resolved
53
+
54
+ if not destination_override and not namespace_destination_override:
55
+ return default_destination
56
+
57
+ return default_destination if send_to_default_if_missing else None
58
+
59
+
60
+ def _get_namespace_metadata(namespace: str) -> Tuple[Dict[str, str], Dict[str, str]]:
61
+ try:
62
+ labels = get_namespace_labels(namespace) or {}
63
+ annotations = get_namespace_annotations(namespace) or {}
64
+ except KeyError:
65
+ logging.debug("namespace %s not found in cache", namespace)
66
+ return {}, {}
67
+ return labels, annotations
@@ -0,0 +1,5 @@
1
+ from robusta.core.sinks.msteams_graph.msteams_graph_sink import MsTeamsGraphSink
2
+ from robusta.core.sinks.msteams_graph.msteams_graph_sink_params import (
3
+ MsTeamsGraphSinkConfigWrapper,
4
+ MsTeamsGraphSinkParams,
5
+ )
@@ -0,0 +1,64 @@
1
+ import logging
2
+ from typing import Optional, Tuple
3
+
4
+ from robusta.core.reporting import Finding
5
+ from robusta.core.sinks.common.destination_override import resolve_destination_override
6
+ from robusta.core.sinks.msteams_graph.msteams_graph_sink_params import MsTeamsGraphSinkConfigWrapper
7
+ from robusta.core.sinks.sink_base import SinkBase
8
+ from robusta.integrations.msteams.graph.graph_client import MsTeamsGraphClient
9
+ from robusta.integrations.msteams.graph.sender import MsTeamsGraphSender
10
+
11
+
12
+ class MsTeamsGraphSink(SinkBase):
13
+ def __init__(self, sink_config: MsTeamsGraphSinkConfigWrapper, registry):
14
+ super().__init__(sink_config.ms_teams_graph_sink, registry)
15
+ params = sink_config.ms_teams_graph_sink
16
+
17
+ client = MsTeamsGraphClient(
18
+ tenant_id=params.tenant_id,
19
+ client_id=params.client_id,
20
+ username=params.username,
21
+ password=params.password.get_secret_value(),
22
+ client_secret=params.client_secret.get_secret_value() if params.client_secret else None,
23
+ scope=params.oauth_scope,
24
+ authority_host=params.authority_host,
25
+ graph_endpoint=params.graph_endpoint,
26
+ )
27
+ self.sender = MsTeamsGraphSender(
28
+ client=client,
29
+ account_id=self.account_id,
30
+ cluster_name=self.cluster_name,
31
+ prefer_redirect_to_platform=params.prefer_redirect_to_platform,
32
+ send_files=params.send_files,
33
+ )
34
+
35
+ def write_finding(self, finding: Finding, platform_enabled: bool):
36
+ destination = self._resolve_destination(finding)
37
+ if destination is None:
38
+ logging.debug(f"no Teams channel resolved for {finding.title}, dropping it")
39
+ return
40
+ team_id, channel_id = destination
41
+ if self.params.disable_platform_links:
42
+ platform_enabled = False
43
+ self.sender.send_finding(finding, platform_enabled, team_id=team_id, channel_id=channel_id)
44
+
45
+ def _resolve_destination(self, finding: Finding) -> Optional[Tuple[str, str]]:
46
+ channel = resolve_destination_override(
47
+ finding,
48
+ self.cluster_name,
49
+ self.params.channel_id,
50
+ self.params.channel_override,
51
+ self.params.namespace_channel_override,
52
+ self.params.send_to_default_if_missing,
53
+ )
54
+ if channel is None:
55
+ return None
56
+ return self.split_team_and_channel(channel.strip(), self.params.team_id)
57
+
58
+ @staticmethod
59
+ def split_team_and_channel(destination: str, default_team_id: str) -> Tuple[str, str]:
60
+ # team ids are GUIDs and channel ids (19:...@thread.tacv2) never contain "/"
61
+ if "/" in destination:
62
+ team_id, channel_id = destination.split("/", 1)
63
+ return team_id.strip(), channel_id.strip()
64
+ return default_team_id, destination
@@ -0,0 +1,42 @@
1
+ from typing import Optional
2
+
3
+ from pydantic.v1 import SecretStr, validator
4
+
5
+ from robusta.core.sinks.common.channel_transformer import ChannelTransformer
6
+ from robusta.core.sinks.sink_base_params import SinkBaseParams
7
+ from robusta.core.sinks.sink_config import SinkConfigBase
8
+ from robusta.integrations.msteams.graph.graph_client import DEFAULT_AUTHORITY_HOST, DEFAULT_GRAPH_ENDPOINT
9
+
10
+
11
+ class MsTeamsGraphSinkParams(SinkBaseParams):
12
+ tenant_id: str
13
+ client_id: str
14
+ client_secret: Optional[SecretStr] = None
15
+ username: str
16
+ password: SecretStr
17
+ # Default destination. Overrides may resolve to "<channel_id>" (in team_id) or "<team_id>/<channel_id>"
18
+ team_id: str
19
+ channel_id: str
20
+ channel_override: Optional[str] = None
21
+ namespace_channel_override: Optional[str] = None
22
+ send_to_default_if_missing: bool = True
23
+ disable_platform_links: bool = False
24
+ send_files: bool = True
25
+ oauth_scope: Optional[str] = None # defaults to <graph_endpoint>/.default
26
+ authority_host: str = DEFAULT_AUTHORITY_HOST
27
+ graph_endpoint: str = DEFAULT_GRAPH_ENDPOINT
28
+
29
+ @classmethod
30
+ def _get_sink_type(cls):
31
+ return "msteams_graph"
32
+
33
+ @validator("channel_override", "namespace_channel_override")
34
+ def validate_overrides(cls, v):
35
+ return ChannelTransformer.validate_channel_override(v)
36
+
37
+
38
+ class MsTeamsGraphSinkConfigWrapper(SinkConfigBase):
39
+ ms_teams_graph_sink: MsTeamsGraphSinkParams
40
+
41
+ def get_params(self) -> SinkBaseParams:
42
+ return self.ms_teams_graph_sink
@@ -12,6 +12,7 @@ from robusta.core.sinks.mail.mail_sink import MailSink
12
12
  from robusta.core.sinks.mail.mail_sink_params import MailSinkConfigWrapper
13
13
  from robusta.core.sinks.mattermost import MattermostSink, MattermostSinkConfigWrapper
14
14
  from robusta.core.sinks.msteams import MsTeamsSink, MsTeamsSinkConfigWrapper
15
+ from robusta.core.sinks.msteams_graph import MsTeamsGraphSink, MsTeamsGraphSinkConfigWrapper
15
16
  from robusta.core.sinks.opsgenie import OpsGenieSink, OpsGenieSinkConfigWrapper
16
17
  from robusta.core.sinks.pagerduty import PagerdutyConfigWrapper, PagerdutySink
17
18
  from robusta.core.sinks.robusta import RobustaSink, RobustaSinkConfigWrapper
@@ -41,6 +42,7 @@ class SinkFactory:
41
42
  RocketchatSinkConfigWrapper: RocketchatSink,
42
43
  RobustaSinkConfigWrapper: RobustaSink,
43
44
  MsTeamsSinkConfigWrapper: MsTeamsSink,
45
+ MsTeamsGraphSinkConfigWrapper: MsTeamsGraphSink,
44
46
  KafkaSinkConfigWrapper: KafkaSink,
45
47
  DataDogSinkConfigWrapper: DataDogSink,
46
48
  DiscordSinkConfigWrapper: DiscordSink,
@@ -0,0 +1,38 @@
1
+ from typing import Optional
2
+
3
+ from robusta.core.reporting.base import Finding
4
+ from robusta.core.sinks.common.destination_override import resolve_destination_override
5
+ from robusta.core.sinks.sink_base import SinkBase
6
+ from robusta.core.sinks.webex.webex_sink_params import WebexSinkConfigWrapper
7
+ from robusta.integrations.webex.sender import WebexSender
8
+
9
+
10
+ class WebexSink(SinkBase):
11
+ def __init__(self, sink_config: WebexSinkConfigWrapper, registry):
12
+ super().__init__(sink_config.webex_sink, registry)
13
+
14
+ self.sender = WebexSender(
15
+ bot_access_token=sink_config.webex_sink.bot_access_token,
16
+ room_id=sink_config.webex_sink.room_id,
17
+ account_id=self.account_id,
18
+ cluster_name=self.cluster_name,
19
+ webex_params=self.params,
20
+ )
21
+
22
+ def write_finding(self, finding: Finding, platform_enabled: bool):
23
+ room_id = self._resolve_room_id(finding)
24
+ if room_id is None:
25
+ return
26
+ if self.params.disable_platform_links:
27
+ platform_enabled = False
28
+ self.sender.send_finding_to_webex(finding, platform_enabled, room_id=room_id)
29
+
30
+ def _resolve_room_id(self, finding: Finding) -> Optional[str]:
31
+ return resolve_destination_override(
32
+ finding,
33
+ self.cluster_name,
34
+ self.params.room_id,
35
+ self.params.room_id_override,
36
+ self.params.namespace_room_id_override,
37
+ self.params.send_to_default_if_missing,
38
+ )
@@ -0,0 +1,119 @@
1
+ import logging
2
+ import threading
3
+ import time
4
+ from typing import Optional
5
+ from urllib.parse import quote, unquote
6
+
7
+ import requests
8
+
9
+ DEFAULT_AUTHORITY_HOST = "https://login.microsoftonline.com"
10
+ DEFAULT_GRAPH_ENDPOINT = "https://graph.microsoft.com"
11
+ REQUEST_TIMEOUT_SEC = 30
12
+ # refresh the access token a bit before it expires, so a message is never sent with an expiring token
13
+ TOKEN_EXPIRY_MARGIN_SEC = 300
14
+ # after a failed sign-in, don't retry on every alert: repeated bad passwords trigger Entra ID smart lockout
15
+ SIGN_IN_FAILURE_BACKOFF_SEC = 300
16
+
17
+
18
+ class MsTeamsGraphError(Exception):
19
+ pass
20
+
21
+
22
+ class MsTeamsGraphClient:
23
+ """Posts Teams channel messages via the Microsoft Graph API.
24
+
25
+ Graph only supports posting channel messages with delegated permissions (application permissions are
26
+ limited to message migration), so the token is acquired on behalf of a service user with the
27
+ Resource Owner Password Credentials (ROPC) flow. The user must be a member of every target team,
28
+ and must not require interactive MFA.
29
+ """
30
+
31
+ def __init__(
32
+ self,
33
+ tenant_id: str,
34
+ client_id: str,
35
+ username: str,
36
+ password: str,
37
+ client_secret: Optional[str] = None,
38
+ scope: Optional[str] = None,
39
+ authority_host: str = DEFAULT_AUTHORITY_HOST,
40
+ graph_endpoint: str = DEFAULT_GRAPH_ENDPOINT,
41
+ ):
42
+ self.tenant_id = tenant_id
43
+ self.client_id = client_id
44
+ self.username = username
45
+ self.password = password
46
+ self.client_secret = client_secret
47
+ self.authority_host = authority_host.rstrip("/")
48
+ self.graph_endpoint = graph_endpoint.rstrip("/")
49
+ self.scope = scope or f"{self.graph_endpoint}/.default"
50
+
51
+ self._lock = threading.Lock()
52
+ self._access_token: Optional[str] = None
53
+ self._expires_at: float = 0
54
+ self._next_sign_in_attempt: float = 0
55
+
56
+ def _get_access_token(self, force_refresh: bool = False) -> str:
57
+ with self._lock:
58
+ if not force_refresh and self._access_token and time.time() < self._expires_at - TOKEN_EXPIRY_MARGIN_SEC:
59
+ return self._access_token
60
+ if time.time() < self._next_sign_in_attempt:
61
+ raise MsTeamsGraphError(
62
+ f"Skipping Graph sign-in for {self.username}, it failed recently. "
63
+ f"Retrying in {int(self._next_sign_in_attempt - time.time())}s"
64
+ )
65
+
66
+ data = {
67
+ "grant_type": "password",
68
+ "client_id": self.client_id,
69
+ "username": self.username,
70
+ "password": self.password,
71
+ "scope": self.scope,
72
+ }
73
+ if self.client_secret:
74
+ data["client_secret"] = self.client_secret
75
+
76
+ response = requests.post(
77
+ f"{self.authority_host}/{self.tenant_id}/oauth2/v2.0/token", data=data, timeout=REQUEST_TIMEOUT_SEC
78
+ )
79
+ if not response.ok:
80
+ self._next_sign_in_attempt = time.time() + SIGN_IN_FAILURE_BACKOFF_SEC
81
+ raise MsTeamsGraphError(
82
+ f"Failed to get a Graph access token for {self.username}: "
83
+ f"{response.status_code} {self.error_message(response)}"
84
+ )
85
+
86
+ body = response.json()
87
+ self._access_token = body["access_token"]
88
+ self._expires_at = time.time() + int(body.get("expires_in", 3600))
89
+ return self._access_token
90
+
91
+ def post_channel_message(self, team_id: str, channel_id: str, message: dict) -> requests.Response:
92
+ # channel ids look like 19:abc@thread.tacv2, and are often copied URL-encoded from the Teams channel link
93
+ url = (
94
+ f"{self.graph_endpoint}/v1.0/teams/{quote(unquote(team_id), safe='')}"
95
+ f"/channels/{quote(unquote(channel_id), safe='')}/messages"
96
+ )
97
+ response = self._post(url, message, force_refresh=False)
98
+ if response.status_code == 401:
99
+ response = self._post(url, message, force_refresh=True)
100
+ return response
101
+
102
+ def _post(self, url: str, message: dict, force_refresh: bool) -> requests.Response:
103
+ headers = {"Authorization": f"Bearer {self._get_access_token(force_refresh)}"}
104
+ return requests.post(url, json=message, headers=headers, timeout=REQUEST_TIMEOUT_SEC)
105
+
106
+ @staticmethod
107
+ def error_message(response: requests.Response) -> str:
108
+ try:
109
+ body = response.json()
110
+ except ValueError:
111
+ return response.text
112
+ error = body.get("error")
113
+ if isinstance(error, dict): # Graph errors
114
+ return error.get("message", response.text)
115
+ if body.get("error_description"): # Entra ID token errors
116
+ return f"{error}: {body['error_description']}"
117
+ logging.debug("unexpected Graph error body %s", body)
118
+ return response.text
119
+
@@ -0,0 +1,167 @@
1
+ import base64
2
+ import json
3
+ import logging
4
+ import uuid
5
+ from typing import List, Optional
6
+
7
+ from robusta.core.reporting import FileBlock, Finding, FindingSeverity
8
+ from robusta.core.reporting.base import Emojis, FindingStatus, LinkType
9
+ from robusta.core.reporting.consts import FindingSource
10
+ from robusta.core.reporting.url_helpers import convert_prom_graph_url_to_robusta_metrics_explorer
11
+ from robusta.core.reporting.utils import JPG_SUFFIX, PNG_SUFFIX, convert_svg_to_png, file_suffix_match, is_image
12
+ from robusta.integrations.msteams.msteams_elements.msteams_base import MsTeamsBase
13
+ from robusta.integrations.msteams.msteams_elements.msteams_images import MsTeamsImages
14
+ from robusta.integrations.msteams.msteams_elements.msteams_text_block import MsTeamsTextBlock
15
+ from robusta.integrations.msteams.msteams_msg import MsTeamsMsg
16
+
17
+ ADAPTIVE_CARD_CONTENT_TYPE = "application/vnd.microsoft.card.adaptive"
18
+ # Teams only shows a handful of card buttons, keep the first ones (Investigate, Silence, the finding links)
19
+ MAX_CARD_ACTIONS = 6
20
+
21
+
22
+ class MsTeamsGraphMsg(MsTeamsMsg):
23
+ """Builds a Teams channel message (chatMessage) for the Graph API.
24
+
25
+ Reuses the adaptive card rendering of the webhook sink for the enrichment blocks, with a Slack like header:
26
+ a status line, a colored title container, the source cluster, the description, and link buttons.
27
+ Images are sent as message hostedContents rather than inline base64, which keeps them out of the
28
+ ~28KB message size limit.
29
+ """
30
+
31
+ def __init__(self, prefer_redirect_to_platform: bool):
32
+ super().__init__(prefer_redirect_to_platform)
33
+ self.hosted_contents: List[dict] = []
34
+
35
+ def write_title_and_desc(self, platform_enabled: bool, finding: Finding, cluster_name: str, account_id: str):
36
+ status = FindingStatus.RESOLVED if finding.title.startswith("[RESOLVED]") else FindingStatus.FIRING
37
+ title = finding.title.removeprefix("[RESOLVED] ")
38
+ severity = finding.severity
39
+
40
+ header_items = [
41
+ MsTeamsTextBlock(
42
+ f"{self.__status_name(finding.source, status)} "
43
+ f"{severity.to_emoji()} **{severity.name.capitalize()}**"
44
+ ).get_map_value(),
45
+ MsTeamsTextBlock(title, weight="bolder", font_size="large").get_map_value(),
46
+ ]
47
+ self._write_to_entire_msg(
48
+ [
49
+ MsTeamsBase(
50
+ {
51
+ "type": "Container",
52
+ "style": self.__card_style(status, severity),
53
+ "bleed": True,
54
+ "items": header_items,
55
+ }
56
+ )
57
+ ]
58
+ )
59
+ # link buttons right under the title, like Slack
60
+ actions = self.__actions(platform_enabled, finding, cluster_name, account_id)
61
+ if actions:
62
+ self._write_to_entire_msg([MsTeamsBase({"type": "ActionSet", "actions": actions})])
63
+ self._write_to_entire_msg([MsTeamsTextBlock(f"**Source:** {cluster_name}")])
64
+
65
+ if finding.description:
66
+ if finding.source == FindingSource.PROMETHEUS:
67
+ description = f"{Emojis.Alert.value} **Alert:** {finding.description}"
68
+ elif finding.source == FindingSource.KUBERNETES_API_SERVER:
69
+ description = f"{Emojis.K8Notification.value} **K8s event detected:** {finding.description}"
70
+ else:
71
+ description = f"{Emojis.K8Notification.value} **Notification:** {finding.description}"
72
+ self._write_to_entire_msg([MsTeamsTextBlock(description)])
73
+
74
+ def __actions(self, platform_enabled: bool, finding: Finding, cluster_name: str, account_id: str) -> List[dict]:
75
+ links = []
76
+ if platform_enabled:
77
+ links.append(("Investigate 🔎", finding.get_investigate_uri(account_id, cluster_name)))
78
+ if finding.add_silence_url:
79
+ links.append(("Configure Silences 🔕", finding.get_prometheus_silence_url(account_id, cluster_name)))
80
+
81
+ for link in finding.links:
82
+ link_url = link.url
83
+ if link.type == LinkType.PROMETHEUS_GENERATOR_URL and self.prefer_redirect_to_platform and platform_enabled:
84
+ link_url = convert_prom_graph_url_to_robusta_metrics_explorer(link.url, cluster_name, account_id)
85
+ links.append((link.link_text, link_url))
86
+
87
+ if len(links) > MAX_CARD_ACTIONS:
88
+ logging.debug(f"Teams card for {finding.title} has {len(links)} links, keeping the first {MAX_CARD_ACTIONS}")
89
+ return [{"type": "Action.OpenUrl", "title": title, "url": url} for title, url in links[:MAX_CARD_ACTIONS]]
90
+
91
+ def upload_files(self, file_blocks: List[FileBlock]):
92
+ # text files are rendered in the card, the same as the webhook sink
93
+ super().upload_files([block for block in file_blocks if not is_image(block.filename)])
94
+
95
+ image_urls = [self.__add_hosted_image(block) for block in file_blocks if is_image(block.filename)]
96
+ image_urls = [url for url in image_urls if url]
97
+ if image_urls:
98
+ self._sub_section_separator()
99
+ self._write_to_current_section([MsTeamsImages(image_urls)])
100
+
101
+ def __add_hosted_image(self, block: FileBlock) -> Optional[str]:
102
+ if file_suffix_match(block.filename, PNG_SUFFIX):
103
+ content_type, contents = "image/png", block.contents
104
+ elif file_suffix_match(block.filename, JPG_SUFFIX):
105
+ content_type, contents = "image/jpeg", block.contents
106
+ else:
107
+ content_type, contents = "image/png", convert_svg_to_png(block.contents)
108
+ if contents is None:
109
+ logging.warning(f"failed to convert {block.filename} to png, not sending it to Teams")
110
+ return None
111
+
112
+ temporary_id = str(len(self.hosted_contents) + 1)
113
+ self.hosted_contents.append(
114
+ {
115
+ "@microsoft.graph.temporaryId": temporary_id,
116
+ "contentBytes": base64.b64encode(contents).decode("utf-8"),
117
+ "contentType": content_type,
118
+ }
119
+ )
120
+ return f"../hostedContents/{temporary_id}/$value"
121
+
122
+ def build_message(self) -> dict:
123
+ card = self.build_card()["attachments"][0]["content"]
124
+ attachment_id = uuid.uuid4().hex
125
+ message = {
126
+ "body": {"contentType": "html", "content": f'<attachment id="{attachment_id}"></attachment>'},
127
+ "attachments": [
128
+ {
129
+ "id": attachment_id,
130
+ "contentType": ADAPTIVE_CARD_CONTENT_TYPE,
131
+ "contentUrl": None,
132
+ "content": json.dumps(card),
133
+ }
134
+ ],
135
+ }
136
+ if self.hosted_contents:
137
+ message["hostedContents"] = self.hosted_contents
138
+ return message
139
+
140
+ @staticmethod
141
+ def without_images(message: dict) -> dict:
142
+ """A copy of the message without its hosted images, used when Teams rejects them."""
143
+ card = json.loads(message["attachments"][0]["content"])
144
+ card["body"] = [element for element in card["body"] if element.get("type") != "ImageSet"]
145
+ message = {k: v for k, v in message.items() if k != "hostedContents"}
146
+ message["attachments"] = [{**message["attachments"][0], "content": json.dumps(card)}]
147
+ return message
148
+
149
+ @staticmethod
150
+ def __status_name(source: FindingSource, status: FindingStatus) -> str:
151
+ if source == FindingSource.PROMETHEUS:
152
+ if status == FindingStatus.FIRING:
153
+ return f"{status.to_emoji()} **Prometheus Alert Firing** {status.to_emoji()}"
154
+ return f"{status.to_emoji()} **Prometheus resolved**"
155
+ if source == FindingSource.KUBERNETES_API_SERVER:
156
+ return "👀 **K8s event detected**"
157
+ return "👀 **Notification**"
158
+
159
+ @staticmethod
160
+ def __card_style(status: FindingStatus, severity: FindingSeverity) -> str:
161
+ if status == FindingStatus.RESOLVED:
162
+ return "good"
163
+ if severity == FindingSeverity.HIGH:
164
+ return "attention"
165
+ if severity == FindingSeverity.LOW:
166
+ return "warning"
167
+ return "accent"
@@ -0,0 +1,41 @@
1
+ import logging
2
+
3
+ from robusta.core.reporting import Finding
4
+ from robusta.integrations.msteams.graph.graph_client import MsTeamsGraphClient
5
+ from robusta.integrations.msteams.graph.graph_msg import MsTeamsGraphMsg
6
+
7
+
8
+ class MsTeamsGraphSender:
9
+ def __init__(
10
+ self,
11
+ client: MsTeamsGraphClient,
12
+ account_id: str,
13
+ cluster_name: str,
14
+ prefer_redirect_to_platform: bool,
15
+ send_files: bool,
16
+ ):
17
+ self.client = client
18
+ self.account_id = account_id
19
+ self.cluster_name = cluster_name
20
+ self.prefer_redirect_to_platform = prefer_redirect_to_platform
21
+ self.send_files = send_files
22
+
23
+ def send_finding(self, finding: Finding, platform_enabled: bool, team_id: str, channel_id: str):
24
+ msg = MsTeamsGraphMsg(self.prefer_redirect_to_platform)
25
+ msg.write_title_and_desc(platform_enabled, finding, self.cluster_name, self.account_id)
26
+ msg.write_enrichments(finding.enrichments, self.send_files)
27
+ message = msg.build_message()
28
+
29
+ response = self.client.post_channel_message(team_id, channel_id, message)
30
+ if response.status_code in (400, 413) and "hostedContents" in message:
31
+ logging.warning(
32
+ f"Teams rejected finding {finding.title} with images ({response.status_code} "
33
+ f"{self.client.error_message(response)}), sending it without them"
34
+ )
35
+ response = self.client.post_channel_message(team_id, channel_id, MsTeamsGraphMsg.without_images(message))
36
+
37
+ if not response.ok:
38
+ logging.error(
39
+ f"Failed to send finding {finding.title} to Teams channel {channel_id} in team {team_id}: "
40
+ f"{response.status_code} {self.client.error_message(response)}"
41
+ )