Coverage for /usr/local/lib/python3.10/site-packages/opal_common-0.0.0-py3.10.egg/opal_common/git_utils/tar_file_to_local_git_extractor.py: 25%
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 os
2import shutil
3from pathlib import Path
4from typing import List, Optional
6import git
7from opal_common.security.tarsafe import TarSafe
8from pydantic.error_wrappers import ValidationError
11class TarFileToLocalGitExtractor:
12 """This class takes tar file from remote api source and extract it to local
13 git, so we could manage update to opal clients.
15 Args:
16 local_clone_path(str): path for the local git to manage policies
17 tmp_bundle_path(Path): path to download bundle from api source
18 """
20 def __init__(
21 self,
22 local_clone_path: str,
23 tmp_bundle_path: Path,
24 policy_bundle_git_add_pattern="*",
25 ):
26 self.local_clone_path = local_clone_path
27 self.tmp_bundle_path = tmp_bundle_path
28 self.policy_bundle_git_add_pattern = policy_bundle_git_add_pattern
30 def commit_local_git(
31 self, init_commit_msg: str = "Init", should_init: bool = False
32 ):
33 """
34 Commit first version of bundle or the updates that come after
35 Args:
36 init_commit_msg(str): text of the commit msg
37 should_init(Path): should it init the repo or it is existing repo
38 """
39 if should_init:
40 local_git = git.Repo.init(self.local_clone_path)
41 else:
42 local_git = git.Repo(self.local_clone_path)
43 prev_commit = None
44 if len(local_git.index.repo.heads):
45 prev_commit = local_git.index.repo.head.commit
46 local_git.index.add(self.policy_bundle_git_add_pattern)
47 new_commit = local_git.index.commit(init_commit_msg)
48 return local_git, prev_commit, new_commit
50 def create_local_git(self):
51 """Extract bundle create local git and commit this initial state."""
53 self.extract_bundle_tar()
54 local_git = TarFileToLocalGitExtractor.is_git_repo(self.local_clone_path)
55 if not local_git or len(local_git.heads) == 0:
56 local_git = self.commit_local_git(should_init=True)
57 return local_git
59 def extract_bundle_to_local_git(self, commit_msg: str):
60 """
61 Update local git with new bundle
62 Args:
63 commit_msg(str): text of the commit msg
64 """
65 tmp_path = f"{self.local_clone_path}.bak"
66 os.rename(self.local_clone_path, tmp_path)
67 try:
68 self.extract_bundle_tar()
69 shutil.move(
70 os.path.join(tmp_path, ".git"),
71 os.path.join(self.local_clone_path, ".git"),
72 )
73 finally:
74 shutil.rmtree(tmp_path)
75 local_git, prev_commit, new_commit = self.commit_local_git(commit_msg)
76 return local_git, prev_commit, new_commit
78 def extract_bundle_tar(self, mode: str = "r:gz") -> bool:
79 """
80 Extract bundle tar, tar path is at self.tmp_bundle_path
81 Uses TarSafe that checks that our bundle file don't have vulnerabilities like path traversal
82 Args:
83 mode(str): mode for TarSafe default to r:gz that can open tar.gz files
84 """
85 with TarSafe.open(self.tmp_bundle_path, mode=mode) as tar_file:
86 tar_file_names = tar_file.getnames()
87 TarFileToLocalGitExtractor.validate_tar_or_throw(tar_file_names)
88 tar_file.extractall(path=self.local_clone_path)
90 @staticmethod
91 def is_git_repo(path) -> Optional[git.Repo]:
92 """
93 Checks is this path is a git repo if it is return Repo obj
94 Return:
95 Repo obj if it is a git repo if not returns None
96 """
97 local_git = False
98 try:
99 local_git = git.Repo(path)
100 _ = local_git.git_dir
101 return local_git
102 except Exception:
103 return None
105 @staticmethod
106 def validate_tar_or_throw(
107 tar_file_names: List[str], forbidden_filename: str = ".git"
108 ):
109 if len(tar_file_names) == 0:
110 raise ValidationError("No files in bundle")
111 if forbidden_filename and forbidden_filename in tar_file_names:
112 raise ValidationError(
113 "No {forbidden_filename} files are allowed in OPAL api bundle".format(
114 forbidden_filename=forbidden_filename
115 )
116 )