Coverage for /usr/local/lib/python3.10/site-packages/opal_common-0.0.0-py3.10.egg/opal_common/sources/git_policy_source.py: 29%
37 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
1from typing import Optional
3from git import Repo
4from opal_common.git_utils.branch_tracker import BranchTracker
5from opal_common.git_utils.exceptions import GitFailed
6from opal_common.git_utils.repo_cloner import RepoCloner
7from opal_common.http_utils import redact_url
8from opal_common.logger import logger
9from opal_common.sources.base_policy_source import BasePolicySource
12class GitPolicySource(BasePolicySource):
13 """Watches a git repository for changes and can trigger callbacks when
14 detecting new commits on the tracked branch.
16 Checking for changes is done following a git pull from a tracked
17 remote. The pull can be either triggered by a method (i.e: you can
18 call it from a webhook) or can be triggered periodically by a polling
19 task.
21 Args:
22 remote_source_url(str): the base address to request the policy from
23 local_clone_path(str): path for the local git to manage policies
24 branch_name(str): name of remote branch in git to pull, default to master
25 ssh_key (str, optional): private ssh key used to gain access to the cloned repo
26 polling_interval(int): how many seconds need to wait between polling
27 request_timeout(int): how many seconds need to wait until timeout
28 """
30 def __init__(
31 self,
32 remote_source_url: str,
33 local_clone_path: str,
34 branch_name: str = "master",
35 ssh_key: Optional[str] = None,
36 polling_interval: int = 0,
37 request_timeout: int = 0,
38 ):
39 super().__init__(
40 remote_source_url=remote_source_url,
41 local_clone_path=local_clone_path,
42 polling_interval=polling_interval,
43 )
44 self._ssh_key = ssh_key
46 self._cloner = RepoCloner(
47 remote_source_url,
48 local_clone_path,
49 branch_name=branch_name,
50 ssh_key=self._ssh_key,
51 clone_timeout=request_timeout,
52 )
53 self._branch_name = branch_name
54 self._tracker = None
56 async def get_initial_policy_state_from_remote(self):
57 """Init remote data to local repo."""
58 try:
59 try:
60 # Check if path already contains valid repo
61 repo = Repo(self._cloner.path)
62 except:
63 # If it doesn't - clone it
64 result = await self._cloner.clone()
65 repo = result.repo
66 else:
67 # If it does - validate remote url is correct and checkout required branch
68 remote_urls = list(repo.remote().urls)
69 if not self._cloner.url in remote_urls:
70 # Don't bother with remove and reclone because this case shouldn't happen on reasobable usage
71 raise GitFailed(
72 RuntimeError(
73 f"Existing repo has wrong remote url: {[redact_url(u) for u in remote_urls]}"
74 )
75 )
76 else:
77 logger.info(
78 "SKIPPED cloning policy repo, found existing repo at '{path}' with remotes: {remote_urls}",
79 path=self._cloner.path,
80 remote_urls=[redact_url(u) for u in remote_urls],
81 )
82 except GitFailed as e:
83 await self._on_git_failed(e)
84 return
86 self._tracker = BranchTracker(
87 repo=repo, branch_name=self._branch_name, ssh_key=self._ssh_key
88 )
90 async def check_for_changes(self):
91 """Calling this method will trigger a git pull from the tracked remote.
93 If after the pull the watcher detects new commits, it will call
94 the callbacks registered with _on_new_policy().
95 """
96 logger.info(
97 "Pulling changes from remote: '{remote}'",
98 remote=self._tracker.tracked_remote.name,
99 )
100 has_changes, prev, latest = self._tracker.pull()
101 if not has_changes:
102 logger.info("No new commits: HEAD is at '{head}'", head=latest.hexsha)
103 else:
104 logger.info(
105 "Found new commits: old HEAD was '{prev_head}', new HEAD is '{new_head}'",
106 prev_head=prev.hexsha,
107 new_head=latest.hexsha,
108 )
109 await self._on_new_policy(old=prev, new=latest)