glpkg 1.3.0__py3-none-any.whl → 1.4.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 +67 -9
- gitlab/packages.py +324 -158
- {glpkg-1.3.0.dist-info → glpkg-1.4.0.dist-info}/METADATA +33 -9
- glpkg-1.4.0.dist-info/RECORD +10 -0
- glpkg-1.3.0.dist-info/RECORD +0 -10
- {glpkg-1.3.0.dist-info → glpkg-1.4.0.dist-info}/WHEEL +0 -0
- {glpkg-1.3.0.dist-info → glpkg-1.4.0.dist-info}/entry_points.txt +0 -0
- {glpkg-1.3.0.dist-info → glpkg-1.4.0.dist-info}/licenses/LICENSE.md +0 -0
- {glpkg-1.3.0.dist-info → glpkg-1.4.0.dist-info}/top_level.txt +0 -0
gitlab/__init__.py
CHANGED
gitlab/cli_handler.py
CHANGED
|
@@ -40,6 +40,13 @@ class CLIHandler:
|
|
|
40
40
|
name="upload", description="Uploads file to a specific package version."
|
|
41
41
|
)
|
|
42
42
|
self._register_upload_parser(upload_parser)
|
|
43
|
+
delete_parser = subparsers.add_parser(
|
|
44
|
+
name="delete",
|
|
45
|
+
description="Deletes a specific package version or a specific file from "
|
|
46
|
+
"a specific package version, depending whether the --file argument is "
|
|
47
|
+
"set or not.",
|
|
48
|
+
)
|
|
49
|
+
self._register_delete_parser(delete_parser)
|
|
43
50
|
self.args = parser.parse_args()
|
|
44
51
|
|
|
45
52
|
def _print_version(self, _args: argparse.Namespace) -> int:
|
|
@@ -240,16 +247,16 @@ class CLIHandler:
|
|
|
240
247
|
host, project, name, token_user, token = self._args(args)
|
|
241
248
|
version = args.version
|
|
242
249
|
destination = args.destination
|
|
243
|
-
|
|
244
|
-
package_id =
|
|
250
|
+
packages = Packages(host, token_user, token)
|
|
251
|
+
package_id = packages.get_id(project, name, version)
|
|
245
252
|
if package_id:
|
|
246
253
|
files = []
|
|
247
254
|
if args.file:
|
|
248
255
|
files.append(args.file)
|
|
249
256
|
else:
|
|
250
|
-
files =
|
|
257
|
+
files = packages.get_files(project, package_id).keys()
|
|
251
258
|
for file in files:
|
|
252
|
-
ret =
|
|
259
|
+
ret = packages.download_file(project, name, version, file, destination)
|
|
253
260
|
if ret:
|
|
254
261
|
print("Failed to download file " + file)
|
|
255
262
|
break
|
|
@@ -287,10 +294,10 @@ class CLIHandler:
|
|
|
287
294
|
Zero if everything goes well, non-zero otherwise
|
|
288
295
|
"""
|
|
289
296
|
host, project, name, token_user, token = self._args(args)
|
|
290
|
-
|
|
291
|
-
|
|
297
|
+
packages = Packages(host, token_user, token)
|
|
298
|
+
package_list = packages.get_versions(project, name)
|
|
292
299
|
print("Name" + "\t\t" + "Version")
|
|
293
|
-
for package in
|
|
300
|
+
for package in package_list:
|
|
294
301
|
print(package["name"] + "\t" + package["version"])
|
|
295
302
|
|
|
296
303
|
def _register_upload_parser(self, parser: argparse.ArgumentParser):
|
|
@@ -356,6 +363,57 @@ class CLIHandler:
|
|
|
356
363
|
print("File " + file + " does not exist!")
|
|
357
364
|
ret = 1
|
|
358
365
|
if not ret:
|
|
359
|
-
|
|
360
|
-
ret =
|
|
366
|
+
packages = Packages(host, token_user, token)
|
|
367
|
+
ret = packages.upload_file(project, name, version, file, source)
|
|
368
|
+
return ret
|
|
369
|
+
|
|
370
|
+
def _register_delete_parser(self, parser: argparse.ArgumentParser):
|
|
371
|
+
"""
|
|
372
|
+
Registers the delete command related arguments to the parser:
|
|
373
|
+
- version
|
|
374
|
+
- file
|
|
375
|
+
- action
|
|
376
|
+
|
|
377
|
+
Additionally, registers the common args.
|
|
378
|
+
|
|
379
|
+
Parameters
|
|
380
|
+
----------
|
|
381
|
+
parser:
|
|
382
|
+
The argparser where to register the upload arguments.
|
|
383
|
+
"""
|
|
384
|
+
self._register_common_arguments(parser)
|
|
385
|
+
parser.add_argument("-v", "--version", type=str, help="The package version.")
|
|
386
|
+
parser.add_argument(
|
|
387
|
+
"-f",
|
|
388
|
+
"--file",
|
|
389
|
+
type=str,
|
|
390
|
+
help="The file to be deleted, for example my_file.txt. Note that "
|
|
391
|
+
"only relative paths (to the package root) are supported. If undefined, "
|
|
392
|
+
"the package version is deleted.",
|
|
393
|
+
)
|
|
394
|
+
parser.set_defaults(action=self._delete)
|
|
395
|
+
|
|
396
|
+
def _delete(self, args: argparse.Namespace) -> int:
|
|
397
|
+
"""
|
|
398
|
+
Deletes a file from a GitLab generic package
|
|
399
|
+
|
|
400
|
+
Parameters
|
|
401
|
+
----------
|
|
402
|
+
args : argparse.Namespace
|
|
403
|
+
The arguments from command line
|
|
404
|
+
|
|
405
|
+
Returns
|
|
406
|
+
-------
|
|
407
|
+
int
|
|
408
|
+
Zero if everything went fine, non-zero otherwise.
|
|
409
|
+
"""
|
|
410
|
+
ret = 0
|
|
411
|
+
host, project, name, token_user, token = self._args(args)
|
|
412
|
+
version = args.version
|
|
413
|
+
file = args.file
|
|
414
|
+
packages = Packages(host, token_user, token)
|
|
415
|
+
if file:
|
|
416
|
+
ret = packages.delete_file(project, name, version, file)
|
|
417
|
+
else:
|
|
418
|
+
ret = packages.delete_package(project, name, version)
|
|
361
419
|
return ret
|
gitlab/packages.py
CHANGED
|
@@ -15,7 +15,7 @@ class Packages:
|
|
|
15
15
|
|
|
16
16
|
def __init__(self, host: str, token_type: str, token: str):
|
|
17
17
|
"""
|
|
18
|
-
Creates a new instance of
|
|
18
|
+
Creates a new instance of class.
|
|
19
19
|
|
|
20
20
|
Parameters
|
|
21
21
|
----------
|
|
@@ -37,7 +37,7 @@ class Packages:
|
|
|
37
37
|
self.token_type = token_type
|
|
38
38
|
self.token = token
|
|
39
39
|
|
|
40
|
-
def
|
|
40
|
+
def _url(self) -> str:
|
|
41
41
|
"""
|
|
42
42
|
Returns the GitLab REST API URL by using the host variable.
|
|
43
43
|
|
|
@@ -48,25 +48,7 @@ class Packages:
|
|
|
48
48
|
"""
|
|
49
49
|
return f"https://{self.host}/api/v4/"
|
|
50
50
|
|
|
51
|
-
def
|
|
52
|
-
"""
|
|
53
|
-
Returns the project REST API URL of the project
|
|
54
|
-
|
|
55
|
-
Parameters
|
|
56
|
-
----------
|
|
57
|
-
project : str
|
|
58
|
-
The project ID or the path of the project, including namespace.
|
|
59
|
-
Examples: `123` or `namespace/project`.
|
|
60
|
-
|
|
61
|
-
Returns
|
|
62
|
-
-------
|
|
63
|
-
str
|
|
64
|
-
The project REST API URL, for example `https://gitlab.com/api/v4/projects/123/`
|
|
65
|
-
or `https://gitlab.com/api/v4/projects/namespace%2Fproject/`
|
|
66
|
-
"""
|
|
67
|
-
return f"{self.api_url()}projects/{parse.quote_plus(project)}/"
|
|
68
|
-
|
|
69
|
-
def get_headers(self) -> dict:
|
|
51
|
+
def _get_headers(self) -> dict:
|
|
70
52
|
"""
|
|
71
53
|
Creates headers for a GitLab REST API call.
|
|
72
54
|
|
|
@@ -83,98 +65,145 @@ class Packages:
|
|
|
83
65
|
headers = {self.token_type: self.token}
|
|
84
66
|
return headers
|
|
85
67
|
|
|
86
|
-
def
|
|
68
|
+
def _parse_header_links(self, headers: HTTPMessage) -> dict:
|
|
87
69
|
"""
|
|
88
|
-
|
|
89
|
-
the
|
|
70
|
+
Parses link field from HTTP headers to a dictionary where
|
|
71
|
+
the link "rel" value is the key.
|
|
90
72
|
|
|
73
|
+
This is useful for example with GitLab REST API to get the pagination links.
|
|
91
74
|
|
|
92
75
|
Parameters
|
|
93
76
|
----------
|
|
94
|
-
|
|
95
|
-
The
|
|
77
|
+
headers : HTTPMessage
|
|
78
|
+
The HTTP response headers that contain the links
|
|
96
79
|
|
|
97
80
|
Returns
|
|
98
81
|
-------
|
|
99
|
-
|
|
100
|
-
The
|
|
101
|
-
|
|
102
|
-
The HTTP response body read as bytes
|
|
103
|
-
HTTPMessage
|
|
104
|
-
The HTTP response headers
|
|
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.
|
|
105
85
|
"""
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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:
|
|
112
104
|
"""
|
|
113
|
-
Returns the
|
|
114
|
-
|
|
115
|
-
Uses the field x-next-page from the headers.
|
|
105
|
+
Returns the path to be appended to the REST API URL
|
|
116
106
|
|
|
117
107
|
Parameters
|
|
118
108
|
----------
|
|
119
|
-
|
|
120
|
-
The
|
|
121
|
-
pagination
|
|
109
|
+
paths : str
|
|
110
|
+
The path string that are joined with "/".
|
|
122
111
|
|
|
123
112
|
Returns
|
|
124
113
|
-------
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if next_page:
|
|
134
|
-
ret = int(next_page)
|
|
135
|
-
logger.debug("Response incomplete, next page is %s", next_page)
|
|
136
|
-
else:
|
|
137
|
-
logger.debug("Response complete")
|
|
138
|
-
return ret
|
|
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)
|
|
139
122
|
|
|
140
|
-
def
|
|
123
|
+
def build_query(self, **args: str) -> str:
|
|
141
124
|
"""
|
|
142
125
|
Builds a query for a GitLab REST API request
|
|
143
126
|
|
|
144
127
|
Parameters
|
|
145
128
|
----------
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
page : int
|
|
149
|
-
Page number for the pagination of the request.
|
|
150
|
-
Set to 0 to omit the pagination.
|
|
129
|
+
args : str
|
|
130
|
+
keyword arguments for the REST API query, like per_page=20, page=20
|
|
151
131
|
|
|
152
132
|
Returns
|
|
153
133
|
-------
|
|
154
134
|
str
|
|
155
135
|
A query string for a REST API request. Append this to
|
|
156
|
-
the request URL. Example `?
|
|
136
|
+
the request URL. Example `?per_page=20&page=20`.
|
|
157
137
|
"""
|
|
158
138
|
query = ""
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
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
|
|
163
143
|
return query
|
|
164
144
|
|
|
165
|
-
def
|
|
145
|
+
def _get(self, url: str, headers: dict) -> tuple[int, bytes, HTTPMessage]:
|
|
166
146
|
"""
|
|
167
|
-
|
|
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
|
|
168
199
|
of multiple pages, all data will be returned.
|
|
169
200
|
|
|
170
201
|
Parameters
|
|
171
202
|
----------
|
|
172
|
-
|
|
173
|
-
The
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
The path of the project API endpoint that is called. For example packages
|
|
177
|
-
arg : str, optional
|
|
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
|
|
178
207
|
Additional arguments for the query of the URL, for example to filter
|
|
179
208
|
results: package_name=mypackage
|
|
180
209
|
|
|
@@ -183,31 +212,80 @@ class Packages:
|
|
|
183
212
|
list
|
|
184
213
|
Data from GitLab REST API endpoint with the arguments.
|
|
185
214
|
"""
|
|
215
|
+
url = self._url() + self._build_path(*paths) + self.build_query(**query_params)
|
|
186
216
|
data = []
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
more = False
|
|
191
|
-
query = self._build_query(arg, page)
|
|
192
|
-
url = self.project_api_url(project) + apath + query
|
|
193
|
-
status, res_data, headers = self._request(url)
|
|
194
|
-
logger.debug("Response status: %d", status)
|
|
217
|
+
while url:
|
|
218
|
+
res_status, res_data, res_headers = self._get(url, self._get_headers())
|
|
219
|
+
logger.debug("Response status: %d", res_status)
|
|
195
220
|
res_data = loads(res_data)
|
|
196
221
|
logger.debug("Response data: %s", res_data)
|
|
197
222
|
data = data + res_data
|
|
198
|
-
|
|
199
|
-
if page:
|
|
200
|
-
more = True
|
|
223
|
+
url = self._parse_header_links(res_headers).get("next")
|
|
201
224
|
return data
|
|
202
225
|
|
|
203
|
-
def
|
|
226
|
+
def put(self, data: bytes, *paths: str, **query_params: str) -> int:
|
|
227
|
+
"""
|
|
228
|
+
Makes a HTTP PUT request to the given GitLab path.
|
|
229
|
+
|
|
230
|
+
Parameters
|
|
231
|
+
----------
|
|
232
|
+
data : bytes
|
|
233
|
+
The data to PUT.
|
|
234
|
+
paths : str
|
|
235
|
+
The URL path of the HTTP request to make.
|
|
236
|
+
query_params : str, optional
|
|
237
|
+
Additional arguments for the query of the URL, for example to filter
|
|
238
|
+
results: package_name=mypackage
|
|
239
|
+
|
|
240
|
+
Returns
|
|
241
|
+
-------
|
|
242
|
+
int
|
|
243
|
+
0 If the request was successful.
|
|
244
|
+
"""
|
|
245
|
+
ret = 1
|
|
246
|
+
url = self._url() + self._build_path(*paths) + self.build_query(**query_params)
|
|
247
|
+
logger.debug("Putting %s", url)
|
|
248
|
+
req = request.Request(url, method="PUT", data=data, headers=self._get_headers())
|
|
249
|
+
with request.urlopen(req) as res:
|
|
250
|
+
if res.status == 201: # 201 is created
|
|
251
|
+
ret = 0
|
|
252
|
+
return ret
|
|
253
|
+
|
|
254
|
+
def delete(self, *paths: str, **query_params: str) -> int:
|
|
255
|
+
"""
|
|
256
|
+
Makes a HTTP DELETE request to the given GitLab path.
|
|
257
|
+
|
|
258
|
+
Parameters
|
|
259
|
+
----------
|
|
260
|
+
paths : str
|
|
261
|
+
The URL path of the HTTP request to make.
|
|
262
|
+
query_params : str, optional
|
|
263
|
+
Additional arguments for the query of the URL, for example to filter
|
|
264
|
+
results: package_name=mypackage
|
|
265
|
+
|
|
266
|
+
Returns
|
|
267
|
+
-------
|
|
268
|
+
int
|
|
269
|
+
0 If the request was successful.
|
|
270
|
+
"""
|
|
271
|
+
ret = 1
|
|
272
|
+
url = self._url() + self._build_path(*paths) + self.build_query(**query_params)
|
|
273
|
+
logger.debug("Deleting %s", url)
|
|
274
|
+
req = request.Request(url, method="DELETE", headers=self._get_headers())
|
|
275
|
+
with request.urlopen(req) as res:
|
|
276
|
+
if res.status == 204:
|
|
277
|
+
# 204 is no content, that GL responds when file deleted
|
|
278
|
+
ret = 0
|
|
279
|
+
return ret
|
|
280
|
+
|
|
281
|
+
def get_versions(self, project_id: str, package_name: str) -> list:
|
|
204
282
|
"""
|
|
205
283
|
Lists the available versions of the package
|
|
206
284
|
|
|
207
285
|
Parameters
|
|
208
286
|
----------
|
|
209
|
-
|
|
210
|
-
The project ID or
|
|
287
|
+
project_id : str
|
|
288
|
+
The project ID or path, including namespace.
|
|
211
289
|
Examples: `123` or `namespace/project`.
|
|
212
290
|
package_name : str
|
|
213
291
|
The name of the package that is listed.
|
|
@@ -219,8 +297,12 @@ class Packages:
|
|
|
219
297
|
"""
|
|
220
298
|
packages = []
|
|
221
299
|
logger.debug("Listing packages with name %s", package_name)
|
|
222
|
-
data = self.
|
|
223
|
-
|
|
300
|
+
data = self.get_all(
|
|
301
|
+
"projects",
|
|
302
|
+
parse.quote_plus(project_id),
|
|
303
|
+
"packages",
|
|
304
|
+
package_name=package_name,
|
|
305
|
+
package_type="generic",
|
|
224
306
|
)
|
|
225
307
|
for package in data:
|
|
226
308
|
name = parse.unquote(package["name"])
|
|
@@ -232,46 +314,52 @@ class Packages:
|
|
|
232
314
|
packages.append({"name": name, "version": version})
|
|
233
315
|
return packages
|
|
234
316
|
|
|
235
|
-
def
|
|
317
|
+
def get_files(self, project_id: str, package_id: int) -> dict:
|
|
236
318
|
"""
|
|
237
319
|
Lists all files of a specific package ID from GitLab REST API
|
|
238
320
|
|
|
239
321
|
Parameters
|
|
240
322
|
----------
|
|
241
|
-
|
|
242
|
-
The project ID or
|
|
323
|
+
project_id : str
|
|
324
|
+
The project ID or path, including namespace.
|
|
243
325
|
Examples: `123` or `namespace/project`.
|
|
244
326
|
package_id : int
|
|
245
327
|
The package ID that is listed
|
|
246
328
|
|
|
247
329
|
Return
|
|
248
330
|
------
|
|
249
|
-
|
|
250
|
-
|
|
331
|
+
dict
|
|
332
|
+
Dictionary of file (names) that are in the package, with
|
|
333
|
+
each element containing a dictionary containing information
|
|
334
|
+
of the file
|
|
251
335
|
"""
|
|
252
|
-
files =
|
|
336
|
+
files = {}
|
|
253
337
|
logger.debug("Listing package %d files", package_id)
|
|
254
|
-
|
|
255
|
-
|
|
338
|
+
data = self.get_all(
|
|
339
|
+
"projects",
|
|
340
|
+
parse.quote_plus(project_id),
|
|
341
|
+
"packages",
|
|
342
|
+
str(package_id),
|
|
343
|
+
"package_files",
|
|
344
|
+
)
|
|
256
345
|
for package in data:
|
|
257
346
|
# Only append the filename once to the list of files
|
|
258
347
|
# as there's no way to download them separately through
|
|
259
348
|
# the API
|
|
260
349
|
filename = parse.unquote(package["file_name"])
|
|
261
|
-
|
|
262
|
-
|
|
350
|
+
file_id = package["id"]
|
|
351
|
+
if not files.get(filename):
|
|
352
|
+
files[filename] = {"id": file_id}
|
|
263
353
|
return files
|
|
264
354
|
|
|
265
|
-
def
|
|
266
|
-
self, project: str, package_name: str, package_version: str
|
|
267
|
-
) -> int:
|
|
355
|
+
def get_id(self, project_id: str, package_name: str, package_version: str) -> int:
|
|
268
356
|
"""
|
|
269
357
|
Gets the package ID of a specific package version.
|
|
270
358
|
|
|
271
359
|
Parameters
|
|
272
360
|
----------
|
|
273
|
-
|
|
274
|
-
The project ID or
|
|
361
|
+
project_id : str
|
|
362
|
+
The project ID or path, including namespace.
|
|
275
363
|
Examples: `123` or `namespace/project`.
|
|
276
364
|
package_name : str
|
|
277
365
|
The name of the package.
|
|
@@ -281,18 +369,18 @@ class Packages:
|
|
|
281
369
|
Return
|
|
282
370
|
------
|
|
283
371
|
int
|
|
284
|
-
The ID of the package.
|
|
372
|
+
The ID of the package. -1 if no ID was found.
|
|
285
373
|
"""
|
|
286
|
-
package_id =
|
|
374
|
+
package_id = -1
|
|
287
375
|
logger.debug("Fetching package %s (%s) ID", package_name, package_version)
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
376
|
+
data = self.get_all(
|
|
377
|
+
"projects",
|
|
378
|
+
parse.quote_plus(project_id),
|
|
379
|
+
"packages",
|
|
380
|
+
package_name=package_name,
|
|
381
|
+
package_version=package_version,
|
|
382
|
+
package_type="generic",
|
|
294
383
|
)
|
|
295
|
-
data = self.gl_project_api(project, apath, arg)
|
|
296
384
|
if len(data) == 1:
|
|
297
385
|
package = data.pop()
|
|
298
386
|
package_id = package["id"]
|
|
@@ -300,7 +388,7 @@ class Packages:
|
|
|
300
388
|
|
|
301
389
|
def download_file(
|
|
302
390
|
self,
|
|
303
|
-
|
|
391
|
+
project_id: str,
|
|
304
392
|
package_name: str,
|
|
305
393
|
package_version: str,
|
|
306
394
|
filename: str,
|
|
@@ -311,8 +399,8 @@ class Packages:
|
|
|
311
399
|
|
|
312
400
|
Parameters
|
|
313
401
|
----------
|
|
314
|
-
|
|
315
|
-
The project ID or
|
|
402
|
+
project_id : str
|
|
403
|
+
The project ID or path, including namespace.
|
|
316
404
|
Examples: `123` or `namespace/project`.
|
|
317
405
|
package_name : str
|
|
318
406
|
The name of the generic package.
|
|
@@ -331,16 +419,15 @@ class Packages:
|
|
|
331
419
|
"""
|
|
332
420
|
ret = 1
|
|
333
421
|
logger.debug("Downloading file %s", filename)
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
422
|
+
status, data, _ = self.get(
|
|
423
|
+
"projects",
|
|
424
|
+
parse.quote_plus(project_id),
|
|
425
|
+
"packages",
|
|
426
|
+
"generic",
|
|
427
|
+
parse.quote_plus(package_name),
|
|
428
|
+
parse.quote_plus(package_version),
|
|
429
|
+
parse.quote(filename),
|
|
342
430
|
)
|
|
343
|
-
status, data, _ = self._request(url)
|
|
344
431
|
if status == 200:
|
|
345
432
|
fpath = os.path.join(destination, filename)
|
|
346
433
|
parent = os.path.dirname(fpath)
|
|
@@ -356,10 +443,10 @@ class Packages:
|
|
|
356
443
|
|
|
357
444
|
def upload_file(
|
|
358
445
|
self,
|
|
359
|
-
|
|
446
|
+
project_id: str,
|
|
360
447
|
package_name: str,
|
|
361
448
|
package_version: str,
|
|
362
|
-
|
|
449
|
+
filename: str,
|
|
363
450
|
source: str,
|
|
364
451
|
) -> int:
|
|
365
452
|
"""
|
|
@@ -367,14 +454,14 @@ class Packages:
|
|
|
367
454
|
|
|
368
455
|
Parameters
|
|
369
456
|
----------
|
|
370
|
-
|
|
371
|
-
The project ID or
|
|
457
|
+
project_id : str
|
|
458
|
+
The project ID or path, including namespace.
|
|
372
459
|
Examples: `123` or `namespace/project`.
|
|
373
460
|
package_name : str
|
|
374
461
|
The name of the generic package.
|
|
375
462
|
package_version : str
|
|
376
463
|
The version of the generic package
|
|
377
|
-
|
|
464
|
+
pfile : str
|
|
378
465
|
The relative path of the file that is uploaded. If left empty,
|
|
379
466
|
all files from the source folder, and it's subfolders, are uploaded.
|
|
380
467
|
source : str
|
|
@@ -388,8 +475,8 @@ class Packages:
|
|
|
388
475
|
"""
|
|
389
476
|
files = []
|
|
390
477
|
ret = 1
|
|
391
|
-
if
|
|
392
|
-
files.append(
|
|
478
|
+
if filename:
|
|
479
|
+
files.append(filename)
|
|
393
480
|
else:
|
|
394
481
|
filelist = glob(os.path.join(source, "**"), recursive=True)
|
|
395
482
|
for item in filelist:
|
|
@@ -397,9 +484,9 @@ class Packages:
|
|
|
397
484
|
if os.path.isfile(os.path.join(item)):
|
|
398
485
|
# Remove the source folder from the path of the files
|
|
399
486
|
files.append(os.path.relpath(item, source))
|
|
400
|
-
for
|
|
487
|
+
for afile in files:
|
|
401
488
|
ret = self._upload_file(
|
|
402
|
-
|
|
489
|
+
project_id, package_name, package_version, afile, source
|
|
403
490
|
)
|
|
404
491
|
if ret:
|
|
405
492
|
break
|
|
@@ -407,10 +494,10 @@ class Packages:
|
|
|
407
494
|
|
|
408
495
|
def _upload_file(
|
|
409
496
|
self,
|
|
410
|
-
|
|
497
|
+
project_id: str,
|
|
411
498
|
package_name: str,
|
|
412
499
|
package_version: str,
|
|
413
|
-
|
|
500
|
+
filename: str,
|
|
414
501
|
source: str,
|
|
415
502
|
) -> int:
|
|
416
503
|
"""
|
|
@@ -418,14 +505,14 @@ class Packages:
|
|
|
418
505
|
|
|
419
506
|
Parameters
|
|
420
507
|
----------
|
|
421
|
-
|
|
422
|
-
The project ID or
|
|
508
|
+
project_id : str
|
|
509
|
+
The project ID or path, including namespace.
|
|
423
510
|
Examples: `123` or `namespace/project`.
|
|
424
511
|
package_name : str
|
|
425
512
|
The name of the generic package.
|
|
426
513
|
package_version : str
|
|
427
514
|
The version of the generic package
|
|
428
|
-
|
|
515
|
+
filename : str
|
|
429
516
|
The relative path of the file that is uploaded.
|
|
430
517
|
source : str
|
|
431
518
|
The source folder that is used as root when uploading.
|
|
@@ -436,22 +523,101 @@ class Packages:
|
|
|
436
523
|
Zero if everything went fine, non-zero coke otherwise.
|
|
437
524
|
"""
|
|
438
525
|
ret = 1
|
|
439
|
-
fpath = os.path.join(source,
|
|
440
|
-
logger.debug("Uploading file %s from %s",
|
|
526
|
+
fpath = os.path.join(source, filename)
|
|
527
|
+
logger.debug("Uploading file %s from %s", filename, source)
|
|
441
528
|
with open(fpath, "rb") as data:
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
529
|
+
ret = self.put(
|
|
530
|
+
data.read(),
|
|
531
|
+
"projects",
|
|
532
|
+
parse.quote_plus(project_id),
|
|
533
|
+
"packages",
|
|
534
|
+
"generic",
|
|
535
|
+
parse.quote_plus(package_name),
|
|
536
|
+
parse.quote_plus(package_version),
|
|
537
|
+
parse.quote(filename),
|
|
450
538
|
)
|
|
451
|
-
|
|
452
|
-
|
|
539
|
+
return ret
|
|
540
|
+
|
|
541
|
+
def delete_package(
|
|
542
|
+
self, project_id: str, package_name: str, package_version: str
|
|
543
|
+
) -> int:
|
|
544
|
+
"""
|
|
545
|
+
Deletes a version of a GitLab generic package.
|
|
546
|
+
|
|
547
|
+
Parameters
|
|
548
|
+
----------
|
|
549
|
+
project_id : str
|
|
550
|
+
The project ID or path, including namespace.
|
|
551
|
+
Examples: `123` or `namespace/project`.
|
|
552
|
+
package_name : str
|
|
553
|
+
The name of the generic package.
|
|
554
|
+
package_version : str
|
|
555
|
+
The version of the generic package that is deleted
|
|
556
|
+
|
|
557
|
+
Return
|
|
558
|
+
------
|
|
559
|
+
int
|
|
560
|
+
Zero if everything went fine, non-zero coke otherwise.
|
|
561
|
+
"""
|
|
562
|
+
ret = 1
|
|
563
|
+
package_id = self.get_id(project_id, package_name, package_version)
|
|
564
|
+
if package_id > 0:
|
|
565
|
+
ret = self.delete(
|
|
566
|
+
"projects", parse.quote_plus(project_id), "packages", str(package_id)
|
|
453
567
|
)
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
568
|
+
return ret
|
|
569
|
+
|
|
570
|
+
def delete_file(
|
|
571
|
+
self,
|
|
572
|
+
project_id: str,
|
|
573
|
+
package_name: str,
|
|
574
|
+
package_version: str,
|
|
575
|
+
filename: str,
|
|
576
|
+
) -> int:
|
|
577
|
+
"""
|
|
578
|
+
Deletes a file from a GitLab generic package.
|
|
579
|
+
|
|
580
|
+
Parameters
|
|
581
|
+
----------
|
|
582
|
+
project_id : str
|
|
583
|
+
The project ID or path, including namespace.
|
|
584
|
+
Examples: `123` or `namespace/project`.
|
|
585
|
+
package_name : str
|
|
586
|
+
The name of the generic package.
|
|
587
|
+
package_version : str
|
|
588
|
+
The version of the generic package
|
|
589
|
+
filename : str
|
|
590
|
+
The path of the file to be deleted in the package.
|
|
591
|
+
|
|
592
|
+
Return
|
|
593
|
+
------
|
|
594
|
+
int
|
|
595
|
+
Zero if everything went fine, non-zero coke otherwise.
|
|
596
|
+
"""
|
|
597
|
+
ret = 1
|
|
598
|
+
package_id = self.get_id(project_id, package_name, package_version)
|
|
599
|
+
if package_id > 0:
|
|
600
|
+
package_files = self.get_files(project_id, package_id)
|
|
601
|
+
file_id = package_files.get(filename)
|
|
602
|
+
if file_id and file_id.get("id"):
|
|
603
|
+
file_id = file_id.get("id")
|
|
604
|
+
ret = self._delete_file(project_id, package_id, file_id)
|
|
605
|
+
return ret
|
|
606
|
+
|
|
607
|
+
def _delete_file(self, project_id: str, package_id: int, file_id: int) -> int:
|
|
608
|
+
ret = 1
|
|
609
|
+
logger.info(
|
|
610
|
+
"Deleting file %d from package %d from project %s",
|
|
611
|
+
file_id,
|
|
612
|
+
package_id,
|
|
613
|
+
project_id,
|
|
614
|
+
)
|
|
615
|
+
ret = self.delete(
|
|
616
|
+
"projects",
|
|
617
|
+
parse.quote_plus(project_id),
|
|
618
|
+
"packages",
|
|
619
|
+
str(package_id),
|
|
620
|
+
"package_files",
|
|
621
|
+
str(file_id),
|
|
622
|
+
)
|
|
457
623
|
return ret
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: glpkg
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.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
|
|
@@ -15,6 +15,7 @@ Dynamic: license-file
|
|
|
15
15
|
|
|
16
16
|
glpkg is a tool that makes it easy to work with [GitLab generic packages](https://docs.gitlab.com/user/packages/generic_packages/).
|
|
17
17
|
|
|
18
|
+
Generic package registries of GitLab projects are supported. Group registries are not, as the GitLab REST API for groups is very limited.
|
|
18
19
|
|
|
19
20
|
## Installation
|
|
20
21
|
|
|
@@ -26,7 +27,6 @@ pip install glpkg
|
|
|
26
27
|
|
|
27
28
|
To check the installation and version, run:
|
|
28
29
|
|
|
29
|
-
|
|
30
30
|
```bash
|
|
31
31
|
glpkg --version
|
|
32
32
|
```
|
|
@@ -117,6 +117,37 @@ glpkg upload --project 12345 --name mypackagename --version 1.0 --source upload
|
|
|
117
117
|
|
|
118
118
|
Note: a file in `upload` folder will be uploaded to the root of the package. If you want to upload the file to a `dir` folder in the package, make a structure in the upload folder, like `upload/dir/`.
|
|
119
119
|
|
|
120
|
+
### Delete a file from a generic package
|
|
121
|
+
|
|
122
|
+
To delete a file from a specific generic package, run
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
glpkg delete --project 12345 --name mypackagename --version 1.0 --file my-file.txt
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Where
|
|
129
|
+
- `12345` is your projects ID ([Find the Project ID](https://docs.gitlab.com/user/project/working_with_projects/#find-the-project-id)) or the path of the project (like `namespace/project`)
|
|
130
|
+
- `mypackagename` is the name of the generic package
|
|
131
|
+
- `1.0` is the version of the generic package from the file is deleted
|
|
132
|
+
- `my-file.txt` is the file that is deleted. Only relative paths are supported. Note that the package may contain multiple files with same name. In this case, one file of them is deleted.
|
|
133
|
+
|
|
134
|
+
The token that is used to delete files must have at least Maintainer role in the project.
|
|
135
|
+
|
|
136
|
+
### Delete a package version
|
|
137
|
+
|
|
138
|
+
To delete a specific generic package version, run
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
glpkg delete --project 12345 --name mypackagename --version 1.0
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Where
|
|
145
|
+
- `12345` is your projects ID ([Find the Project ID](https://docs.gitlab.com/user/project/working_with_projects/#find-the-project-id)) or the path of the project (like `namespace/project`)
|
|
146
|
+
- `mypackagename` is the name of the generic package
|
|
147
|
+
- `1.0` is the version of the generic package that is deleted
|
|
148
|
+
|
|
149
|
+
The token that is used to delete packages must have at least Maintainer role in the project.
|
|
150
|
+
|
|
120
151
|
### Use in GitLab pipelines
|
|
121
152
|
|
|
122
153
|
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`.
|
|
@@ -128,10 +159,3 @@ glpkg upload --ci --name mypackagename --version 1.0 --file my-file.txt
|
|
|
128
159
|
```
|
|
129
160
|
|
|
130
161
|
To use the `CI_JOB_TOKEN` with package registry of another projects, add `--project <otherproject ID>` argument. Remember that you may need to add [permissions for the CI_JOB_TOKEN](https://docs.gitlab.com/ci/jobs/ci_job_token/#control-job-token-access-to-your-project) in the other project.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
## Limitations
|
|
134
|
-
|
|
135
|
-
The tool is not perfect (yet) and has limitations. The following limitations are known, but more can exist:
|
|
136
|
-
|
|
137
|
-
- Only project registries are supported for now.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
gitlab/__init__.py,sha256=TXqR34k7YgbFTeeKfSOdxBH0lFaC3-SiZU5mTBL9GI0,105
|
|
2
|
+
gitlab/__main__.py,sha256=UPVixjP98KzJi9Ljth4yqqDTTaE3EfyAuj2xGCQAGys,586
|
|
3
|
+
gitlab/cli_handler.py,sha256=02Yc3yE3jNMGTx_HvUu0rYH5NwxrnTRaaeAGMFh8Mq8,14273
|
|
4
|
+
gitlab/packages.py,sha256=wP0hRLad09LoE9uinUljrvGoBt6KfP49QESqOpxLihQ,20255
|
|
5
|
+
glpkg-1.4.0.dist-info/licenses/LICENSE.md,sha256=josGXvZq628dNS0Iru58-DPE7dRpDXzjJxKKT35103g,1065
|
|
6
|
+
glpkg-1.4.0.dist-info/METADATA,sha256=PX4jWVi_HCznrjQ9OztSYqYurdqv1-qHXKbkRiIUVlU,7678
|
|
7
|
+
glpkg-1.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
8
|
+
glpkg-1.4.0.dist-info/entry_points.txt,sha256=xHPZwx2oShYDZ3AyH7WSIvuhFMssy7QLlQk-JAbje_w,46
|
|
9
|
+
glpkg-1.4.0.dist-info/top_level.txt,sha256=MvIaP8p_Oaf4gO_hXmHkX-5y2deHLp1pe6tJR3ukQ6o,7
|
|
10
|
+
glpkg-1.4.0.dist-info/RECORD,,
|
glpkg-1.3.0.dist-info/RECORD
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
gitlab/__init__.py,sha256=ZXRwtUQEDE9bdewmwKYCjMAhm5KsuvAe525_QBiWLHY,105
|
|
2
|
-
gitlab/__main__.py,sha256=UPVixjP98KzJi9Ljth4yqqDTTaE3EfyAuj2xGCQAGys,586
|
|
3
|
-
gitlab/cli_handler.py,sha256=2P3R-yYkT6D5CjNCAEd9FC9HYI09aJ_eyMSMsOHtZzs,12289
|
|
4
|
-
gitlab/packages.py,sha256=50CbLdYxj0r6l8J40TxpIo2hTkKlv1BCFywFR6lg2nc,14785
|
|
5
|
-
glpkg-1.3.0.dist-info/licenses/LICENSE.md,sha256=josGXvZq628dNS0Iru58-DPE7dRpDXzjJxKKT35103g,1065
|
|
6
|
-
glpkg-1.3.0.dist-info/METADATA,sha256=4bQDKmHrf2da0eQ2hjfQKKItoirfcIr1VbWa3hwxbwY,6362
|
|
7
|
-
glpkg-1.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
8
|
-
glpkg-1.3.0.dist-info/entry_points.txt,sha256=xHPZwx2oShYDZ3AyH7WSIvuhFMssy7QLlQk-JAbje_w,46
|
|
9
|
-
glpkg-1.3.0.dist-info/top_level.txt,sha256=MvIaP8p_Oaf4gO_hXmHkX-5y2deHLp1pe6tJR3ukQ6o,7
|
|
10
|
-
glpkg-1.3.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|