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/>.

16

17 from __future__ import annotations

18

19 from logging import getLogger

20 from typing import TYPE_CHECKING

21 from urllib.parse import quote

22

23 from gitlabracadabra.packages.destination import Destination

24

25 if TYPE_CHECKING: 25 ↛ 26 line 25 didn't jump to line 26 because the condition on line 25 was never true

26 from gitlabracadabra.gitlab.connection import GitlabConnection

27 from gitlabracadabra.packages.package_file import PackageFile

28

29

30 HELM = "helm"

31 PYPI = "pypi"

32

33 logger = getLogger(__name__)

34

35

36 class Gitlab(Destination):

37 """Gitlab repository."""

38

39 def __init__(

40 self,

41 *,

42 connection: GitlabConnection,

43 full_path: str,

44 project_id: int,

45 ) -> None:

46 """Initialize Gitlab repository.

47

48 Args:

49 connection: A Gitlab connection.

50 full_path: Project full path.

51 project_id: Project ID.

52 """

53 super().__init__(log_prefix=f"[{full_path}] ")

54 self._connection = connection

55 self._full_path = full_path

56 self._project_id = project_id

57 self._connection.session_callback(self.session)

58

59 def upload_method(self, package_file: PackageFile) -> str:

60 """Get upload HTTP method.

61

62 Args:

63 package_file: Source package file.

64

65 Returns:

66 The upload method.

67 """

68 if package_file.package_type in {HELM, PYPI}:

69 return "POST"

70

71 return super().upload_method(package_file)

72

73 def head_url(self, package_file: PackageFile) -> str:

74 """Get URL to test existence of destination package file with a HEAD request.

75

76 Args:

77 package_file: Source package file.

78

79 Returns:

80 An URL.

81

82 Raises:

83 NotImplementedError: For unsupported package types.

84 """

85 if package_file.package_type == "raw":

86 return "{}/projects/{}/packages/generic/{}/{}/{}".format(

87 self._connection.api_url,

88 quote(self._full_path, safe=""),

89 quote(package_file.package_name, safe=""), # [A-Za-z0-9\.\_\-\+]+

90 quote(package_file.package_version, safe=""), # (\.?[\w\+-]+\.?)+

91 quote(package_file.file_name, safe=""), # [A-Za-z0-9\.\_\-\+]+

92 )

93 if package_file.package_type == HELM:

94 channel = package_file.metadata.get("channel") or "stable"

95 file_name = f"{package_file.package_name}-{package_file.package_version}.tgz"

96 return "{}/projects/{}/packages/helm/{}/charts/{}".format(

97 self._connection.api_url,

98 quote(self._full_path, safe=""),

99 quote(channel, safe=""),

100 quote(file_name, safe=""),

101 )

102 if package_file.package_type == PYPI: 102 ↛ 110 line 102 didn't jump to line 110 because the condition on line 102 was always true

103 return "{}/projects/{}/packages/pypi/files/{}/{}".format(

104 self._connection.api_url,

105 self._project_id,

106 quote(package_file.metadata.get("sha256", ""), safe=""),

107 quote(package_file.file_name, safe=""),

108 )

109

110 raise NotImplementedError

111

112 def upload_url(self, package_file: PackageFile) -> str:

113 """Get URL to upload to.

114

115 Args:

116 package_file: Source package file.

117

118 Returns:

119 The upload URL.

120 """

121 if package_file.package_type == HELM:

122 channel = package_file.metadata.get("channel") or "stable"

123 return "{}/projects/{}/packages/helm/api/{}/charts".format(

124 self._connection.api_url,

125 quote(self._full_path, safe=""),

126 quote(channel, safe=""),

127 )

128 if package_file.package_type == PYPI:

129 return (

130 "{}/projects/{}/packages/pypi?"

131 "requires_python={}&"

132 "name={}&"

133 "version={}&"

134 "md5_digest={}&"

135 "sha256_digest={}"

136 ).format(

137 self._connection.api_url,

138 self._project_id,

139 quote(package_file.metadata.get("requires-python", ""), safe=""),

140 quote(package_file.package_name, safe=""),

141 quote(package_file.package_version, safe=""),

142 quote(package_file.metadata.get("md5", ""), safe=""),

143 quote(package_file.metadata.get("sha256", ""), safe=""),

144 )

145

146 return super().upload_url(package_file)

147

148 def files_key(self, package_file: PackageFile) -> str | None:

149 """Get files key, to upload to. If None, uploaded as body.

150

151 Args:

152 package_file: Source package file.

153

154 Returns:

155 The files key, or None.

156 """

157 if package_file.package_type == HELM:

158 return "chart"

159 if package_file.package_type == PYPI:

160 return "content"

161

162 return super().files_key(package_file)