Coverage for /usr/local/lib/python3.10/site-packages/opal_common-0.0.0-py3.10.egg/opal_common/sources/base_policy_source.py: 37%
57 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 11:54 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 11:54 +0000
1import asyncio
2import os
3from functools import partial
4from typing import Callable, Coroutine, List, Union
6from git.objects.commit import Commit
7from opal_common.logger import logger
9OnNewPolicyCallback = Callable[[Commit, Commit], Coroutine]
10OnPolicyFailureCallback = Callable[[Exception], Coroutine]
13class BasePolicySource:
14 """Base class to support git and api policy source.
16 Args:
17 remote_source_url(str): the base address to request the policy from
18 local_clone_path(str): path for the local git to manage policies
19 polling_interval(int): how many seconds need to wait between polling
20 """
22 def __init__(
23 self,
24 remote_source_url: str,
25 local_clone_path: str,
26 polling_interval: int = 0,
27 ):
28 self._on_failure_callbacks: List[OnNewPolicyCallback] = []
29 self._on_new_policy_callbacks: List[OnPolicyFailureCallback] = []
30 self._polling_interval = polling_interval
31 self._polling_task = None
32 self.remote_source_url = remote_source_url
33 self.local_clone_path = os.path.expanduser(local_clone_path)
35 def add_on_new_policy_callback(self, callback: OnNewPolicyCallback):
36 """Register a callback that will be called when new policy are detected
37 on the monitored repo (after a pull)."""
38 self._on_new_policy_callbacks.append(callback)
40 def add_on_failure_callback(self, callback: OnPolicyFailureCallback):
41 """Register a callback that will be called when failure occurred."""
42 self._on_failure_callbacks.append(callback)
44 async def get_initial_policy_state_from_remote(self):
45 """Init remote data to local repo."""
46 raise NotImplementedError()
48 async def check_for_changes(self):
49 """Trigger check for policy change."""
50 raise NotImplementedError()
52 async def run(self):
53 """Potentially starts the polling task."""
54 await self.get_initial_policy_state_from_remote()
56 if self._polling_interval > 0:
57 logger.info(
58 "Launching polling task, interval: {interval} seconds",
59 interval=self._polling_interval,
60 )
61 self._start_polling_task(self.check_for_changes)
62 else:
63 logger.info("Polling task is off")
65 async def stop(self):
66 return await self._stop_polling_task()
68 def _start_polling_task(self, polling_task):
69 if self._polling_task is None and self._polling_interval > 0:
70 self._polling_task = asyncio.create_task(self._do_polling(polling_task))
72 async def _do_polling(self, polling_task):
73 """Optional task to periodically check the remote for changes (git pull
74 and compare hash)."""
75 while True:
76 try:
77 await polling_task()
78 except Exception as ex:
79 logger.error(
80 "Error occurred during polling task {task}: {err}",
81 task=polling_task.__name__,
82 err=ex,
83 )
84 await asyncio.sleep(self._polling_interval)
86 async def _stop_polling_task(self):
87 if self._polling_task is not None:
88 self._polling_task.cancel()
89 try:
90 await self._polling_task
91 except asyncio.CancelledError:
92 pass
94 async def _on_new_policy(self, old: Commit, new: Commit):
95 """Triggers callbacks registered with on_new_policy()."""
96 await self._run_callbacks(self._on_new_policy_callbacks, old, new)
98 async def _on_failed(self, exc: Exception):
99 """Will be triggered if a failure occurred.
101 triggers callbacks registered with on_git_failed().
102 """
103 await self._run_callbacks(self._on_failure_callbacks, exc)
105 async def _run_callbacks(self, handlers, *args, **kwargs):
106 """Triggers a list of callbacks."""
107 await asyncio.gather(*(callback(*args, **kwargs) for callback in handlers))
109 async def _on_git_failed(self, exc: Exception):
110 """Will be triggered if a git failure occurred (i.e: repo does not
111 exist, can't clone, etc).
113 triggers callbacks registered with on_git_failed().
114 """
115 await self._run_callbacks(self._on_failure_callbacks, exc)