Compare commits

..

72 Commits

Author SHA1 Message Date
f447611d6f 4.44.3
All checks were successful
Build-Release-Image / Build-Image (linux/amd64) (push) Successful in 2m53s
Build-Release-Image / Build-Image (linux/arm64) (push) Successful in 3m28s
Build-Release-Image / Merge-Images (push) Successful in 26s
Build-Release-Image / Create-Release (push) Successful in 10s
Build-Release-Image / Notify (push) Successful in 3s
2024-05-24 12:00:06 +01:00
b0a170dcb4 4.44.0
All checks were successful
Build-Release-Image / Build-Image (linux/amd64) (push) Successful in 3m31s
Build-Release-Image / Build-Image (linux/arm64) (push) Successful in 4m10s
Build-Release-Image / Merge-Images (push) Successful in 23s
Build-Release-Image / Create-Release (push) Successful in 10s
Build-Release-Image / Notify (push) Successful in 5s
2024-05-23 12:00:07 +01:00
d807c96f06 4.43.0
All checks were successful
Build-Release-Image / Build-Image (linux/amd64) (push) Successful in 3m31s
Build-Release-Image / Build-Image (linux/arm64) (push) Successful in 4m49s
Build-Release-Image / Merge-Images (push) Successful in 23s
Build-Release-Image / Create-Release (push) Successful in 30s
Build-Release-Image / Notify (push) Successful in 4s
2024-05-09 12:00:07 +01:00
22113148e2 4.42.2
All checks were successful
Build-Release-Image / Build-Image (linux/amd64) (push) Successful in 3m25s
Build-Release-Image / Build-Image (linux/arm64) (push) Successful in 4m39s
Build-Release-Image / Merge-Images (push) Successful in 1m6s
Build-Release-Image / Create-Release (push) Successful in 11s
Build-Release-Image / Notify (push) Successful in 4s
2024-04-10 17:23:11 +01:00
ee6bcad4f9 4.42.1
All checks were successful
Build-Release-Image / Build-Image (linux/amd64) (push) Successful in 3m7s
Build-Release-Image / Build-Image (linux/arm64) (push) Successful in 3m32s
Build-Release-Image / Merge-Images (push) Successful in 18s
Build-Release-Image / Create-Release (push) Successful in 14s
Build-Release-Image / Notify (push) Successful in 15s
2024-03-26 12:00:08 +00:00
42696112cb 4.42.0
All checks were successful
Build-Release-Image / Build-Image (linux/arm64) (push) Successful in 3m40s
Build-Release-Image / Build-Image (linux/amd64) (push) Successful in 4m7s
Build-Release-Image / Merge-Images (push) Successful in 22s
Build-Release-Image / Create-Release (push) Successful in 15s
Build-Release-Image / Notify (push) Successful in 17s
2024-03-19 12:00:09 +00:00
df611e4774 4.41.2
All checks were successful
Build-Release-Image / Build-Image (linux/arm64) (push) Successful in 3m24s
Build-Release-Image / Build-Image (linux/amd64) (push) Successful in 3m44s
Build-Release-Image / Merge-Images (push) Successful in 17s
Build-Release-Image / Create-Release (push) Successful in 11s
Build-Release-Image / Notify (push) Successful in 5s
2024-03-15 12:00:08 +00:00
cb216393a5 Merge pull request 'Correct docker package name' (#2) from fix-package-name-in-gitea-actions into main
All checks were successful
Build-Release-Image / Build-Image (linux/amd64) (push) Successful in 3m21s
Build-Release-Image / Build-Image (linux/arm64) (push) Successful in 4m23s
Build-Release-Image / Merge-Images (push) Successful in 21s
Build-Release-Image / Create-Release (push) Successful in 32s
Build-Release-Image / Notify (push) Successful in 4s
Reviewed-on: #2
2024-03-14 15:47:01 +00:00
a77c8d2a51 Correct docker package name 2024-03-14 15:46:44 +00:00
6ce4344005 4.41.0
All checks were successful
Build-Release-Image / Build-Image (linux/amd64) (push) Successful in 3m35s
Build-Release-Image / Build-Image (linux/arm64) (push) Successful in 4m50s
Build-Release-Image / Merge-Images (push) Successful in 19s
Build-Release-Image / Create-Release (push) Successful in 12s
Build-Release-Image / Notify (push) Successful in 21s
2024-03-14 12:00:08 +00:00
2f50662c5d 4.40.2
All checks were successful
Build-Release-Image / Build-Image (linux/arm64) (push) Successful in 3m24s
Build-Release-Image / Build-Image (linux/amd64) (push) Successful in 4m8s
Build-Release-Image / Merge-Images (push) Successful in 51s
Build-Release-Image / Create-Release (push) Successful in 22s
Build-Release-Image / Notify (push) Successful in 18s
2024-03-07 12:00:08 +00:00
810b59efec 4.40.1
All checks were successful
Build-Release-Image / Build-Image (linux/arm64) (push) Successful in 3m26s
Build-Release-Image / Build-Image (linux/amd64) (push) Successful in 3m48s
Build-Release-Image / Merge-Images (push) Successful in 20s
Build-Release-Image / Create-Release (push) Successful in 33s
Build-Release-Image / Notify (push) Successful in 20s
2024-03-05 12:00:09 +00:00
bfbcf567aa Merge pull request 'Replace Drone with Gitea Actions' (#1) from gitea-actions into main
Reviewed-on: #1
2024-03-04 13:42:58 +00:00
a505186051 Remove Drone 2024-03-04 13:38:57 +00:00
8fcca8571a Add Gitea Actions 2024-03-04 13:38:52 +00:00
1550507667 4.39.3
All checks were successful
continuous-integration/drone/tag Build is passing
2024-02-27 12:00:07 +00:00
757f153042 4.39.2
All checks were successful
continuous-integration/drone/tag Build is passing
2024-02-23 12:00:07 +00:00
a9f65bed60 4.39.1
All checks were successful
continuous-integration/drone/tag Build is passing
2024-02-20 12:00:07 +00:00
a8ca607581 4.38.3
All checks were successful
continuous-integration/drone/tag Build is passing
2024-02-14 12:00:07 +00:00
5b47bd1654 4.38.2
All checks were successful
continuous-integration/drone/tag Build is passing
2024-02-06 12:00:07 +00:00
e9faf93878 4.38.0
All checks were successful
continuous-integration/drone/tag Build is passing
2024-02-03 16:55:23 +00:00
0f60f7cec9 4.37.2
All checks were successful
continuous-integration/drone/tag Build is passing
2024-01-27 12:00:07 +00:00
3180034ff8 4.37.1
All checks were successful
continuous-integration/drone/tag Build is passing
2024-01-25 12:00:08 +00:00
b3ee67213d 4.37.0
All checks were successful
continuous-integration/drone/tag Build is passing
2024-01-18 12:00:07 +00:00
aeb34f8582 4.36.8
All checks were successful
continuous-integration/drone/tag Build is passing
2023-12-28 12:00:07 +00:00
2372b8f50f 4.36.7
All checks were successful
continuous-integration/drone/tag Build is passing
2023-12-21 12:00:09 +00:00
f3050b2ca0 4.36.6
All checks were successful
continuous-integration/drone/tag Build is passing
2023-12-17 14:56:57 +00:00
ebe941c8a5 4.36.5
All checks were successful
continuous-integration/drone/tag Build is passing
2023-11-30 12:00:09 +00:00
651b2dd52a 4.36.4 2023-11-22 12:00:09 +00:00
1c580cb6f7 4.36.3 2023-11-08 12:00:06 +00:00
21765ae9d8 4.35.6 2023-11-07 12:00:06 +00:00
d661a52f43 4.35.3
All checks were successful
continuous-integration/drone/tag Build is passing
2023-10-05 12:00:06 +01:00
45528ff81d 4.35.2
All checks were successful
continuous-integration/drone/tag Build is passing
2023-10-03 12:00:06 +01:00
6170fbf127 4.35.1
All checks were successful
continuous-integration/drone/tag Build is passing
2023-10-02 12:00:06 +01:00
c8ab76066b 4.35.0
All checks were successful
continuous-integration/drone/tag Build is passing
2023-09-29 12:00:06 +01:00
357d34a42b 4.34.4
All checks were successful
continuous-integration/drone/tag Build is passing
2023-08-31 12:00:06 +01:00
246754872d 4.34.3
All checks were successful
continuous-integration/drone/tag Build is passing
2023-08-29 20:20:00 +01:00
df59d73d66 4.34.2
All checks were successful
continuous-integration/drone/tag Build is passing
2023-08-25 12:00:05 +01:00
ff6d78f255 4.34.1
All checks were successful
continuous-integration/drone/tag Build is passing
2023-08-09 12:00:05 +01:00
d59fa5fe1c Update .drone.yml 2023-08-06 17:56:31 +00:00
de1fe02200 4.33.3
All checks were successful
continuous-integration/drone/tag Build is passing
2023-08-06 17:51:04 +01:00
439bfc5efd Update README.md 2023-08-06 16:04:57 +00:00
0a8a420850 Update README.md 2023-08-06 16:04:47 +00:00
d119e74c2f Update README.md 2023-08-06 16:04:41 +00:00
b5485429ef Remove provenance [CI SKIP] 2023-08-06 16:01:04 +00:00
f3a7900cbd 4.32.4
All checks were successful
continuous-integration/drone/tag Build is passing
2023-08-02 16:49:54 +01:00
0f91161ff3 4.32.1
All checks were successful
continuous-integration/drone/tag Build is passing
2023-07-12 11:00:04 +00:00
1da9a3f828 4.32.0
All checks were successful
continuous-integration/drone/tag Build is passing
2023-07-11 11:00:05 +00:00
167e56bc95 4.31.0
Some checks failed
continuous-integration/drone/tag Build is failing
2023-06-30 11:00:06 +00:00
c5a0d2d513 4.30.1
Some checks failed
continuous-integration/drone/tag Build is failing
2023-06-28 11:00:03 +00:00
25ebbaa7fd 4.30.0
Some checks failed
continuous-integration/drone/tag Build is failing
2023-06-27 11:00:04 +00:00
067d94841e 4.29.4
All checks were successful
continuous-integration/drone/tag Build is passing
2023-06-07 11:00:05 +00:00
804eec0c03 4.29.3
All checks were successful
continuous-integration/drone/tag Build is passing
2023-06-01 11:00:05 +00:00
651f3f1e9c 4.28.2
All checks were successful
continuous-integration/drone/tag Build is passing
2023-05-16 11:00:09 +00:00
fd988d6ef0 4.28.1
All checks were successful
continuous-integration/drone/tag Build is passing
2023-05-10 11:00:05 +00:00
da4a8cc979 4.27.0
All checks were successful
continuous-integration/drone/tag Build is passing
2023-04-25 11:00:05 +00:00
299da46abe 4.26.1
All checks were successful
continuous-integration/drone/tag Build is passing
2023-04-20 11:00:06 +00:00
1ad8294ec3 4.25.1
All checks were successful
continuous-integration/drone/tag Build is passing
2023-04-15 11:00:05 +00:00
f5de4a9624 4.24.0
All checks were successful
continuous-integration/drone/tag Build is passing
2023-04-11 11:00:05 +00:00
5501b033e8 4.23.0
All checks were successful
continuous-integration/drone/tag Build is passing
2023-03-24 12:00:07 +00:00
32a4b865ef 4.22.5
All checks were successful
continuous-integration/drone/tag Build is passing
2023-03-14 12:00:06 +00:00
5d5a23dd63 Update '.drone.yml' 2023-03-08 18:32:53 +00:00
03053d0e54 4.22.4
Some checks are pending
continuous-integration/drone/tag Build is running
2023-03-08 12:00:06 +00:00
4d70590d05 4.22.3
All checks were successful
continuous-integration/drone/tag Build is passing
2023-03-01 12:00:06 +00:00
bc879c10ae 4.22.2
All checks were successful
continuous-integration/drone/tag Build is passing
2023-02-16 12:00:05 +00:00
c6b237a004 Update 'README.md' 2023-02-10 13:00:46 +00:00
722979fe19 Update 'README.md' 2023-01-27 16:29:12 +00:00
b63ada023d Update '.drone.yml' 2023-01-27 16:26:22 +00:00
8b4e4e3a2b 4.22.0
All checks were successful
continuous-integration/drone/tag Build is passing
2023-01-17 12:00:04 +00:00
32465d1220 4.21.3
All checks were successful
continuous-integration/drone/tag Build is passing
2022-12-30 16:47:07 +00:00
98bae4c86c 4.21.3 2022-12-30 16:23:27 +00:00
7ff6cf2451 add drone 2022-12-30 15:35:10 +00:00
8 changed files with 109 additions and 15 deletions

View File

@ -583,3 +583,7 @@ UPCLOUD_DB_ID = os.environ.get("UPCLOUD_DB_ID", None)
STORE_TRANSACTIONAL_EMAILS = "STORE_TRANSACTIONAL_EMAILS" in os.environ STORE_TRANSACTIONAL_EMAILS = "STORE_TRANSACTIONAL_EMAILS" in os.environ
EVENT_WEBHOOK = os.environ.get("EVENT_WEBHOOK", None) EVENT_WEBHOOK = os.environ.get("EVENT_WEBHOOK", None)
# We want it disabled by default, so only skip if defined
EVENT_WEBHOOK_SKIP_VERIFY_SSL = "EVENT_WEBHOOK_SKIP_VERIFY_SSL" in os.environ
EVENT_WEBHOOK_DISABLE = "EVENT_WEBHOOK_DISABLE" in os.environ

View File

@ -34,6 +34,9 @@ class EventDispatcher:
dispatcher: Dispatcher = PostgresDispatcher.get(), dispatcher: Dispatcher = PostgresDispatcher.get(),
skip_if_webhook_missing: bool = True, skip_if_webhook_missing: bool = True,
): ):
if config.EVENT_WEBHOOK_DISABLE:
return
if not config.EVENT_WEBHOOK and skip_if_webhook_missing: if not config.EVENT_WEBHOOK and skip_if_webhook_missing:
return return

View File

@ -3699,7 +3699,10 @@ class SyncEvent(Base, ModelMixin):
AND taken_time IS NULL AND taken_time IS NULL
""" """
args = {"taken_time": arrow.now().datetime, "sync_event_id": self.id} args = {"taken_time": arrow.now().datetime, "sync_event_id": self.id}
res = Session.execute(sql, args) res = Session.execute(sql, args)
Session.commit()
return res.rowcount > 0 return res.rowcount > 0
@classmethod @classmethod

View File

@ -3,9 +3,10 @@ from enum import Enum
from sys import argv, exit from sys import argv, exit
from app.config import DB_URI from app.config import DB_URI
from app.log import LOG
from events.runner import Runner from events.runner import Runner
from events.event_source import DeadLetterEventSource, PostgresEventSource from events.event_source import DeadLetterEventSource, PostgresEventSource
from events.event_sink import ConsoleEventSink from events.event_sink import ConsoleEventSink, HttpEventSink
class Mode(Enum): class Mode(Enum):
@ -24,16 +25,20 @@ class Mode(Enum):
def main(mode: Mode, dry_run: bool): def main(mode: Mode, dry_run: bool):
if mode == Mode.DEAD_LETTER: if mode == Mode.DEAD_LETTER:
LOG.i("Using DeadLetterEventSource")
source = DeadLetterEventSource() source = DeadLetterEventSource()
elif mode == Mode.LISTENER: elif mode == Mode.LISTENER:
LOG.i("Using PostgresEventSource")
source = PostgresEventSource(DB_URI) source = PostgresEventSource(DB_URI)
else: else:
raise ValueError(f"Invalid mode: {mode}") raise ValueError(f"Invalid mode: {mode}")
if dry_run: if dry_run:
LOG.i("Starting with ConsoleEventSink")
sink = ConsoleEventSink() sink = ConsoleEventSink()
else: else:
sink = ConsoleEventSink() LOG.i("Starting with HttpEventSink")
sink = HttpEventSink()
runner = Runner(source=source, sink=sink) runner = Runner(source=source, sink=sink)
runner.run() runner.run()

View File

@ -1,19 +1,42 @@
import requests
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from app.config import EVENT_WEBHOOK, EVENT_WEBHOOK_SKIP_VERIFY_SSL
from app.log import LOG from app.log import LOG
from app.models import SyncEvent from app.models import SyncEvent
class EventSink(ABC): class EventSink(ABC):
@abstractmethod @abstractmethod
def process(self, event: SyncEvent): def process(self, event: SyncEvent) -> bool:
pass pass
class HttpEventSink(EventSink): class HttpEventSink(EventSink):
def process(self, event: SyncEvent): def process(self, event: SyncEvent) -> bool:
pass if not EVENT_WEBHOOK:
LOG.warning("Skipping sending event because there is no webhook configured")
return False
LOG.info(f"Sending event {event.id} to {EVENT_WEBHOOK}")
res = requests.post(
url=EVENT_WEBHOOK,
data=event.content,
headers={"Content-Type": "application/x-protobuf"},
verify=not EVENT_WEBHOOK_SKIP_VERIFY_SSL,
)
if res.status_code != 200:
LOG.warning(
f"Failed to send event to webhook: {res.status_code} {res.text}"
)
return False
else:
LOG.info(f"Event {event.id} sent successfully to webhook")
return True
class ConsoleEventSink(EventSink): class ConsoleEventSink(EventSink):
def process(self, event: SyncEvent): def process(self, event: SyncEvent) -> bool:
LOG.info(f"Handling event {event.id}") LOG.info(f"Handling event {event.id}")
return True

View File

@ -13,6 +13,8 @@ from typing import Callable, NoReturn
_DEAD_LETTER_THRESHOLD_MINUTES = 10 _DEAD_LETTER_THRESHOLD_MINUTES = 10
_DEAD_LETTER_INTERVAL_SECONDS = 30 _DEAD_LETTER_INTERVAL_SECONDS = 30
_POSTGRES_RECONNECT_INTERVAL_SECONDS = 5
class EventSource(ABC): class EventSource(ABC):
@abstractmethod @abstractmethod
@ -22,9 +24,19 @@ class EventSource(ABC):
class PostgresEventSource(EventSource): class PostgresEventSource(EventSource):
def __init__(self, connection_string: str): def __init__(self, connection_string: str):
self.__connection = psycopg2.connect(connection_string) self.__connection_string = connection_string
self.__connect()
def run(self, on_event: Callable[[SyncEvent], NoReturn]): def run(self, on_event: Callable[[SyncEvent], NoReturn]):
while True:
try:
self.__listen(on_event)
except Exception as e:
LOG.warn(f"Error listening to events: {e}")
sleep(_POSTGRES_RECONNECT_INTERVAL_SECONDS)
self.__connect()
def __listen(self, on_event: Callable[[SyncEvent], NoReturn]):
self.__connection.set_isolation_level( self.__connection.set_isolation_level(
psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT
) )
@ -44,12 +56,24 @@ class PostgresEventSource(EventSource):
webhook_id = int(notify.payload) webhook_id = int(notify.payload)
event = SyncEvent.get_by(id=webhook_id) event = SyncEvent.get_by(id=webhook_id)
if event is not None: if event is not None:
if event.mark_as_taken():
on_event(event) on_event(event)
else:
LOG.info(
f"Event {event.id} was handled by another runner"
)
else: else:
LOG.info(f"Could not find event with id={notify.payload}") LOG.info(f"Could not find event with id={notify.payload}")
except Exception as e: except Exception as e:
LOG.warn(f"Error getting event: {e}") LOG.warn(f"Error getting event: {e}")
def __connect(self):
self.__connection = psycopg2.connect(self.__connection_string)
from app.db import Session
Session.close()
class DeadLetterEventSource(EventSource): class DeadLetterEventSource(EventSource):
@newrelic.agent.background_task() @newrelic.agent.background_task()
@ -73,3 +97,4 @@ class DeadLetterEventSource(EventSource):
sleep(_DEAD_LETTER_INTERVAL_SECONDS) sleep(_DEAD_LETTER_INTERVAL_SECONDS)
except Exception as e: except Exception as e:
LOG.warn(f"Error getting dead letter event: {e}") LOG.warn(f"Error getting dead letter event: {e}")
sleep(_DEAD_LETTER_INTERVAL_SECONDS)

View File

@ -18,11 +18,10 @@ class Runner:
@newrelic.agent.background_task() @newrelic.agent.background_task()
def __on_event(self, event: SyncEvent): def __on_event(self, event: SyncEvent):
try: try:
can_process = event.mark_as_taken()
if can_process:
event_created_at = event.created_at event_created_at = event.created_at
start_time = arrow.now() start_time = arrow.now()
self.__sink.process(event) success = self.__sink.process(event)
if success:
event_id = event.id event_id = event.id
SyncEvent.delete(event.id, commit=True) SyncEvent.delete(event.id, commit=True)
LOG.info(f"Marked {event_id} as done") LOG.info(f"Marked {event_id} as done")
@ -38,8 +37,6 @@ class Runner:
"Custom/sync_event_elapsed_time", "Custom/sync_event_elapsed_time",
time_between_taken_and_created.total_seconds(), time_between_taken_and_created.total_seconds(),
) )
else:
LOG.info(f"{event.id} was handled by another runner")
except Exception as e: except Exception as e:
LOG.warn(f"Exception processing event [id={event.id}]: {e}") LOG.warn(f"Exception processing event [id={event.id}]: {e}")
newrelic.agent.record_custom_metric("Custom/sync_event_failed", 1) newrelic.agent.record_custom_metric("Custom/sync_event_failed", 1)

View File

@ -4,6 +4,7 @@ import subprocess
from time import sleep from time import sleep
from typing import List, Dict from typing import List, Dict
import arrow
import newrelic.agent import newrelic.agent
from app.db import Session from app.db import Session
@ -93,11 +94,44 @@ def log_nb_db_connection():
newrelic.agent.record_custom_metric("Custom/nb_db_connections", nb_connection) newrelic.agent.record_custom_metric("Custom/nb_db_connections", nb_connection)
@newrelic.agent.background_task()
def log_pending_to_process_events():
r = Session.execute("select count(*) from sync_event WHERE taken_time IS NULL;")
events_pending = list(r)[0][0]
LOG.d("number of events pending to process %s", events_pending)
newrelic.agent.record_custom_metric(
"Custom/sync_events_pending_to_process", events_pending
)
@newrelic.agent.background_task()
def log_events_pending_dead_letter():
since = arrow.now().shift(minutes=-10).datetime
r = Session.execute(
"""
SELECT COUNT(*)
FROM sync_event
WHERE (taken_time IS NOT NULL AND taken_time < :since)
OR (taken_time IS NULL AND created_at < :since)
""",
{"since": since},
)
events_pending = list(r)[0][0]
LOG.d("number of events pending dead letter %s", events_pending)
newrelic.agent.record_custom_metric(
"Custom/sync_events_pending_dead_letter", events_pending
)
if __name__ == "__main__": if __name__ == "__main__":
exporter = MetricExporter(get_newrelic_license()) exporter = MetricExporter(get_newrelic_license())
while True: while True:
log_postfix_metrics() log_postfix_metrics()
log_nb_db_connection() log_nb_db_connection()
log_pending_to_process_events()
log_events_pending_dead_letter()
Session.close() Session.close()
exporter.run() exporter.run()