glpkg 1.4.2__py3-none-any.whl → 1.5.0__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.
- gitlab/__init__.py +1 -1
- gitlab/cli_handler.py +75 -17
- gitlab/lib/gitlab_api.py +248 -0
- gitlab/lib/http.py +90 -0
- gitlab/packages.py +62 -325
- {glpkg-1.4.2.dist-info → glpkg-1.5.0.dist-info}/METADATA +25 -2
- glpkg-1.5.0.dist-info/RECORD +12 -0
- glpkg-1.4.2.dist-info/RECORD +0 -10
- {glpkg-1.4.2.dist-info → glpkg-1.5.0.dist-info}/WHEEL +0 -0
- {glpkg-1.4.2.dist-info → glpkg-1.5.0.dist-info}/entry_points.txt +0 -0
- {glpkg-1.4.2.dist-info → glpkg-1.5.0.dist-info}/licenses/LICENSE.md +0 -0
- {glpkg-1.4.2.dist-info → glpkg-1.5.0.dist-info}/top_level.txt +0 -0
gitlab/__init__.py
CHANGED
gitlab/cli_handler.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import argparse
|
|
4
4
|
from netrc import netrc
|
|
5
5
|
import os
|
|
6
|
+
import re
|
|
6
7
|
import sys
|
|
7
8
|
import urllib
|
|
8
9
|
from gitlab import Packages, __version__
|
|
@@ -72,6 +73,9 @@ class CLIHandler:
|
|
|
72
73
|
file=sys.stderr,
|
|
73
74
|
)
|
|
74
75
|
print("Check your arguments and credentials.", file=sys.stderr)
|
|
76
|
+
except ValueError as e:
|
|
77
|
+
print("Whopsie! Something did go wrong.", file=sys.stderr)
|
|
78
|
+
print(e, file=sys.stderr)
|
|
75
79
|
return ret
|
|
76
80
|
|
|
77
81
|
def _register_common_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
@@ -89,7 +93,7 @@ class CLIHandler:
|
|
|
89
93
|
parser:
|
|
90
94
|
The argparser where to register the common arguments.
|
|
91
95
|
"""
|
|
92
|
-
group = parser.
|
|
96
|
+
group = parser.add_argument_group()
|
|
93
97
|
group.add_argument(
|
|
94
98
|
"-H",
|
|
95
99
|
"--host",
|
|
@@ -148,6 +152,12 @@ class CLIHandler:
|
|
|
148
152
|
"""
|
|
149
153
|
self._register_common_arguments(parser)
|
|
150
154
|
parser.add_argument("-v", "--version", type=str, help="The package version.")
|
|
155
|
+
parser.add_argument(
|
|
156
|
+
"--from-file",
|
|
157
|
+
type=str,
|
|
158
|
+
help="Download all files from packages listed in the file. Each line in the "
|
|
159
|
+
"file should follow format <package-name>==<package-version>@<project>.",
|
|
160
|
+
)
|
|
151
161
|
parser.add_argument(
|
|
152
162
|
"-f",
|
|
153
163
|
"--file",
|
|
@@ -195,6 +205,8 @@ class CLIHandler:
|
|
|
195
205
|
project = os.environ["CI_PROJECT_ID"]
|
|
196
206
|
token = os.environ["CI_JOB_TOKEN"]
|
|
197
207
|
token_user = "JOB-TOKEN"
|
|
208
|
+
if args.host:
|
|
209
|
+
host = args.host
|
|
198
210
|
if args.project:
|
|
199
211
|
project = args.project
|
|
200
212
|
if args.token:
|
|
@@ -229,23 +241,67 @@ class CLIHandler:
|
|
|
229
241
|
host, project, name, token_user, token = self._args(args)
|
|
230
242
|
version = args.version
|
|
231
243
|
destination = args.destination
|
|
244
|
+
file = args.file
|
|
245
|
+
from_file = args.from_file
|
|
232
246
|
packages = Packages(host, token_user, token)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
files = packages.get_files(project, package_id).keys()
|
|
240
|
-
for file in files:
|
|
241
|
-
ret = packages.download_file(project, name, version, file, destination)
|
|
247
|
+
if from_file:
|
|
248
|
+
package_list = self._read_from_file(from_file)
|
|
249
|
+
for package in package_list:
|
|
250
|
+
ret = packages.download(
|
|
251
|
+
package["project"], package["name"], package["version"], destination
|
|
252
|
+
)
|
|
242
253
|
if ret:
|
|
243
|
-
print("Failed to download file " + file)
|
|
244
254
|
break
|
|
255
|
+
elif file:
|
|
256
|
+
ret = packages.download_file(project, name, version, file, destination)
|
|
245
257
|
else:
|
|
246
|
-
|
|
258
|
+
ret = packages.download(project, name, version, destination)
|
|
259
|
+
if ret:
|
|
260
|
+
if ret == 2:
|
|
261
|
+
print("No package " + name + " version " + version + " found!")
|
|
262
|
+
else:
|
|
263
|
+
print("Failed to download file(s)!")
|
|
247
264
|
return ret
|
|
248
265
|
|
|
266
|
+
def _read_from_file(self, filepath: str) -> list[dict]:
|
|
267
|
+
"""
|
|
268
|
+
Reads packages file and returns a list of packages to be downloaded.
|
|
269
|
+
|
|
270
|
+
The lines in the file should be
|
|
271
|
+
<packagename>==<version>@<project>
|
|
272
|
+
|
|
273
|
+
Parameters
|
|
274
|
+
----------
|
|
275
|
+
filepath : str
|
|
276
|
+
Filepath of the packages file
|
|
277
|
+
|
|
278
|
+
Returns
|
|
279
|
+
-------
|
|
280
|
+
int
|
|
281
|
+
Zero if everything goes well, non-zero otherwise
|
|
282
|
+
"""
|
|
283
|
+
packages = []
|
|
284
|
+
pattern = (
|
|
285
|
+
r"\s*(?P<packagename>\S+)\s*=="
|
|
286
|
+
r"\s*(?P<packageversion>\S+)\s*@"
|
|
287
|
+
r"\s*(?P<project>\S+)\s*"
|
|
288
|
+
)
|
|
289
|
+
with open(filepath, "r", encoding="utf-8") as file:
|
|
290
|
+
for line in file:
|
|
291
|
+
if not line:
|
|
292
|
+
continue
|
|
293
|
+
match = re.match(pattern, line)
|
|
294
|
+
if not match:
|
|
295
|
+
raise ValueError(
|
|
296
|
+
f"Package file {filepath} line {line} seems fishy! \
|
|
297
|
+
Check that the line is <packagename>==<version>@<project>"
|
|
298
|
+
)
|
|
299
|
+
name = match.group("packagename")
|
|
300
|
+
version = match.group("packageversion")
|
|
301
|
+
project = match.group("project")
|
|
302
|
+
packages.append({"name": name, "version": version, "project": project})
|
|
303
|
+
return packages
|
|
304
|
+
|
|
249
305
|
def _register_list_parser(self, parser: argparse.ArgumentParser):
|
|
250
306
|
"""
|
|
251
307
|
Registers the list command related arguments to the parser:
|
|
@@ -334,19 +390,21 @@ class CLIHandler:
|
|
|
334
390
|
int
|
|
335
391
|
Zero if everything went fine, non-zero otherwise.
|
|
336
392
|
"""
|
|
337
|
-
ret =
|
|
393
|
+
ret = 1
|
|
338
394
|
host, project, name, token_user, token = self._args(args)
|
|
339
395
|
version = args.version
|
|
340
396
|
file = args.file
|
|
341
397
|
source = args.source
|
|
398
|
+
packages = Packages(host, token_user, token)
|
|
342
399
|
if file:
|
|
343
400
|
# Check if the uploaded file exists.
|
|
344
401
|
if not os.path.isfile(os.path.join(source, file)):
|
|
345
402
|
print("File " + file + " does not exist!")
|
|
346
403
|
ret = 1
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
404
|
+
else:
|
|
405
|
+
ret = packages.upload_file(project, name, version, file, source)
|
|
406
|
+
else:
|
|
407
|
+
ret = packages.upload(project, name, version, source)
|
|
350
408
|
return ret
|
|
351
409
|
|
|
352
410
|
def _register_delete_parser(self, parser: argparse.ArgumentParser):
|
|
@@ -397,5 +455,5 @@ class CLIHandler:
|
|
|
397
455
|
if file:
|
|
398
456
|
ret = packages.delete_file(project, name, version, file)
|
|
399
457
|
else:
|
|
400
|
-
ret = packages.
|
|
458
|
+
ret = packages.delete(project, name, version)
|
|
401
459
|
return ret
|
gitlab/lib/gitlab_api.py
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""GitLab basic API"""
|
|
2
|
+
|
|
3
|
+
from http.client import HTTPMessage
|
|
4
|
+
from json import loads
|
|
5
|
+
import logging
|
|
6
|
+
from urllib import parse
|
|
7
|
+
from gitlab.lib import http
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GitLabAPI:
|
|
13
|
+
"""Class for basic GitLab API"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, host: str, token_type: str, token: str):
|
|
16
|
+
"""
|
|
17
|
+
Creates a new instance of class.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
host : str
|
|
22
|
+
The GitLab instance hostname, without schema.
|
|
23
|
+
The host will be used for the package API interaction.
|
|
24
|
+
For example gitlab.com.
|
|
25
|
+
token_type : str
|
|
26
|
+
The token type or "user" to authenticate with GitLab REST API.
|
|
27
|
+
For personal, project, and group tokens this is `PRIVATE-TOKEN`.
|
|
28
|
+
For `CI_JOB_TOKEN` this is `JOB-TOKEN`.
|
|
29
|
+
Can be left empty when authentication is not used.
|
|
30
|
+
token : str
|
|
31
|
+
The token (secret) to authenticate with GitLab REST API.
|
|
32
|
+
This can be a personal token, project token, or`CI_JOB_TOKEN`.
|
|
33
|
+
Leave empty when authentication is not used.
|
|
34
|
+
"""
|
|
35
|
+
self.host = host
|
|
36
|
+
self.token_type = token_type
|
|
37
|
+
self.token = token
|
|
38
|
+
|
|
39
|
+
def _url(self) -> str:
|
|
40
|
+
"""
|
|
41
|
+
Returns the GitLab REST API URL by using the host variable.
|
|
42
|
+
|
|
43
|
+
Returns
|
|
44
|
+
-------
|
|
45
|
+
str
|
|
46
|
+
The GitLab REST API URL, for example `https://gitlab.com/api/v4/`.
|
|
47
|
+
"""
|
|
48
|
+
return f"https://{self.host}/api/v4/"
|
|
49
|
+
|
|
50
|
+
def _get_headers(self) -> dict:
|
|
51
|
+
"""
|
|
52
|
+
Creates headers for a GitLab REST API call.
|
|
53
|
+
|
|
54
|
+
The headers contain token for authentication according to the
|
|
55
|
+
instance variables.
|
|
56
|
+
|
|
57
|
+
Returns
|
|
58
|
+
-------
|
|
59
|
+
dict
|
|
60
|
+
Headers for a REST API request, that contain the authentication token.
|
|
61
|
+
"""
|
|
62
|
+
headers = {}
|
|
63
|
+
if self.token_type and self.token:
|
|
64
|
+
headers = {self.token_type: self.token}
|
|
65
|
+
return headers
|
|
66
|
+
|
|
67
|
+
def _parse_header_links(self, headers: HTTPMessage) -> dict:
|
|
68
|
+
"""
|
|
69
|
+
Parses link field from HTTP headers to a dictionary where
|
|
70
|
+
the link "rel" value is the key.
|
|
71
|
+
|
|
72
|
+
This is useful for example with GitLab REST API to get the pagination links.
|
|
73
|
+
|
|
74
|
+
Parameters
|
|
75
|
+
----------
|
|
76
|
+
headers : HTTPMessage
|
|
77
|
+
The HTTP response headers that contain the links
|
|
78
|
+
|
|
79
|
+
Returns
|
|
80
|
+
-------
|
|
81
|
+
dict
|
|
82
|
+
The header links in a dictionary, that can be used to for example pagination:
|
|
83
|
+
_parse_header_links(headers).get("next") returns the next page link, or None.
|
|
84
|
+
"""
|
|
85
|
+
links = {}
|
|
86
|
+
header_links = headers.get("link")
|
|
87
|
+
if header_links:
|
|
88
|
+
items = header_links.split(",")
|
|
89
|
+
# Values are <uri-reference>; param1=value1; param2="value2"
|
|
90
|
+
for item in items:
|
|
91
|
+
parts = item.split(";", 1)
|
|
92
|
+
if parts:
|
|
93
|
+
# First value should be the URI
|
|
94
|
+
val = parts.pop(0).lstrip(" <").rstrip("> ")
|
|
95
|
+
# The rest are the parameters; let's take the first one that has rel=
|
|
96
|
+
# in it, split it with ; and take the first part as a value
|
|
97
|
+
typ = parts.pop()
|
|
98
|
+
typ = typ.split("rel=", 1).pop().split(";").pop(0).strip('" ')
|
|
99
|
+
links[typ] = val
|
|
100
|
+
return links
|
|
101
|
+
|
|
102
|
+
def _build_query(self, **args: str) -> str:
|
|
103
|
+
"""
|
|
104
|
+
Builds a query for a GitLab REST API request
|
|
105
|
+
|
|
106
|
+
Parameters
|
|
107
|
+
----------
|
|
108
|
+
args : str
|
|
109
|
+
keyword arguments for the REST API query, like per_page=20, page=20
|
|
110
|
+
|
|
111
|
+
Returns
|
|
112
|
+
-------
|
|
113
|
+
str
|
|
114
|
+
A query string for a REST API request. Append this to
|
|
115
|
+
the request URL. Example `?per_page=20&page=20`.
|
|
116
|
+
"""
|
|
117
|
+
query = ""
|
|
118
|
+
for key, value in args.items():
|
|
119
|
+
query = "&".join(filter(None, (query, f"{key}={parse.quote_plus(value)}")))
|
|
120
|
+
if query:
|
|
121
|
+
query = "?" + query
|
|
122
|
+
return query
|
|
123
|
+
|
|
124
|
+
def _build_path(self, *paths: str) -> str:
|
|
125
|
+
"""
|
|
126
|
+
Returns the path to be appended to the REST API URL
|
|
127
|
+
|
|
128
|
+
Parameters
|
|
129
|
+
----------
|
|
130
|
+
paths : str
|
|
131
|
+
The path string that are joined with "/".
|
|
132
|
+
|
|
133
|
+
Returns
|
|
134
|
+
-------
|
|
135
|
+
str
|
|
136
|
+
A URL path, for example `projects/123/`
|
|
137
|
+
or `projects/namespace%2Fproject/`
|
|
138
|
+
"""
|
|
139
|
+
quoted_paths = []
|
|
140
|
+
for subpath in paths:
|
|
141
|
+
quoted_paths.append(subpath)
|
|
142
|
+
return "/".join(quoted_paths)
|
|
143
|
+
|
|
144
|
+
def get(self, *paths: str, **query_params: str) -> tuple[int, bytes, HTTPMessage]:
|
|
145
|
+
"""
|
|
146
|
+
Makes a HTTP GET request to the given GitLab path, and returns
|
|
147
|
+
the response status, body, and headers.
|
|
148
|
+
|
|
149
|
+
Parameters
|
|
150
|
+
----------
|
|
151
|
+
paths : str
|
|
152
|
+
The URL path of the HTTP request to make.
|
|
153
|
+
query_params : str, optional
|
|
154
|
+
Dictionary of query parameters for the request, like package_name=mypackage
|
|
155
|
+
|
|
156
|
+
Returns
|
|
157
|
+
-------
|
|
158
|
+
int
|
|
159
|
+
The HTTP response code, such as 200
|
|
160
|
+
bytes
|
|
161
|
+
The HTTP response body read as bytes
|
|
162
|
+
HTTPMessage
|
|
163
|
+
The HTTP response headers
|
|
164
|
+
"""
|
|
165
|
+
url = self._url() + self._build_path(*paths) + self._build_query(**query_params)
|
|
166
|
+
headers = self._get_headers()
|
|
167
|
+
return http.get(url, headers)
|
|
168
|
+
|
|
169
|
+
def get_all(self, *paths: str, **query_params: str) -> list:
|
|
170
|
+
"""
|
|
171
|
+
Returns data from the REST API endpoint. In case
|
|
172
|
+
of multiple pages, all data will be returned.
|
|
173
|
+
|
|
174
|
+
Parameters
|
|
175
|
+
----------
|
|
176
|
+
paths : str
|
|
177
|
+
The paths of the API endpoint that is called. For example projects, 123, packages
|
|
178
|
+
would be querying the projects/123/packages endpoint.
|
|
179
|
+
query_params : str, optional
|
|
180
|
+
Additional arguments for the query of the URL, for example to filter
|
|
181
|
+
results: package_name=mypackage
|
|
182
|
+
|
|
183
|
+
Returns
|
|
184
|
+
-------
|
|
185
|
+
list
|
|
186
|
+
Data from GitLab REST API endpoint with the arguments.
|
|
187
|
+
"""
|
|
188
|
+
url = self._url() + self._build_path(*paths) + self._build_query(**query_params)
|
|
189
|
+
data = []
|
|
190
|
+
while url:
|
|
191
|
+
res_status, res_data, res_headers = http.get(url, self._get_headers())
|
|
192
|
+
logger.debug("Response status: %d", res_status)
|
|
193
|
+
res_data = loads(res_data)
|
|
194
|
+
logger.debug("Response data: %s", res_data)
|
|
195
|
+
data = data + res_data
|
|
196
|
+
url = self._parse_header_links(res_headers).get("next")
|
|
197
|
+
return data
|
|
198
|
+
|
|
199
|
+
def put(self, data: bytes, *paths: str, **query_params: str) -> int:
|
|
200
|
+
"""
|
|
201
|
+
Makes a HTTP PUT request to the given GitLab path.
|
|
202
|
+
|
|
203
|
+
Parameters
|
|
204
|
+
----------
|
|
205
|
+
data : bytes
|
|
206
|
+
The data to PUT.
|
|
207
|
+
paths : str
|
|
208
|
+
The URL path of the HTTP request to make.
|
|
209
|
+
query_params : str, optional
|
|
210
|
+
Additional arguments for the query of the URL, for example to filter
|
|
211
|
+
results: package_name=mypackage
|
|
212
|
+
|
|
213
|
+
Returns
|
|
214
|
+
-------
|
|
215
|
+
int
|
|
216
|
+
0 If the request was successful.
|
|
217
|
+
"""
|
|
218
|
+
ret = 1
|
|
219
|
+
url = self._url() + self._build_path(*paths) + self._build_query(**query_params)
|
|
220
|
+
status, _, _ = http.put(url, data, self._get_headers())
|
|
221
|
+
if status == 201: # 201 is created
|
|
222
|
+
ret = 0
|
|
223
|
+
return ret
|
|
224
|
+
|
|
225
|
+
def delete(self, *paths: str, **query_params: str) -> int:
|
|
226
|
+
"""
|
|
227
|
+
Makes a HTTP DELETE request to the given GitLab path.
|
|
228
|
+
|
|
229
|
+
Parameters
|
|
230
|
+
----------
|
|
231
|
+
paths : str
|
|
232
|
+
The URL path of the HTTP request to make.
|
|
233
|
+
query_params : str, optional
|
|
234
|
+
Additional arguments for the query of the URL, for example to filter
|
|
235
|
+
results: package_name=mypackage
|
|
236
|
+
|
|
237
|
+
Returns
|
|
238
|
+
-------
|
|
239
|
+
int
|
|
240
|
+
0 If the request was successful.
|
|
241
|
+
"""
|
|
242
|
+
ret = 1
|
|
243
|
+
url = self._url() + self._build_path(*paths) + self._build_query(**query_params)
|
|
244
|
+
status, _, _ = http.delete(url, self._get_headers())
|
|
245
|
+
if status == 204:
|
|
246
|
+
# 204 is no content, that GL responds when file deleted
|
|
247
|
+
ret = 0
|
|
248
|
+
return ret
|
gitlab/lib/http.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""HTTP helper"""
|
|
2
|
+
|
|
3
|
+
from http.client import HTTPMessage
|
|
4
|
+
import logging
|
|
5
|
+
from urllib import request
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get(url: str, headers: dict) -> tuple[int, bytes, HTTPMessage]:
|
|
11
|
+
"""
|
|
12
|
+
Makes a raw GET request to the given URL, and returns
|
|
13
|
+
the response status, body, and headers.
|
|
14
|
+
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
url : str
|
|
18
|
+
The URL of the HTTP request to make.
|
|
19
|
+
headers: dict
|
|
20
|
+
The HTTP headers used in the request.
|
|
21
|
+
|
|
22
|
+
Returns
|
|
23
|
+
-------
|
|
24
|
+
int
|
|
25
|
+
The HTTP response code, such as 200
|
|
26
|
+
bytes
|
|
27
|
+
The HTTP response body read as bytes
|
|
28
|
+
HTTPMessage
|
|
29
|
+
The HTTP response headers
|
|
30
|
+
"""
|
|
31
|
+
logger.debug("Getting %s", url)
|
|
32
|
+
req = request.Request(url, headers=headers)
|
|
33
|
+
with request.urlopen(req) as response:
|
|
34
|
+
return response.status, response.read(), response.headers
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def put(url: str, data: bytes, headers: dict) -> tuple[int, bytes, HTTPMessage]:
|
|
38
|
+
"""
|
|
39
|
+
Makes a raw PUT request to the given URL, and returns
|
|
40
|
+
the response status, body, and headers.
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
url : str
|
|
45
|
+
The URL of the HTTP request to make.
|
|
46
|
+
data : bytes
|
|
47
|
+
The data to PUT
|
|
48
|
+
headers: dict
|
|
49
|
+
The HTTP headers used in the request.
|
|
50
|
+
|
|
51
|
+
Returns
|
|
52
|
+
-------
|
|
53
|
+
int
|
|
54
|
+
The HTTP response code, such as 200
|
|
55
|
+
bytes
|
|
56
|
+
The HTTP response body read as bytes
|
|
57
|
+
HTTPMessage
|
|
58
|
+
The HTTP response headers
|
|
59
|
+
"""
|
|
60
|
+
logger.debug("Putting %s", url)
|
|
61
|
+
req = request.Request(url, method="PUT", data=data, headers=headers)
|
|
62
|
+
with request.urlopen(req) as response:
|
|
63
|
+
return response.status, response.read(), response.headers
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def delete(url, headers):
|
|
67
|
+
"""
|
|
68
|
+
Makes a raw DELETE request to the given URL, and returns
|
|
69
|
+
the response status, body, and headers.
|
|
70
|
+
|
|
71
|
+
Parameters
|
|
72
|
+
----------
|
|
73
|
+
url : str
|
|
74
|
+
The URL of the HTTP request to make.
|
|
75
|
+
headers: dict
|
|
76
|
+
The HTTP headers used in the request.
|
|
77
|
+
|
|
78
|
+
Returns
|
|
79
|
+
-------
|
|
80
|
+
int
|
|
81
|
+
The HTTP response code, such as 200
|
|
82
|
+
bytes
|
|
83
|
+
The HTTP response body read as bytes
|
|
84
|
+
HTTPMessage
|
|
85
|
+
The HTTP response headers
|
|
86
|
+
"""
|
|
87
|
+
logger.debug("Deleting %s", url)
|
|
88
|
+
req = request.Request(url, method="DELETE", headers=headers)
|
|
89
|
+
with request.urlopen(req) as response:
|
|
90
|
+
return response.status, response.read(), response.headers
|
gitlab/packages.py
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
"""GitLab generic packages module"""
|
|
2
2
|
|
|
3
3
|
from glob import glob
|
|
4
|
-
from http.client import HTTPMessage
|
|
5
|
-
from json import loads
|
|
6
4
|
import logging
|
|
7
5
|
import os
|
|
8
|
-
from urllib import
|
|
6
|
+
from urllib import parse
|
|
7
|
+
from gitlab.lib.gitlab_api import GitLabAPI
|
|
9
8
|
|
|
10
9
|
logger = logging.getLogger(__name__)
|
|
11
10
|
|
|
@@ -33,302 +32,7 @@ class Packages:
|
|
|
33
32
|
This can be a personal token, project token, or`CI_JOB_TOKEN`.
|
|
34
33
|
Leave empty when authentication is not used.
|
|
35
34
|
"""
|
|
36
|
-
self.
|
|
37
|
-
self.token_type = token_type
|
|
38
|
-
self.token = token
|
|
39
|
-
|
|
40
|
-
def _url(self) -> str:
|
|
41
|
-
"""
|
|
42
|
-
Returns the GitLab REST API URL by using the host variable.
|
|
43
|
-
|
|
44
|
-
Returns
|
|
45
|
-
-------
|
|
46
|
-
str
|
|
47
|
-
The GitLab REST API URL, for example `https://gitlab.com/api/v4/`.
|
|
48
|
-
"""
|
|
49
|
-
return f"https://{self.host}/api/v4/"
|
|
50
|
-
|
|
51
|
-
def _get_headers(self) -> dict:
|
|
52
|
-
"""
|
|
53
|
-
Creates headers for a GitLab REST API call.
|
|
54
|
-
|
|
55
|
-
The headers contain token for authentication according to the
|
|
56
|
-
instance variables.
|
|
57
|
-
|
|
58
|
-
Returns
|
|
59
|
-
-------
|
|
60
|
-
dict
|
|
61
|
-
Headers for a REST API request, that contain the authentication token.
|
|
62
|
-
"""
|
|
63
|
-
headers = {}
|
|
64
|
-
if self.token_type and self.token:
|
|
65
|
-
headers = {self.token_type: self.token}
|
|
66
|
-
return headers
|
|
67
|
-
|
|
68
|
-
def _parse_header_links(self, headers: HTTPMessage) -> dict:
|
|
69
|
-
"""
|
|
70
|
-
Parses link field from HTTP headers to a dictionary where
|
|
71
|
-
the link "rel" value is the key.
|
|
72
|
-
|
|
73
|
-
This is useful for example with GitLab REST API to get the pagination links.
|
|
74
|
-
|
|
75
|
-
Parameters
|
|
76
|
-
----------
|
|
77
|
-
headers : HTTPMessage
|
|
78
|
-
The HTTP response headers that contain the links
|
|
79
|
-
|
|
80
|
-
Returns
|
|
81
|
-
-------
|
|
82
|
-
dict
|
|
83
|
-
The header links in a dictionary, that can be used to for example pagination:
|
|
84
|
-
_parse_header_links(headers).get("next") returns the next page link, or None.
|
|
85
|
-
"""
|
|
86
|
-
links = {}
|
|
87
|
-
header_links = headers.get("link")
|
|
88
|
-
if header_links:
|
|
89
|
-
items = header_links.split(",")
|
|
90
|
-
# Values are <uri-reference>; param1=value1; param2="value2"
|
|
91
|
-
for item in items:
|
|
92
|
-
parts = item.split(";", 1)
|
|
93
|
-
if parts:
|
|
94
|
-
# First value should be the URI
|
|
95
|
-
val = parts.pop(0).lstrip(" <").rstrip("> ")
|
|
96
|
-
# The rest are the parameters; let's take the first one that has rel=
|
|
97
|
-
# in it, split it with ; and take the first part as a value
|
|
98
|
-
typ = parts.pop()
|
|
99
|
-
typ = typ.split("rel=", 1).pop().split(";").pop(0).strip('" ')
|
|
100
|
-
links[typ] = val
|
|
101
|
-
return links
|
|
102
|
-
|
|
103
|
-
def _build_path(self, *paths: str) -> str:
|
|
104
|
-
"""
|
|
105
|
-
Returns the path to be appended to the REST API URL
|
|
106
|
-
|
|
107
|
-
Parameters
|
|
108
|
-
----------
|
|
109
|
-
paths : str
|
|
110
|
-
The path string that are joined with "/".
|
|
111
|
-
|
|
112
|
-
Returns
|
|
113
|
-
-------
|
|
114
|
-
str
|
|
115
|
-
A URL path, for example `projects/123/`
|
|
116
|
-
or `projects/namespace%2Fproject/`
|
|
117
|
-
"""
|
|
118
|
-
quoted_paths = []
|
|
119
|
-
for subpath in paths:
|
|
120
|
-
quoted_paths.append(subpath)
|
|
121
|
-
return "/".join(quoted_paths)
|
|
122
|
-
|
|
123
|
-
def build_query(self, **args: str) -> str:
|
|
124
|
-
"""
|
|
125
|
-
Builds a query for a GitLab REST API request
|
|
126
|
-
|
|
127
|
-
Parameters
|
|
128
|
-
----------
|
|
129
|
-
args : str
|
|
130
|
-
keyword arguments for the REST API query, like per_page=20, page=20
|
|
131
|
-
|
|
132
|
-
Returns
|
|
133
|
-
-------
|
|
134
|
-
str
|
|
135
|
-
A query string for a REST API request. Append this to
|
|
136
|
-
the request URL. Example `?per_page=20&page=20`.
|
|
137
|
-
"""
|
|
138
|
-
query = ""
|
|
139
|
-
for key, value in args.items():
|
|
140
|
-
query = "&".join(filter(None, (query, f"{key}={parse.quote_plus(value)}")))
|
|
141
|
-
if query:
|
|
142
|
-
query = "?" + query
|
|
143
|
-
return query
|
|
144
|
-
|
|
145
|
-
def _get(self, url: str, headers: dict) -> tuple[int, bytes, HTTPMessage]:
|
|
146
|
-
"""
|
|
147
|
-
Makes a raw GET request to the given URL, and returns
|
|
148
|
-
the response status, body, and headers.
|
|
149
|
-
|
|
150
|
-
Parameters
|
|
151
|
-
----------
|
|
152
|
-
url : str
|
|
153
|
-
The URL of the HTTP request to make.
|
|
154
|
-
headers: dict
|
|
155
|
-
The HTTP headers used in the request.
|
|
156
|
-
|
|
157
|
-
Returns
|
|
158
|
-
-------
|
|
159
|
-
int
|
|
160
|
-
The HTTP response code, such as 200
|
|
161
|
-
bytes
|
|
162
|
-
The HTTP response body read as bytes
|
|
163
|
-
HTTPMessage
|
|
164
|
-
The HTTP response headers
|
|
165
|
-
"""
|
|
166
|
-
logger.debug("Getting %s", url)
|
|
167
|
-
req = request.Request(url, headers=headers)
|
|
168
|
-
with request.urlopen(req) as response:
|
|
169
|
-
return response.status, response.read(), response.headers
|
|
170
|
-
|
|
171
|
-
def get(self, *paths: str, **query_params: str) -> tuple[int, bytes, HTTPMessage]:
|
|
172
|
-
"""
|
|
173
|
-
Makes a HTTP GET request to the given GitLab path, and returns
|
|
174
|
-
the response status, body, and headers.
|
|
175
|
-
|
|
176
|
-
Parameters
|
|
177
|
-
----------
|
|
178
|
-
paths : str
|
|
179
|
-
The URL path of the HTTP request to make.
|
|
180
|
-
query_params : str, optional
|
|
181
|
-
Dictionary of query parameters for the request, like package_name=mypackage
|
|
182
|
-
|
|
183
|
-
Returns
|
|
184
|
-
-------
|
|
185
|
-
int
|
|
186
|
-
The HTTP response code, such as 200
|
|
187
|
-
bytes
|
|
188
|
-
The HTTP response body read as bytes
|
|
189
|
-
HTTPMessage
|
|
190
|
-
The HTTP response headers
|
|
191
|
-
"""
|
|
192
|
-
url = self._url() + self._build_path(*paths) + self.build_query(**query_params)
|
|
193
|
-
headers = self._get_headers()
|
|
194
|
-
return self._get(url, headers)
|
|
195
|
-
|
|
196
|
-
def get_all(self, *paths: str, **query_params: str) -> list:
|
|
197
|
-
"""
|
|
198
|
-
Returns data from the REST API endpoint. In case
|
|
199
|
-
of multiple pages, all data will be returned.
|
|
200
|
-
|
|
201
|
-
Parameters
|
|
202
|
-
----------
|
|
203
|
-
paths : str
|
|
204
|
-
The paths of the API endpoint that is called. For example projects, 123, packages
|
|
205
|
-
would be querying the projects/123/packages endpoint.
|
|
206
|
-
query_params : str, optional
|
|
207
|
-
Additional arguments for the query of the URL, for example to filter
|
|
208
|
-
results: package_name=mypackage
|
|
209
|
-
|
|
210
|
-
Returns
|
|
211
|
-
-------
|
|
212
|
-
list
|
|
213
|
-
Data from GitLab REST API endpoint with the arguments.
|
|
214
|
-
"""
|
|
215
|
-
url = self._url() + self._build_path(*paths) + self.build_query(**query_params)
|
|
216
|
-
data = []
|
|
217
|
-
while url:
|
|
218
|
-
res_status, res_data, res_headers = self._get(url, self._get_headers())
|
|
219
|
-
logger.debug("Response status: %d", res_status)
|
|
220
|
-
res_data = loads(res_data)
|
|
221
|
-
logger.debug("Response data: %s", res_data)
|
|
222
|
-
data = data + res_data
|
|
223
|
-
url = self._parse_header_links(res_headers).get("next")
|
|
224
|
-
return data
|
|
225
|
-
|
|
226
|
-
def _put(
|
|
227
|
-
self, url: str, data: bytes, headers: dict
|
|
228
|
-
) -> tuple[int, bytes, HTTPMessage]:
|
|
229
|
-
"""
|
|
230
|
-
Makes a raw PUT request to the given URL, and returns
|
|
231
|
-
the response status, body, and headers.
|
|
232
|
-
|
|
233
|
-
Parameters
|
|
234
|
-
----------
|
|
235
|
-
url : str
|
|
236
|
-
The URL of the HTTP request to make.
|
|
237
|
-
data : bytes
|
|
238
|
-
The data to PUT
|
|
239
|
-
headers: dict
|
|
240
|
-
The HTTP headers used in the request.
|
|
241
|
-
|
|
242
|
-
Returns
|
|
243
|
-
-------
|
|
244
|
-
int
|
|
245
|
-
The HTTP response code, such as 200
|
|
246
|
-
bytes
|
|
247
|
-
The HTTP response body read as bytes
|
|
248
|
-
HTTPMessage
|
|
249
|
-
The HTTP response headers
|
|
250
|
-
"""
|
|
251
|
-
logger.debug("Putting %s", url)
|
|
252
|
-
req = request.Request(url, method="PUT", data=data, headers=headers)
|
|
253
|
-
with request.urlopen(req) as response:
|
|
254
|
-
return response.status, response.read(), response.headers
|
|
255
|
-
|
|
256
|
-
def put(self, data: bytes, *paths: str, **query_params: str) -> int:
|
|
257
|
-
"""
|
|
258
|
-
Makes a HTTP PUT request to the given GitLab path.
|
|
259
|
-
|
|
260
|
-
Parameters
|
|
261
|
-
----------
|
|
262
|
-
data : bytes
|
|
263
|
-
The data to PUT.
|
|
264
|
-
paths : str
|
|
265
|
-
The URL path of the HTTP request to make.
|
|
266
|
-
query_params : str, optional
|
|
267
|
-
Additional arguments for the query of the URL, for example to filter
|
|
268
|
-
results: package_name=mypackage
|
|
269
|
-
|
|
270
|
-
Returns
|
|
271
|
-
-------
|
|
272
|
-
int
|
|
273
|
-
0 If the request was successful.
|
|
274
|
-
"""
|
|
275
|
-
ret = 1
|
|
276
|
-
url = self._url() + self._build_path(*paths) + self.build_query(**query_params)
|
|
277
|
-
status, _, _ = self._put(url, data, self._get_headers())
|
|
278
|
-
if status == 201: # 201 is created
|
|
279
|
-
ret = 0
|
|
280
|
-
return ret
|
|
281
|
-
|
|
282
|
-
def _delete(self, url, headers):
|
|
283
|
-
"""
|
|
284
|
-
Makes a raw DELETE request to the given URL, and returns
|
|
285
|
-
the response status, body, and headers.
|
|
286
|
-
|
|
287
|
-
Parameters
|
|
288
|
-
----------
|
|
289
|
-
url : str
|
|
290
|
-
The URL of the HTTP request to make.
|
|
291
|
-
headers: dict
|
|
292
|
-
The HTTP headers used in the request.
|
|
293
|
-
|
|
294
|
-
Returns
|
|
295
|
-
-------
|
|
296
|
-
int
|
|
297
|
-
The HTTP response code, such as 200
|
|
298
|
-
bytes
|
|
299
|
-
The HTTP response body read as bytes
|
|
300
|
-
HTTPMessage
|
|
301
|
-
The HTTP response headers
|
|
302
|
-
"""
|
|
303
|
-
logger.debug("Deleting %s", url)
|
|
304
|
-
req = request.Request(url, method="DELETE", headers=headers)
|
|
305
|
-
with request.urlopen(req) as response:
|
|
306
|
-
return response.status, response.read(), response.headers
|
|
307
|
-
|
|
308
|
-
def delete(self, *paths: str, **query_params: str) -> int:
|
|
309
|
-
"""
|
|
310
|
-
Makes a HTTP DELETE request to the given GitLab path.
|
|
311
|
-
|
|
312
|
-
Parameters
|
|
313
|
-
----------
|
|
314
|
-
paths : str
|
|
315
|
-
The URL path of the HTTP request to make.
|
|
316
|
-
query_params : str, optional
|
|
317
|
-
Additional arguments for the query of the URL, for example to filter
|
|
318
|
-
results: package_name=mypackage
|
|
319
|
-
|
|
320
|
-
Returns
|
|
321
|
-
-------
|
|
322
|
-
int
|
|
323
|
-
0 If the request was successful.
|
|
324
|
-
"""
|
|
325
|
-
ret = 1
|
|
326
|
-
url = self._url() + self._build_path(*paths) + self.build_query(**query_params)
|
|
327
|
-
status, _, _ = self._delete(url, self._get_headers())
|
|
328
|
-
if status == 204:
|
|
329
|
-
# 204 is no content, that GL responds when file deleted
|
|
330
|
-
ret = 0
|
|
331
|
-
return ret
|
|
35
|
+
self.gl_api = GitLabAPI(host, token_type, token)
|
|
332
36
|
|
|
333
37
|
def get_versions(self, project_id: str, package_name: str) -> list:
|
|
334
38
|
"""
|
|
@@ -349,7 +53,7 @@ class Packages:
|
|
|
349
53
|
"""
|
|
350
54
|
packages = []
|
|
351
55
|
logger.debug("Listing packages with name %s", package_name)
|
|
352
|
-
data = self.get_all(
|
|
56
|
+
data = self.gl_api.get_all(
|
|
353
57
|
"projects",
|
|
354
58
|
parse.quote_plus(project_id),
|
|
355
59
|
"packages",
|
|
@@ -387,7 +91,7 @@ class Packages:
|
|
|
387
91
|
"""
|
|
388
92
|
files = {}
|
|
389
93
|
logger.debug("Listing package %d files", package_id)
|
|
390
|
-
data = self.get_all(
|
|
94
|
+
data = self.gl_api.get_all(
|
|
391
95
|
"projects",
|
|
392
96
|
parse.quote_plus(project_id),
|
|
393
97
|
"packages",
|
|
@@ -425,7 +129,7 @@ class Packages:
|
|
|
425
129
|
"""
|
|
426
130
|
package_id = -1
|
|
427
131
|
logger.debug("Fetching package %s (%s) ID", package_name, package_version)
|
|
428
|
-
data = self.get_all(
|
|
132
|
+
data = self.gl_api.get_all(
|
|
429
133
|
"projects",
|
|
430
134
|
parse.quote_plus(project_id),
|
|
431
135
|
"packages",
|
|
@@ -438,6 +142,48 @@ class Packages:
|
|
|
438
142
|
package_id = package["id"]
|
|
439
143
|
return package_id
|
|
440
144
|
|
|
145
|
+
def download(
|
|
146
|
+
self,
|
|
147
|
+
project_id: str,
|
|
148
|
+
package_name: str,
|
|
149
|
+
package_version: str,
|
|
150
|
+
destination: str = "",
|
|
151
|
+
) -> int:
|
|
152
|
+
"""
|
|
153
|
+
Downloads a package from a GitLab generic package registry
|
|
154
|
+
|
|
155
|
+
Parameters
|
|
156
|
+
----------
|
|
157
|
+
project_id : str
|
|
158
|
+
The project ID or path, including namespace.
|
|
159
|
+
Examples: `123` or `namespace/project`.
|
|
160
|
+
package_name : str
|
|
161
|
+
The name of the generic package.
|
|
162
|
+
package_version : str
|
|
163
|
+
The version of the generic package
|
|
164
|
+
destination : str, optional
|
|
165
|
+
The destination folder of the downloaded file. If not set,
|
|
166
|
+
current working directory is used.
|
|
167
|
+
|
|
168
|
+
Return
|
|
169
|
+
------
|
|
170
|
+
int
|
|
171
|
+
Zero if everything went fine, non-zero coke otherwise.
|
|
172
|
+
Two in case package was not found.
|
|
173
|
+
"""
|
|
174
|
+
ret = 2
|
|
175
|
+
package_id = self.get_id(project_id, package_name, package_version)
|
|
176
|
+
if package_id:
|
|
177
|
+
files = []
|
|
178
|
+
files = self.get_files(project_id, package_id).keys()
|
|
179
|
+
for file in files:
|
|
180
|
+
ret = self.download_file(
|
|
181
|
+
project_id, package_name, package_version, file, destination
|
|
182
|
+
)
|
|
183
|
+
if ret:
|
|
184
|
+
break
|
|
185
|
+
return ret
|
|
186
|
+
|
|
441
187
|
def download_file(
|
|
442
188
|
self,
|
|
443
189
|
project_id: str,
|
|
@@ -471,7 +217,7 @@ class Packages:
|
|
|
471
217
|
"""
|
|
472
218
|
ret = 1
|
|
473
219
|
logger.debug("Downloading file %s", filename)
|
|
474
|
-
status, data, _ = self.get(
|
|
220
|
+
status, data, _ = self.gl_api.get(
|
|
475
221
|
"projects",
|
|
476
222
|
parse.quote_plus(project_id),
|
|
477
223
|
"packages",
|
|
@@ -493,12 +239,11 @@ class Packages:
|
|
|
493
239
|
ret = 0
|
|
494
240
|
return ret
|
|
495
241
|
|
|
496
|
-
def
|
|
242
|
+
def upload(
|
|
497
243
|
self,
|
|
498
244
|
project_id: str,
|
|
499
245
|
package_name: str,
|
|
500
246
|
package_version: str,
|
|
501
|
-
filename: str,
|
|
502
247
|
source: str,
|
|
503
248
|
) -> int:
|
|
504
249
|
"""
|
|
@@ -513,9 +258,6 @@ class Packages:
|
|
|
513
258
|
The name of the generic package.
|
|
514
259
|
package_version : str
|
|
515
260
|
The version of the generic package
|
|
516
|
-
pfile : str
|
|
517
|
-
The relative path of the file that is uploaded. If left empty,
|
|
518
|
-
all files from the source folder, and it's subfolders, are uploaded.
|
|
519
261
|
source : str
|
|
520
262
|
The source folder that is used as root when uploading. If empty,
|
|
521
263
|
current working directory is used.
|
|
@@ -527,24 +269,21 @@ class Packages:
|
|
|
527
269
|
"""
|
|
528
270
|
files = []
|
|
529
271
|
ret = 1
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
if os.path.isfile(os.path.join(item)):
|
|
537
|
-
# Remove the source folder from the path of the files
|
|
538
|
-
files.append(os.path.relpath(item, source))
|
|
272
|
+
filelist = glob(os.path.join(source, "**"), recursive=True)
|
|
273
|
+
for item in filelist:
|
|
274
|
+
# Only add files, not folders
|
|
275
|
+
if os.path.isfile(os.path.join(item)):
|
|
276
|
+
# Remove the source folder from the path of the files
|
|
277
|
+
files.append(os.path.relpath(item, source))
|
|
539
278
|
for afile in files:
|
|
540
|
-
ret = self.
|
|
279
|
+
ret = self.upload_file(
|
|
541
280
|
project_id, package_name, package_version, afile, source
|
|
542
281
|
)
|
|
543
282
|
if ret:
|
|
544
283
|
break
|
|
545
284
|
return ret
|
|
546
285
|
|
|
547
|
-
def
|
|
286
|
+
def upload_file(
|
|
548
287
|
self,
|
|
549
288
|
project_id: str,
|
|
550
289
|
package_name: str,
|
|
@@ -578,7 +317,7 @@ class Packages:
|
|
|
578
317
|
fpath = os.path.join(source, filename)
|
|
579
318
|
logger.debug("Uploading file %s from %s", filename, source)
|
|
580
319
|
with open(fpath, "rb") as data:
|
|
581
|
-
ret = self.put(
|
|
320
|
+
ret = self.gl_api.put(
|
|
582
321
|
data.read(),
|
|
583
322
|
"projects",
|
|
584
323
|
parse.quote_plus(project_id),
|
|
@@ -590,9 +329,7 @@ class Packages:
|
|
|
590
329
|
)
|
|
591
330
|
return ret
|
|
592
331
|
|
|
593
|
-
def
|
|
594
|
-
self, project_id: str, package_name: str, package_version: str
|
|
595
|
-
) -> int:
|
|
332
|
+
def delete(self, project_id: str, package_name: str, package_version: str) -> int:
|
|
596
333
|
"""
|
|
597
334
|
Deletes a version of a GitLab generic package.
|
|
598
335
|
|
|
@@ -614,7 +351,7 @@ class Packages:
|
|
|
614
351
|
ret = 1
|
|
615
352
|
package_id = self.get_id(project_id, package_name, package_version)
|
|
616
353
|
if package_id > 0:
|
|
617
|
-
ret = self.delete(
|
|
354
|
+
ret = self.gl_api.delete(
|
|
618
355
|
"projects", parse.quote_plus(project_id), "packages", str(package_id)
|
|
619
356
|
)
|
|
620
357
|
return ret
|
|
@@ -664,7 +401,7 @@ class Packages:
|
|
|
664
401
|
package_id,
|
|
665
402
|
project_id,
|
|
666
403
|
)
|
|
667
|
-
ret = self.delete(
|
|
404
|
+
ret = self.gl_api.delete(
|
|
668
405
|
"projects",
|
|
669
406
|
parse.quote_plus(project_id),
|
|
670
407
|
"packages",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: glpkg
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.0
|
|
4
4
|
Summary: Tool to make GitLab generic package registry operations easy.
|
|
5
5
|
Author-email: bugproduction <bugproduction@outlook.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -101,6 +101,29 @@ glpkg download --project 12345 --name mypackagename --version 1.5 --file the_onl
|
|
|
101
101
|
|
|
102
102
|
> If a package has multiple files with the same filename, the tool can only download the newest file. This is a restriction of GitLab API.
|
|
103
103
|
|
|
104
|
+
#### Downloading a list of packages
|
|
105
|
+
|
|
106
|
+
To download multiple packages or versions from one or more projects, a package file can be used together with `--from-file` argument with `download`:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
glpkg download --from-file my-packages.txt --destination my-downloads
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The `my-packages.txt` lists all wanted packages with their versions and projects in format `<package-name>==<package-version>@<project-id>`.For example `my-packages.txt` could look like this:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
mypackage==2.3.6-beta@namespace/project
|
|
116
|
+
yourpackage==1.2.3@12345
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The `<project-id>` can be either the path to the project in string format or the project ID in numeric.
|
|
120
|
+
|
|
121
|
+
When `--from-file` argument is used, `--file`, `--project`, `--version`, and `--name` arguments are unused.
|
|
122
|
+
|
|
123
|
+
> The `--destination` argument can be used to download the files from the packages to a different folder. In case packages contains files with same names, some files will be overwritten.
|
|
124
|
+
|
|
125
|
+
> Whatever credentials (`--token`, `--ci`, or `--netrc`) you run the download command with will be used for all package downloads in the list. Make sure that the credential has access to all projects.
|
|
126
|
+
|
|
104
127
|
### Upload a file to a generic package
|
|
105
128
|
|
|
106
129
|
To upload a file to a version of a generic package, run
|
|
@@ -158,7 +181,7 @@ The token that is used to delete packages must have at least Maintainer role in
|
|
|
158
181
|
|
|
159
182
|
### Use in GitLab pipelines
|
|
160
183
|
|
|
161
|
-
If you use the tool in a GitLab pipeline, setting argument `--ci` uses [GitLab predefined variables](https://docs.gitlab.com/ci/variables/predefined_variables/) to configure the tool. In this case `CI_SERVER_HOST`, `CI_PROJECT_ID`, and `CI_JOB_TOKEN` environment variables are used. The `--project`, and `--token` arguments can still be used to override the project ID or to use a personal or project access token instead of `CI_JOB_TOKEN`.
|
|
184
|
+
If you use the tool in a GitLab pipeline, setting argument `--ci` uses [GitLab predefined variables](https://docs.gitlab.com/ci/variables/predefined_variables/) to configure the tool. In this case `CI_SERVER_HOST`, `CI_PROJECT_ID`, and `CI_JOB_TOKEN` environment variables are used. The `--host`, `--project`, and `--token` arguments can still be used to override the host, project ID, or to use a personal or project access token instead of `CI_JOB_TOKEN`.
|
|
162
185
|
|
|
163
186
|
In other words, you don't need to give the `--host`, `--project`, or `--token` arguments if you are interacting with the package registry of the project where the pipeline is running. Example: uploading `my-file.txt` to generic package `mypackagename` version `1.0` in the project package registry in CI:
|
|
164
187
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
gitlab/__init__.py,sha256=_Gr_P6JuBFaW50vk2fuhb4-PspiCUUCDot6pPQZhrZM,105
|
|
2
|
+
gitlab/__main__.py,sha256=ol-xxwX5PONbnqEsq5w-5_bowDyg9Zu-eJZG5XrJeFc,427
|
|
3
|
+
gitlab/cli_handler.py,sha256=LorqYsco1bL8up_2QyAlku0L6p_8Cp_twvaKpCNmEuQ,15921
|
|
4
|
+
gitlab/packages.py,sha256=6m2SkqA-wNyysgqCIToBUM1uy9VH0iegQZCIaoYxq6s,13179
|
|
5
|
+
gitlab/lib/gitlab_api.py,sha256=a__xlefBIX-sEIV21wVHVaZxwRrulI-_RoNKwXnX8JM,8222
|
|
6
|
+
gitlab/lib/http.py,sha256=1QgH8unBNCg6709EhYH0MhtjlcAjzWQ7TFqB5T0-pgY,2344
|
|
7
|
+
glpkg-1.5.0.dist-info/licenses/LICENSE.md,sha256=josGXvZq628dNS0Iru58-DPE7dRpDXzjJxKKT35103g,1065
|
|
8
|
+
glpkg-1.5.0.dist-info/METADATA,sha256=2rhVd60bHadPleyxCU4UUu6jlnJX8J59m60tWkFUqmU,8889
|
|
9
|
+
glpkg-1.5.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
10
|
+
glpkg-1.5.0.dist-info/entry_points.txt,sha256=xHPZwx2oShYDZ3AyH7WSIvuhFMssy7QLlQk-JAbje_w,46
|
|
11
|
+
glpkg-1.5.0.dist-info/top_level.txt,sha256=MvIaP8p_Oaf4gO_hXmHkX-5y2deHLp1pe6tJR3ukQ6o,7
|
|
12
|
+
glpkg-1.5.0.dist-info/RECORD,,
|
glpkg-1.4.2.dist-info/RECORD
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
gitlab/__init__.py,sha256=zDp9HG0fq7XbOi99n4lMzur9Yeb1uhtfTJoZQxwAW3w,105
|
|
2
|
-
gitlab/__main__.py,sha256=ol-xxwX5PONbnqEsq5w-5_bowDyg9Zu-eJZG5XrJeFc,427
|
|
3
|
-
gitlab/cli_handler.py,sha256=_iQCOUli5jXrXD8trnh8KFCSQQXXOEMZrkTRfmE797Q,13854
|
|
4
|
-
gitlab/packages.py,sha256=lIIo14EVm8lOwgZKug_w7txWppiqx6N0D0zLqpbbJ8c,21729
|
|
5
|
-
glpkg-1.4.2.dist-info/licenses/LICENSE.md,sha256=josGXvZq628dNS0Iru58-DPE7dRpDXzjJxKKT35103g,1065
|
|
6
|
-
glpkg-1.4.2.dist-info/METADATA,sha256=Sh4rBcnF7W47p-K8mw6rA5OTQmONmYAT0LTQUkbD_Gw,7730
|
|
7
|
-
glpkg-1.4.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
8
|
-
glpkg-1.4.2.dist-info/entry_points.txt,sha256=xHPZwx2oShYDZ3AyH7WSIvuhFMssy7QLlQk-JAbje_w,46
|
|
9
|
-
glpkg-1.4.2.dist-info/top_level.txt,sha256=MvIaP8p_Oaf4gO_hXmHkX-5y2deHLp1pe6tJR3ukQ6o,7
|
|
10
|
-
glpkg-1.4.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|