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

1import os 

2import shutil 

3from pathlib import Path 

4from typing import List, Optional 

5 

6import git 

7from opal_common.security.tarsafe import TarSafe 

8from pydantic.error_wrappers import ValidationError 

9 

10 

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. 

14 

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 """ 

19 

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 

29 

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 

49 

50 def create_local_git(self): 

51 """Extract bundle create local git and commit this initial state.""" 

52 

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 

58 

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 

77 

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) 

89 

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 

104 

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 )