From 46c8635046f4fafd4bf726a6f5ec9c965e105335 Mon Sep 17 00:00:00 2001 From: Matvey Kukuy Date: Wed, 30 Oct 2024 10:48:34 +0400 Subject: [PATCH 1/2] fix: migration messing link between alerts and incidents (#2338) --- .../versions/2024-10-29-18-37_991b30bcf0b9.py | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 keep/api/models/db/migrations/versions/2024-10-29-18-37_991b30bcf0b9.py diff --git a/keep/api/models/db/migrations/versions/2024-10-29-18-37_991b30bcf0b9.py b/keep/api/models/db/migrations/versions/2024-10-29-18-37_991b30bcf0b9.py new file mode 100644 index 000000000..b93b5f7fd --- /dev/null +++ b/keep/api/models/db/migrations/versions/2024-10-29-18-37_991b30bcf0b9.py @@ -0,0 +1,61 @@ +"""Fix broken links between alerts and incidents + +Revision ID: 991b30bcf0b9 +Revises: 89b4d3905d26 +Create Date: 2024-10-29 18:37:28.668473 + +""" + +import sqlalchemy as sa +from alembic import op +import logging + +# revision identifiers, used by Alembic. +revision = "991b30bcf0b9" +down_revision = "89b4d3905d26" +branch_labels = None +depends_on = None + +logger = logging.getLogger(__name__) + +def upgrade() -> None: + connection = op.get_bind() + if connection.dialect.name == 'sqlite': + logger.info("""Migration 83c1020be97d corrupted alert_to_incident.deleted_at at SQLite databases +because server_default was set to \"1000-01-01 00:00:00\", not \"1000-01-01 00:00:00.000000\". +Fixing the value in this migration.""") + + # Filtering only by deleted_at = '1000-01-01 00:00:00'. If deleted_at is different, it should be already formated well. + result = connection.execute(sa.text("SELECT incident_id, alert_id, deleted_at FROM alerttoincident WHERE deleted_at = '1000-01-01 00:00:00'")) + db_datetime_format = "%Y-%m-%d %H:%M:%S.%f" + print(f"Database datetime format: {db_datetime_format}") + for row in result: + try: + connection.execute( + sa.text( + "UPDATE alerttoincident SET deleted_at = '1000-01-01 00:00:00.000000' WHERE incident_id = :incident_id AND alert_id = :alert_id AND deleted_at = '1000-01-01 00:00:00'" + ), + {"incident_id": row["incident_id"], "alert_id": row["alert_id"]} + ) + print(f"Updated deleted_at for incident_id: {row['incident_id']}, alert_id: {row['alert_id']}") + except sa.exc.IntegrityError as e: + if "UNIQUE constraint failed: alerttoincident.alert_id, alerttoincident.incident_id, alerttoincident.deleted_at" in str(e): + connection.execute( + sa.text( + "DELETE FROM alerttoincident WHERE incident_id = :incident_id AND alert_id = :alert_id AND deleted_at = '1000-01-01 00:00:00'" + ), + {"incident_id": row["incident_id"], "alert_id": row["alert_id"]} + ) + logger.warning(f"IntegrityError encountered for incident_id: {row['incident_id']}, alert_id: {row['alert_id']}. It's a duplicate. Deleted.") + else: + raise e + else: + logger.info("Skipping the fix since it's not SQLite.") + + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### From 7e2df165a6b0235f34fefa5d8f76f8f782ff68a1 Mon Sep 17 00:00:00 2001 From: Tal Date: Wed, 30 Oct 2024 11:42:23 +0200 Subject: [PATCH 2/2] fix(smtp): username and password are not mandatory (#2348) --- keep/providers/smtp_provider/smtp_provider.py | 91 ++++++++++--------- pyproject.toml | 2 +- 2 files changed, 48 insertions(+), 45 deletions(-) diff --git a/keep/providers/smtp_provider/smtp_provider.py b/keep/providers/smtp_provider/smtp_provider.py index c0606349a..f8f187c09 100644 --- a/keep/providers/smtp_provider/smtp_provider.py +++ b/keep/providers/smtp_provider/smtp_provider.py @@ -4,11 +4,11 @@ import dataclasses import typing - -import pydantic -from smtplib import SMTP, SMTP_SSL from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText +from smtplib import SMTP, SMTP_SSL + +import pydantic from keep.contextmanager.contextmanager import ContextManager from keep.providers.base.base_provider import BaseProvider @@ -17,23 +17,6 @@ @pydantic.dataclasses.dataclass class SmtpProviderAuthConfig: - smtp_username: str = dataclasses.field( - metadata={ - "required": True, - "description": "SMTP username", - "config_main_group": "authentication", - } - ) - - smtp_password: str = dataclasses.field( - metadata={ - "required": True, - "sensitive": True, - "description": "SMTP password", - "config_main_group": "authentication", - } - ) - smtp_server: str = dataclasses.field( metadata={ "required": True, @@ -47,7 +30,8 @@ class SmtpProviderAuthConfig: "required": True, "description": "SMTP port", "config_main_group": "authentication", - } + }, + default=587, ) encryption: typing.Literal["SSL", "TLS"] = dataclasses.field( @@ -61,6 +45,25 @@ class SmtpProviderAuthConfig: }, ) + smtp_username: str = dataclasses.field( + metadata={ + "required": False, + "description": "SMTP username", + "config_main_group": "authentication", + }, + default="", + ) + + smtp_password: str = dataclasses.field( + metadata={ + "required": False, + "sensitive": True, + "description": "SMTP password", + "config_main_group": "authentication", + }, + default="", + ) + class SmtpProvider(BaseProvider): PROVIDER_SCOPES = [ @@ -75,18 +78,18 @@ class SmtpProvider(BaseProvider): PROVIDER_DISPLAY_NAME = "SMTP" def __init__( - self, context_manager: ContextManager, provider_id: str, config: ProviderConfig + self, context_manager: ContextManager, provider_id: str, config: ProviderConfig ): super().__init__(context_manager, provider_id, config) def dispose(self): pass - + def validate_config(self): self.authentication_config = SmtpProviderAuthConfig( **self.config.authentication ) - + def validate_scopes(self): """ Validate that the scopes provided are correct. @@ -97,7 +100,7 @@ def validate_scopes(self): return {"send_email": True} except Exception as e: return {"send_email": str(e)} - + def generate_smtp_client(self): """ Generate an SMTP client. @@ -108,18 +111,20 @@ def generate_smtp_client(self): smtp_port = self.authentication_config.smtp_port encryption = self.authentication_config.encryption - if (encryption == "SSL"): + if encryption == "SSL": smtp = SMTP_SSL(smtp_server, smtp_port) - smtp.login(smtp_username, smtp_password) - return smtp - - elif (encryption == "TLS"): + elif encryption == "TLS": smtp = SMTP(smtp_server, smtp_port) smtp.starttls() + + if smtp_username and smtp_password: smtp.login(smtp_username, smtp_password) - return smtp - - def send_email(self, from_email: str, from_name: str, to_email: str, subject: str, body: str): + + return smtp + + def send_email( + self, from_email: str, from_name: str, to_email: str, subject: str, body: str + ): """ Send an email using SMTP protocol. """ @@ -127,9 +132,9 @@ def send_email(self, from_email: str, from_name: str, to_email: str, subject: st if from_name == "": msg["From"] = from_email msg["From"] = f"{from_name} <{from_email}>" - msg['To'] = to_email - msg['Subject'] = subject - msg.attach(MIMEText(body, 'plain')) + msg["To"] = to_email + msg["Subject"] = subject + msg.attach(MIMEText(body, "plain")) try: smtp = self.generate_smtp_client() @@ -138,18 +143,16 @@ def send_email(self, from_email: str, from_name: str, to_email: str, subject: st except Exception as e: raise Exception(f"Failed to send email: {str(e)}") - - def _notify(self, from_email: str, from_name: str, to_email: str, subject: str, body: str): + + def _notify( + self, from_email: str, from_name: str, to_email: str, subject: str, body: str + ): """ Send an email using SMTP protocol. """ self.send_email(from_email, from_name, to_email, subject, body) - return { - "from": from_email, - "to": to_email, - "subject": subject, - "body": body - } + return {"from": from_email, "to": to_email, "subject": subject, "body": body} + if __name__ == "__main__": import logging diff --git a/pyproject.toml b/pyproject.toml index 758f57d9e..0fea838e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "keep" -version = "0.27.7" +version = "0.27.8" description = "Alerting. for developers, by developers." authors = ["Keep Alerting LTD"] readme = "README.md"