Coverage for src/gitlabracadabra/packages/pulp_manifest.py: 83%
77 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-23 06:44 +0200
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-23 06:44 +0200
1#
2# Copyright (C) 2019-2025 Mathieu Parent <math.parent@gmail.com>
3#
4# This program is free software: you can redistribute it and/or modify
5# it under the terms of the GNU Lesser General Public License as published by
6# the Free Software Foundation, either version 3 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# You should have received a copy of the GNU Lesser General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
17from __future__ import annotations
19from logging import getLogger
20from typing import TYPE_CHECKING
21from urllib.parse import urljoin
23from requests import codes
25from gitlabracadabra.packages.package_file import PackageFile
26from gitlabracadabra.packages.source import Source
28if TYPE_CHECKING: 28 ↛ 29line 28 didn't jump to line 29 because the condition on line 28 was never true
29 from requests.models import Response
31 from gitlabracadabra.packages.destination import Destination
33logger = getLogger(__name__)
36class PulpManifestSource(Source):
37 """PULP_MANIFEST repository."""
39 def __init__(
40 self,
41 *,
42 log_prefix: str = "",
43 url: str,
44 package_name: str,
45 package_version: str | None = None,
46 ) -> None:
47 """Initialize a PULP_MANIFEST Source object.
49 Args:
50 log_prefix: Log prefix.
51 url: PULP_MANIFEST URL.
52 package_name: Destination package name.
53 package_version: Destination package version.
54 """
55 super().__init__()
56 self._log_prefix = log_prefix
57 self._url = url
58 self._package_name = package_name
59 self._package_version = package_version or "0"
61 def __str__(self) -> str:
62 """Return string representation.
64 Returns:
65 A string.
66 """
67 return f"PULP_MANIFEST repository (url={self._url})"
69 def package_files(
70 self,
71 destination: Destination,
72 ) -> list[PackageFile]:
73 """Return list of package files.
75 Returns:
76 List of package files.
77 """
78 pulp_manifest = self._package_file(None)
79 pulp_manifest.force = True
80 destination_pulp_manifest_url = destination.get_url(pulp_manifest)
81 destination_pulp_manifest_response = destination.session.request(
82 "GET", destination_pulp_manifest_url, stream=True
83 )
84 current_pulp_manifest_files: list[PackageFile] = []
85 if destination_pulp_manifest_response.status_code == codes["ok"]: 85 ↛ 87line 85 didn't jump to line 87 because the condition on line 85 was always true
86 current_pulp_manifest_files = self._pulp_manifest_files(destination_pulp_manifest_response)
87 elif destination_pulp_manifest_response.status_code != codes["not_found"]:
88 logger.warning(
89 '%sUnexpected HTTP status for %s package file "%s" from "%s" version %s (%s): received %i %s with GET method on destination',
90 self._log_prefix,
91 pulp_manifest.package_type,
92 pulp_manifest.file_name,
93 pulp_manifest.package_name,
94 pulp_manifest.package_version,
95 destination_pulp_manifest_url,
96 destination_pulp_manifest_response.status_code,
97 destination_pulp_manifest_response.reason,
98 )
99 return []
101 source_pulp_manifest_response = self.session.request("GET", self._url, stream=True)
102 if source_pulp_manifest_response.status_code != codes["ok"]: 102 ↛ 103line 102 didn't jump to line 103 because the condition on line 102 was never true
103 logger.warning(
104 "%sUnexpected HTTP status for PULP_MANIFEST url %s: received %i %s",
105 self._log_prefix,
106 self._url,
107 source_pulp_manifest_response.status_code,
108 source_pulp_manifest_response.reason,
109 )
110 return []
111 target_pulp_manifest_files = self._pulp_manifest_files(source_pulp_manifest_response)
112 package_files: list[PackageFile] = []
113 for package_file in target_pulp_manifest_files:
114 if package_file not in current_pulp_manifest_files:
115 package_file.force = True
116 package_files.append(package_file)
117 package_files.append(pulp_manifest)
118 for package_file in current_pulp_manifest_files:
119 if package_file not in target_pulp_manifest_files:
120 package_file.delete = True
121 package_file.force = True
122 package_files.append(package_file)
123 if len(package_files) > 1: 123 ↛ 130line 123 didn't jump to line 130 because the condition on line 123 was always true
124 old_pulp_manifest = self._package_file(None)
125 old_pulp_manifest.delete = True
126 old_pulp_manifest.force = True
127 package_files.append(old_pulp_manifest)
128 destination.cache_project_package_package_files("generic", self._package_name, self._package_version)
129 else:
130 package_files.remove(pulp_manifest)
131 return package_files
133 def _pulp_manifest_files(self, pulp_manifest_response: Response) -> list[PackageFile]:
134 if pulp_manifest_response.encoding is None:
135 pulp_manifest_response.encoding = "utf-8"
136 package_files: list[PackageFile] = []
137 for pulp_manifest_line in pulp_manifest_response.iter_lines(decode_unicode=True):
138 try:
139 filename, sha256_checksum, size_in_bytes = pulp_manifest_line.split(",", 3)
140 except ValueError:
141 logger.warning(
142 "%sInvalid PULP_MANIFEST line: %s",
143 self._log_prefix,
144 pulp_manifest_line,
145 )
146 continue
147 package_file = self._package_file(filename, sha256_checksum, size_in_bytes)
148 if package_file: 148 ↛ 137line 148 didn't jump to line 137 because the condition on line 148 was always true
149 package_files.append(package_file)
150 return package_files
152 def _package_file(
153 self, filename: str | None, sha256_checksum: str | None = None, size_in_bytes: str | None = None
154 ) -> PackageFile:
155 url = urljoin(self._url, filename, allow_fragments=False)
156 package_file = PackageFile(
157 url,
158 "generic",
159 self._package_name,
160 self._package_version,
161 url.split("/").pop(),
162 )
163 if sha256_checksum is not None:
164 package_file.metadata["sha256_checksum"] = sha256_checksum
165 if size_in_bytes is not None:
166 package_file.metadata["size_in_bytes"] = size_in_bytes
167 return package_file