qontract-reconcile 0.10.2.dev183__py3-none-any.whl → 0.10.2.dev185__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,7 +16,7 @@ from reconcile.gql_definitions.service_dependencies.service_dependencies import
16
16
  SaasResourceTemplateV2,
17
17
  )
18
18
  from reconcile.utils import gql
19
- from reconcile.utils.vcs import GITHUB_BASE_URL
19
+ from reconcile.utils.vcs import VCS
20
20
 
21
21
  QONTRACT_INTEGRATION = "service-dependencies"
22
22
 
@@ -32,15 +32,13 @@ def get_desired_dependency_names(
32
32
  required_dep_names = set()
33
33
 
34
34
  code_components: list[AppCodeComponentsV1] = app.code_components or []
35
- if code_components:
36
- gitlab_urls = [cc for cc in code_components if "gitlab" in cc.url]
37
- if gitlab_urls:
38
- required_dep_names.update(get_dependency_names(dependency_map, "gitlab"))
39
- github_urls = [
40
- cc for cc in code_components if cc.url.startswith(GITHUB_BASE_URL)
41
- ]
42
- if github_urls:
43
- required_dep_names.update(get_dependency_names(dependency_map, "github"))
35
+ code_component_platforms = {
36
+ platform
37
+ for cc in code_components
38
+ if (platform := VCS.parse_repo_url(cc.url).platform)
39
+ }
40
+ for platform in code_component_platforms:
41
+ required_dep_names.update(get_dependency_names(dependency_map, platform))
44
42
 
45
43
  jenkins_configs: list[JenkinsConfigV1] = app.jenkins_configs or []
46
44
  if jenkins_configs:
@@ -10,7 +10,6 @@ from typing import (
10
10
  Any,
11
11
  TypedDict,
12
12
  )
13
- from urllib.parse import urlparse
14
13
 
15
14
  from github.GithubException import UnknownObjectException
16
15
  from pydantic import BaseModel
@@ -67,6 +66,7 @@ from reconcile.utils.slack_api import (
67
66
  SlackApiError,
68
67
  UsergroupNotFoundException,
69
68
  )
69
+ from reconcile.utils.vcs import VCS
70
70
 
71
71
  DATE_FORMAT = "%Y-%m-%d %H:%M"
72
72
  QONTRACT_INTEGRATION = "slack-usergroups"
@@ -77,25 +77,23 @@ error_occurred = False
77
77
 
78
78
  def get_git_api(url: str) -> GithubRepositoryApi | GitLabApi:
79
79
  """Return GitHub/GitLab API based on url."""
80
- parsed_url = urlparse(url)
81
-
82
- if parsed_url.hostname:
83
- if "github" in parsed_url.hostname:
80
+ repo_info = VCS.parse_repo_url(url)
81
+ match repo_info.platform:
82
+ case "github":
84
83
  vault_settings = get_app_interface_vault_settings()
85
84
  secret_reader = create_secret_reader(use_vault=vault_settings.vault)
86
85
  instance = queries.get_github_instance()
87
86
  token = secret_reader.read(instance["token"])
88
-
89
87
  return GithubRepositoryApi(
90
88
  repo_url=url,
91
89
  token=token,
92
90
  )
93
- if "gitlab" in parsed_url.hostname:
91
+ case "gitlab":
94
92
  settings = queries.get_app_interface_settings()
95
93
  instance = queries.get_gitlab_instance()
96
94
  return GitLabApi(instance=instance, project_url=url, settings=settings)
97
-
98
- raise ValueError(f"Unable to handle URL: {url}")
95
+ case _:
96
+ raise ValueError(f"Unable to handle URL: {url}")
99
97
 
100
98
 
101
99
  class SlackObject(BaseModel):
@@ -388,7 +388,7 @@ class GitLabApi:
388
388
 
389
389
  @retry()
390
390
  def get_project(self, repo_url: str) -> Project | None:
391
- repo = repo_url.replace(self.server + "/", "")
391
+ repo = repo_url.removeprefix(self.server).strip("/")
392
392
  try:
393
393
  project = self.gl.projects.get(repo)
394
394
  except GitlabGetError:
@@ -20,7 +20,6 @@ from contextlib import suppress
20
20
  from datetime import UTC, datetime, timedelta
21
21
  from types import TracebackType
22
22
  from typing import Any
23
- from urllib.parse import urlparse
24
23
 
25
24
  import yaml
26
25
  from github import (
@@ -83,7 +82,7 @@ from reconcile.utils.saasherder.models import (
83
82
  )
84
83
  from reconcile.utils.secret_reader import SecretReaderBase
85
84
  from reconcile.utils.state import State
86
- from reconcile.utils.vcs import GITHUB_BASE_URL
85
+ from reconcile.utils.vcs import VCS
87
86
 
88
87
  TARGET_CONFIG_HASH = "target_config_hash"
89
88
 
@@ -741,17 +740,21 @@ class SaasHerder: # pylint: disable=too-many-public-methods
741
740
  trigger_reason: str,
742
741
  ) -> tuple[str, str]:
743
742
  [url, sha] = trigger_reason.split(" ")[0].split("/commit/")
744
- repo_name = urlparse(url).path.strip("/")
743
+ repo_info = VCS.parse_repo_url(url)
744
+ repo_name = repo_info.name
745
745
  file_name = f"{repo_name.replace('/', '-')}-{sha}.tar.gz"
746
- if url.startswith(GITHUB_BASE_URL):
747
- github = self._initiate_github(saas_file, base_url="https://api.github.com")
748
- repo = github.get_repo(repo_name)
749
- # get_archive_link get redirect url form header, it does not work with github-mirror
750
- archive_url = repo.get_archive_link("tarball", ref=sha)
751
- elif "gitlab" in url:
752
- archive_url = f"{url}/-/archive/{sha}/{file_name}"
753
- else:
754
- raise Exception(f"Only GitHub and GitLab are supported: {url}")
746
+ match repo_info.platform:
747
+ case "github":
748
+ github = self._initiate_github(
749
+ saas_file, base_url="https://api.github.com"
750
+ )
751
+ repo = github.get_repo(repo_name)
752
+ # get_archive_link get redirect url form header, it does not work with github-mirror
753
+ archive_url = repo.get_archive_link("tarball", ref=sha)
754
+ case "gitlab":
755
+ archive_url = f"{url}/-/archive/{sha}/{file_name}"
756
+ case _:
757
+ raise Exception(f"Only GitHub and GitLab are supported: {url}")
755
758
 
756
759
  return file_name, archive_url
757
760
 
@@ -761,18 +764,19 @@ class SaasHerder: # pylint: disable=too-many-public-methods
761
764
  ) -> tuple[Any, str]:
762
765
  commit_sha = self._get_commit_sha(url, ref, github)
763
766
 
764
- if url.startswith(GITHUB_BASE_URL):
765
- repo_name = url.removeprefix(GITHUB_BASE_URL).rstrip("/")
766
- repo = github.get_repo(repo_name)
767
- content = self._get_file_contents_github(repo, path, commit_sha)
768
- elif "gitlab" in url:
769
- if not self.gitlab:
770
- raise Exception("gitlab is not initialized")
771
- project = self.gitlab.get_project(url)
772
- f = project.files.get(file_path=path.lstrip("/"), ref=commit_sha)
773
- content = f.decode()
774
- else:
775
- raise Exception(f"Only GitHub and GitLab are supported: {url}")
767
+ repo_info = VCS.parse_repo_url(url)
768
+ match repo_info.platform:
769
+ case "github":
770
+ repo = github.get_repo(repo_info.name)
771
+ content = self._get_file_contents_github(repo, path, commit_sha)
772
+ case "gitlab":
773
+ if not self.gitlab:
774
+ raise Exception("gitlab is not initialized")
775
+ project = self.gitlab.get_project(url)
776
+ f = project.files.get(file_path=path.lstrip("/"), ref=commit_sha)
777
+ content = f.decode()
778
+ case _:
779
+ raise Exception(f"Only GitHub and GitLab are supported: {url}")
776
780
 
777
781
  return yaml.safe_load(content), commit_sha
778
782
 
@@ -782,52 +786,53 @@ class SaasHerder: # pylint: disable=too-many-public-methods
782
786
  ) -> tuple[list[Any], str]:
783
787
  commit_sha = self._get_commit_sha(url, ref, github)
784
788
  resources: list[Any] = []
785
- if url.startswith(GITHUB_BASE_URL):
786
- repo_name = url.removeprefix(GITHUB_BASE_URL).rstrip("/")
787
- repo = github.get_repo(repo_name)
788
- directory = repo.get_contents(path, commit_sha)
789
- if isinstance(directory, ContentFile):
790
- raise Exception(f"Path {path} and sha {commit_sha} is a file!")
791
- for f in directory:
792
- file_path = os.path.join(path, f.name)
793
- file_contents_decoded = self._get_file_contents_github(
794
- repo, file_path, commit_sha
789
+ repo_info = VCS.parse_repo_url(url)
790
+ match repo_info.platform:
791
+ case "github":
792
+ repo = github.get_repo(repo_info.name)
793
+ directory = repo.get_contents(path, commit_sha)
794
+ if isinstance(directory, ContentFile):
795
+ raise Exception(f"Path {path} and sha {commit_sha} is a file!")
796
+ for f in directory:
797
+ file_path = os.path.join(path, f.name)
798
+ file_contents_decoded = self._get_file_contents_github(
799
+ repo, file_path, commit_sha
800
+ )
801
+ result_resources = yaml.safe_load_all(file_contents_decoded)
802
+ resources.extend(result_resources)
803
+ case "gitlab":
804
+ if not self.gitlab:
805
+ raise Exception("gitlab is not initialized")
806
+ project = self.gitlab.get_project(url)
807
+ dir_contents = self.gitlab.get_directory_contents(
808
+ project,
809
+ ref=commit_sha,
810
+ path=path,
795
811
  )
796
- result_resources = yaml.safe_load_all(file_contents_decoded)
797
- resources.extend(result_resources)
798
- elif "gitlab" in url:
799
- if not self.gitlab:
800
- raise Exception("gitlab is not initialized")
801
- project = self.gitlab.get_project(url)
802
- dir_contents = self.gitlab.get_directory_contents(
803
- project,
804
- ref=commit_sha,
805
- path=path,
806
- )
807
- for content in dir_contents.values():
808
- result_resources = yaml.safe_load_all(content)
809
- resources.extend(result_resources)
810
- else:
811
- raise Exception(f"Only GitHub and GitLab are supported: {url}")
812
+ for content in dir_contents.values():
813
+ result_resources = yaml.safe_load_all(content)
814
+ resources.extend(result_resources)
815
+ case _:
816
+ raise Exception(f"Only GitHub and GitLab are supported: {url}")
812
817
 
813
818
  return resources, commit_sha
814
819
 
815
820
  @retry()
816
821
  def _get_commit_sha(self, url: str, ref: str, github: Github) -> str:
817
- commit_sha = ""
818
- if url.startswith(GITHUB_BASE_URL):
819
- repo_name = url.removeprefix(GITHUB_BASE_URL).rstrip("/")
820
- repo = github.get_repo(repo_name)
821
- commit = repo.get_commit(sha=ref)
822
- commit_sha = commit.sha
823
- elif "gitlab" in url:
824
- if not self.gitlab:
825
- raise Exception("gitlab is not initialized")
826
- project = self.gitlab.get_project(url)
827
- commits = project.commits.list(ref_name=ref, per_page=1, page=1)
828
- commit_sha = commits[0].id
829
-
830
- return commit_sha
822
+ repo_info = VCS.parse_repo_url(url)
823
+ match repo_info.platform:
824
+ case "github":
825
+ repo = github.get_repo(repo_info.name)
826
+ commit = repo.get_commit(sha=ref)
827
+ return commit.sha
828
+ case "gitlab":
829
+ if not self.gitlab:
830
+ raise Exception("gitlab is not initialized")
831
+ project = self.gitlab.get_project(url)
832
+ commits = project.commits.list(ref_name=ref, per_page=1, page=1)
833
+ return commits[0].id
834
+ case _:
835
+ return ""
831
836
 
832
837
  @staticmethod
833
838
  def _additional_resource_process(resources: Resources, html_url: str) -> None:
@@ -186,7 +186,7 @@ from reconcile.utils.password_validator import (
186
186
  )
187
187
  from reconcile.utils.secret_reader import SecretReader, SecretReaderBase
188
188
  from reconcile.utils.terraform import safe_resource_id
189
- from reconcile.utils.vcs import GITHUB_BASE_URL
189
+ from reconcile.utils.vcs import VCS
190
190
 
191
191
  GH_BASE_URL = os.environ.get("GITHUB_API", "https://api.github.com")
192
192
  LOGTOES_RELEASE = "repos/app-sre/logs-to-elasticsearch-lambda/releases/latest"
@@ -5686,20 +5686,20 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
5686
5686
  return ref
5687
5687
 
5688
5688
  # get commit_sha from branch
5689
- if url.startswith(GITHUB_BASE_URL):
5690
- github = self.init_github()
5691
- repo_name = url.removeprefix(GITHUB_BASE_URL).rstrip("/")
5692
- repo = github.get_repo(repo_name)
5693
- commit = repo.get_commit(sha=ref)
5694
- return commit.sha
5695
-
5696
- if "gitlab" in url:
5697
- gitlab = self.init_gitlab()
5698
- project = gitlab.get_project(url)
5699
- commits = project.commits.list(ref_name=ref, per_page=1, page=1)
5700
- return commits[0].id
5701
-
5702
- return ""
5689
+ repo_url_info = VCS.parse_repo_url(url)
5690
+ match repo_url_info.platform:
5691
+ case "github":
5692
+ github = self.init_github()
5693
+ repo = github.get_repo(repo_url_info.name)
5694
+ commit = repo.get_commit(sha=ref)
5695
+ return commit.sha
5696
+ case "gitlab":
5697
+ gitlab = self.init_gitlab()
5698
+ project = gitlab.get_project(url)
5699
+ commits = project.commits.list(ref_name=ref, per_page=1, page=1)
5700
+ return commits[0].id
5701
+ case _:
5702
+ return ""
5703
5703
 
5704
5704
  def get_asg_image_id(
5705
5705
  self, filters: Iterable[Mapping[str, Any]], account: str, region: str
reconcile/utils/vcs.py CHANGED
@@ -6,6 +6,8 @@ from collections.abc import Iterable
6
6
  from dataclasses import dataclass
7
7
  from datetime import datetime
8
8
  from enum import Enum
9
+ from typing import Literal
10
+ from urllib.parse import urlparse
9
11
 
10
12
  from gitlab.v4.objects import ProjectMergeRequest
11
13
 
@@ -24,6 +26,16 @@ from reconcile.utils.secret_reader import (
24
26
 
25
27
  GITHUB_BASE_URL = "https://github.com/"
26
28
 
29
+ Platform = Literal["github", "gitlab"]
30
+
31
+ SUPPORTED_PLATFORMS: list[Platform] = ["github", "gitlab"]
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class RepoInfo:
36
+ platform: Platform | None
37
+ name: str
38
+
27
39
 
28
40
  class MRCheckStatus(Enum):
29
41
  NONE = 0
@@ -155,16 +167,40 @@ class VCS:
155
167
  # Lets assume all other states as non-present
156
168
  return MRCheckStatus.NONE
157
169
 
170
+ @staticmethod
171
+ def parse_repo_url(url: str) -> RepoInfo:
172
+ """
173
+ Parse a repository URL and return a RepoInfo object.
174
+ `platform` can be 'github', 'gitlab' or None if not recognized,
175
+ it's inferred from the URL host.
176
+ `name` is the path part of the URL, stripped of leading and trailing slashes.
177
+ """
178
+ parsed_url = urlparse(url)
179
+ platform = next(
180
+ (
181
+ p
182
+ for p in SUPPORTED_PLATFORMS
183
+ if (hostname := parsed_url.hostname) and p in hostname
184
+ ),
185
+ None,
186
+ )
187
+ name = parsed_url.path.strip("/").removesuffix(".git")
188
+ return RepoInfo(platform=platform, name=name)
189
+
158
190
  def get_commit_sha(
159
191
  self, repo_url: str, ref: str, auth_code: HasSecret | None
160
192
  ) -> str:
161
193
  if bool(self._is_commit_sha_regex.search(ref)):
162
194
  return ref
163
- if repo_url.startswith(GITHUB_BASE_URL):
164
- github = self._init_github(repo_url=repo_url, auth_code=auth_code)
165
- return github.get_commit_sha(ref=ref)
166
- # assume gitlab by default
167
- return self._gitlab_instance.get_commit_sha(ref=ref, repo_url=repo_url)
195
+ repo_info = self.parse_repo_url(repo_url)
196
+ match repo_info.platform:
197
+ case "github":
198
+ github = self._init_github(repo_url=repo_url, auth_code=auth_code)
199
+ return github.get_commit_sha(ref=ref)
200
+ case "gitlab":
201
+ return self._gitlab_instance.get_commit_sha(ref=ref, repo_url=repo_url)
202
+ case _:
203
+ raise ValueError(f"Unsupported repository URL: {repo_url}")
168
204
 
169
205
  def get_commits_between(
170
206
  self,
@@ -177,30 +213,33 @@ class VCS:
177
213
  Return a list of commits between two commits.
178
214
  Note, that the commit_to is included in the result list, whereas commit_from is not included.
179
215
  """
180
- if repo_url.startswith(GITHUB_BASE_URL):
181
- github = self._init_github(repo_url=repo_url, auth_code=auth_code)
182
- data = github.compare(commit_from=commit_from, commit_to=commit_to)
183
- return [
184
- Commit(
185
- repo=repo_url,
186
- sha=gh_commit.sha,
187
- date=gh_commit.commit.committer.date,
216
+ repo_info = self.parse_repo_url(repo_url)
217
+ match repo_info.platform:
218
+ case "github":
219
+ github = self._init_github(repo_url=repo_url, auth_code=auth_code)
220
+ data = github.compare(commit_from=commit_from, commit_to=commit_to)
221
+ return [
222
+ Commit(
223
+ repo=repo_url,
224
+ sha=gh_commit.sha,
225
+ date=gh_commit.commit.committer.date,
226
+ )
227
+ for gh_commit in data
228
+ ]
229
+ case "gitlab":
230
+ data = self._gitlab_instance.repository_compare(
231
+ repo_url=repo_url, ref_from=commit_from, ref_to=commit_to
188
232
  )
189
- for gh_commit in data
190
- ]
191
- # assume gitlab by default
192
- else:
193
- data = self._gitlab_instance.repository_compare(
194
- repo_url=repo_url, ref_from=commit_from, ref_to=commit_to
195
- )
196
- return [
197
- Commit(
198
- repo=repo_url,
199
- sha=gl_commit["id"],
200
- date=datetime.fromisoformat(gl_commit["committed_date"]),
201
- )
202
- for gl_commit in data
203
- ]
233
+ return [
234
+ Commit(
235
+ repo=repo_url,
236
+ sha=gl_commit["id"],
237
+ date=datetime.fromisoformat(gl_commit["committed_date"]),
238
+ )
239
+ for gl_commit in data
240
+ ]
241
+ case _:
242
+ raise ValueError(f"Unsupported repository URL: {repo_url}")
204
243
 
205
244
  def close_app_interface_mr(self, mr: ProjectMergeRequest, comment: str) -> None:
206
245
  if not self._allow_deleting_mrs: