FOSSology  4.5.1
Open Source License Compliance by Open Source Software
RepoSetup.py
1 #!/usr/bin/env python3
2 
3 # SPDX-FileCopyrightText: © 2023,2025 Siemens AG
4 # SPDX-FileContributor: Gaurav Mishra <mishra.gaurav@siemens.com>
5 
6 # SPDX-License-Identifier: GPL-2.0-only
7 
8 import fnmatch
9 import json
10 import os
11 import ssl
12 import urllib.request
13 from tempfile import TemporaryDirectory
14 
15 from .ApiConfig import ApiConfig, Runner
16 from .CliOptions import CliOptions
17 
18 
19 class RepoSetup:
20  """
21  Setup temp_dir using the diff or current MR
22 
23  :ivar temp_dir: Temporary directory location for storing MR changes.
24  :ivar allowlist: Allow list from JSON
25  :ivar api_config: ApiConfig
26  :ivar cli_options: CliOptions object
27  """
28 
29  def __init__(self, cli_options: CliOptions, api_config: ApiConfig):
30  """
31  Create a temp dir
32 
33  :param cli_options: CliOptions object to get allow list from
34  :param api_config: API configuration for the CI
35  """
36  self.temp_dir: TemporaryDirectory[str] | TemporaryDirectory[bytes] \
37  = TemporaryDirectory()
38  self.allowlist: dict[str, list[str]] = cli_options.allowlist
39  self.api_config: ApiConfig = api_config
40  self.cli_options: CliOptions = cli_options
41 
42  def __del__(self):
43  """
44  Clean the created temp dir
45  """
46  self.temp_dir.cleanup()
47 
48  def __is_excluded_path(self, path: str) -> bool:
49  """
50  Check if the path is allow listed
51 
52  The function used fnmatch to check if the path is in allow list or not.
53 
54  :param path: path to check
55  :return: True if the path is in allow list, False otherwise
56  """
57  for pattern in self.allowlist.get('exclude', []):
58  if fnmatch.fnmatchcase(path, pattern):
59  return True
60  return False
61 
62  def get_diff_dir(self) -> str:
63  """
64  Populate temp dir using the gitlab API `merge_requests`
65 
66  :return: temp dir path
67  """
68 
69  context = ssl.create_default_context()
70  context.check_hostname = False
71  context.verify_mode = ssl.CERT_NONE
72 
73  if self.api_config.running_on == Runner.GITLAB:
74  headers = {'Private-Token': self.api_config.api_token}
75  path_key = "new_path"
76  change_key = "diff"
77  api_req_url = (
78  f"{self.api_config.api_url}/projects/"
79  f"{self.api_config.project_id}/merge_requests/"
80  f"{self.api_config.mr_iid}/changes"
81  )
82  if self.cli_options.differential and self.cli_options.tags != ('',''):
83  tags = self.cli_options.tags
84  api_req_url = (
85  f"{self.api_config.api_url}/projects/"
86  f"{self.api_config.project_id}/repository/compare?"
87  f"from={tags[0]}&to={tags[1]}"
88  )
89 
90  elif self.api_config.running_on == Runner.GITHUB:
91  headers = {
92  "Authorization": f"Bearer {self.api_config.api_token}",
93  "X-GitHub-Api-Version": "2022-11-28",
94  "Accept": "application/vnd.github+json"
95  }
96  path_key = "filename"
97  change_key = "patch"
98  api_req_url = (
99  f"{self.api_config.api_url}/repos/"
100  f"{self.api_config.github_repo_slug}/pulls/"
101  f"{self.api_config.github_pull_request}/files"
102  )
103  if self.cli_options.differential and self.cli_options.tags != ('',''):
104  tags = self.cli_options.tags
105  api_req_url = (
106  f"{self.api_config.api_url}/repos/"
107  f"{self.api_config.github_repo_slug}/compare/"
108  f"{tags[0]}...{tags[1]}"
109  )
110 
111  else: # Default to Travis/GitHub behavior if runner is not explicitly GitLab or GitHub
112  api_req_url = (
113  "https://api.github.com/repos/"
114  f"{self.api_config.travis_repo_slug}/pulls/"
115  f"{self.api_config.travis_pull_request}/files"
116  )
117  headers = {}
118  path_key = "filename"
119  change_key = "patch"
120 
121  if not api_req_url:
122  raise ValueError("API request URL could not be constructed. Check ApiConfig and runner type.")
123 
124  req = urllib.request.Request(api_req_url, headers=headers)
125  try:
126  with urllib.request.urlopen(req, context=context) as response:
127  change_response = response.read()
128  except Exception as e:
129  print(f"Unable to get URL {api_req_url}")
130  raise e
131 
132  change_response = json.loads(change_response)
133  if self.api_config.running_on == Runner.GITLAB:
134  if self.cli_options.differential and self.cli_options.tags != ('',''):
135  changes = change_response.get('diffs', [])
136  else:
137  changes = change_response.get('changes', [])
138  elif self.api_config.running_on == Runner.GITHUB:
139  if self.cli_options.differential and self.cli_options.tags != ('',''):
140  changes = change_response.get('files', [])
141  else:
142  changes = change_response
143  else: # TRAVIS (or other unspecified runners)
144  changes = change_response
145 
146  for change in changes:
147  if path_key in change and change_key in change:
148  file_path_in_repo = change[path_key]
149  if not self.__is_excluded_path__is_excluded_path(file_path_in_repo):
150  full_temp_file_path = os.path.join(self.temp_dir.name, file_path_in_repo)
151  target_dir = os.path.dirname(full_temp_file_path)
152  if target_dir and not os.path.exists(target_dir):
153  os.makedirs(name=target_dir, exist_ok=True)
154 
155  try:
156  with open(file=full_temp_file_path, mode='w+', encoding='UTF-8') as f:
157  f.write(change[change_key])
158  except IOError as e:
159  print(f"Error writing file {full_temp_file_path}: {e}")
160  raise e
161 
162  return self.temp_dir.name
bool __is_excluded_path(self, str path)
Definition: RepoSetup.py:48
def __init__(self, CliOptions cli_options, ApiConfig api_config)
Definition: RepoSetup.py:29