glpkg 1.2.0__tar.gz → 1.4.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: glpkg
3
- Version: 1.2.0
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
  ```
@@ -105,10 +105,49 @@ Where:
105
105
  - `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`)
106
106
  - `mypackagename` is the name of the generic package
107
107
  - `1.0` is the version of the generic package to which the file is uploaded
108
- - `my-file.txt` is the file that is uploaded to the generic package. Currently, only relative paths are supported, and the relative path (e.g. `folder/file.txt`) is preserved when uploading the file to the registry.
108
+ - `my-file.txt` is the file that is uploaded to the generic package. Only relative paths are supported, and the relative path (e.g. `folder/file.txt`) is preserved when uploading the file to the package.
109
109
 
110
110
  > A GitLab generic package may have multiple files with the same file name. However, it likely is not a great idea, as they cannot be downloaded separately from the GitLab API.
111
111
 
112
+ To upload multiple files, or to upload a single file from a different directory, use `--source` argument. If no `--file` argument is set, all of the files in the source directory are uploaded, recursively. As an example, to upload all files from a `upload` folder to the package:
113
+
114
+ ```bash
115
+ glpkg upload --project 12345 --name mypackagename --version 1.0 --source upload
116
+ ```
117
+
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
+
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
+
112
151
  ### Use in GitLab pipelines
113
152
 
114
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`.
@@ -120,11 +159,3 @@ glpkg upload --ci --name mypackagename --version 1.0 --file my-file.txt
120
159
  ```
121
160
 
122
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.
123
-
124
-
125
- ## Limitations
126
-
127
- The tool is not perfect (yet) and has limitations. The following limitations are known, but more can exist:
128
-
129
- - Uploading files must be done one-by-one.
130
- - Only project registries are supported for now.
@@ -2,6 +2,7 @@
2
2
 
3
3
  glpkg is a tool that makes it easy to work with [GitLab generic packages](https://docs.gitlab.com/user/packages/generic_packages/).
4
4
 
5
+ Generic package registries of GitLab projects are supported. Group registries are not, as the GitLab REST API for groups is very limited.
5
6
 
6
7
  ## Installation
7
8
 
@@ -13,7 +14,6 @@ pip install glpkg
13
14
 
14
15
  To check the installation and version, run:
15
16
 
16
-
17
17
  ```bash
18
18
  glpkg --version
19
19
  ```
@@ -92,10 +92,49 @@ Where:
92
92
  - `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`)
93
93
  - `mypackagename` is the name of the generic package
94
94
  - `1.0` is the version of the generic package to which the file is uploaded
95
- - `my-file.txt` is the file that is uploaded to the generic package. Currently, only relative paths are supported, and the relative path (e.g. `folder/file.txt`) is preserved when uploading the file to the registry.
95
+ - `my-file.txt` is the file that is uploaded to the generic package. Only relative paths are supported, and the relative path (e.g. `folder/file.txt`) is preserved when uploading the file to the package.
96
96
 
97
97
  > A GitLab generic package may have multiple files with the same file name. However, it likely is not a great idea, as they cannot be downloaded separately from the GitLab API.
98
98
 
99
+ To upload multiple files, or to upload a single file from a different directory, use `--source` argument. If no `--file` argument is set, all of the files in the source directory are uploaded, recursively. As an example, to upload all files from a `upload` folder to the package:
100
+
101
+ ```bash
102
+ glpkg upload --project 12345 --name mypackagename --version 1.0 --source upload
103
+ ```
104
+
105
+ 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/`.
106
+
107
+ ### Delete a file from a generic package
108
+
109
+ To delete a file from a specific generic package, run
110
+
111
+ ```bash
112
+ glpkg delete --project 12345 --name mypackagename --version 1.0 --file my-file.txt
113
+ ```
114
+
115
+ Where
116
+ - `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`)
117
+ - `mypackagename` is the name of the generic package
118
+ - `1.0` is the version of the generic package from the file is deleted
119
+ - `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.
120
+
121
+ The token that is used to delete files must have at least Maintainer role in the project.
122
+
123
+ ### Delete a package version
124
+
125
+ To delete a specific generic package version, run
126
+
127
+ ```bash
128
+ glpkg delete --project 12345 --name mypackagename --version 1.0
129
+ ```
130
+
131
+ Where
132
+ - `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`)
133
+ - `mypackagename` is the name of the generic package
134
+ - `1.0` is the version of the generic package that is deleted
135
+
136
+ The token that is used to delete packages must have at least Maintainer role in the project.
137
+
99
138
  ### Use in GitLab pipelines
100
139
 
101
140
  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`.
@@ -107,11 +146,3 @@ glpkg upload --ci --name mypackagename --version 1.0 --file my-file.txt
107
146
  ```
108
147
 
109
148
  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.
110
-
111
-
112
- ## Limitations
113
-
114
- The tool is not perfect (yet) and has limitations. The following limitations are known, but more can exist:
115
-
116
- - Uploading files must be done one-by-one.
117
- - Only project registries are supported for now.
@@ -31,3 +31,9 @@ where = ["src"]
31
31
  [tool.pytest.ini_options]
32
32
  pythonpath = ["src"]
33
33
  addopts = "--cov=src --cov-report term"
34
+
35
+ [tool.pylint.'MESSAGES CONTROL']
36
+ min-public-methods = 1
37
+ max-args = 6
38
+ max-positional-arguments = 6
39
+ ignore = ["build", "test"]
@@ -0,0 +1,5 @@
1
+ """GitLab packages initialization module"""
2
+
3
+ from gitlab.packages import Packages
4
+
5
+ __version__ = "1.4.0"
@@ -0,0 +1,29 @@
1
+ """GitLab packages main module"""
2
+
3
+ import logging
4
+ import sys
5
+
6
+ from gitlab.cli_handler import CLIHandler
7
+
8
+
9
+ def cli() -> int:
10
+ """
11
+ Runs the main program of the glpkg.
12
+
13
+ Uses arguments from command line and executes the given command.
14
+
15
+ Return
16
+ ------
17
+ int
18
+ Zero when everything is fine, non-zero otherwise.
19
+ """
20
+ logging.basicConfig(
21
+ level=logging.DEBUG,
22
+ handlers=[logging.FileHandler("glpkg.log")], # , logging.StreamHandler()],
23
+ )
24
+ handler = CLIHandler()
25
+ return handler.do_it()
26
+
27
+
28
+ if __name__ == "__main__":
29
+ sys.exit(cli())
@@ -0,0 +1,419 @@
1
+ """CLI handler for glpkg"""
2
+
3
+ import argparse
4
+ from netrc import netrc
5
+ import os
6
+ import sys
7
+ import urllib
8
+ from gitlab import Packages, __version__
9
+
10
+
11
+ class CLIHandler:
12
+ """Class to parse CLI arguments and run the requested command"""
13
+
14
+ def __init__(self):
15
+ """
16
+ Creates a new instance of CLIHandler.
17
+
18
+ Parses the arguments from command line and prepares everything
19
+ to be ready to executed. Just use do_it to run it!
20
+ """
21
+ parser = argparse.ArgumentParser(
22
+ description="Toolbox for GitLab generic packages"
23
+ )
24
+ parser.add_argument("-v", "--version", action="store_true")
25
+ parser.set_defaults(action=self._print_version)
26
+ subparsers = parser.add_subparsers()
27
+ list_parser = subparsers.add_parser(
28
+ name="list",
29
+ description="Lists the available version of a package from the "
30
+ "package registry.",
31
+ )
32
+ self._register_list_parser(list_parser)
33
+ download_parser = subparsers.add_parser(
34
+ name="download",
35
+ description="Downloads all files from a specific package version "
36
+ "to the current directory.",
37
+ )
38
+ self._register_download_parser(download_parser)
39
+ upload_parser = subparsers.add_parser(
40
+ name="upload", description="Uploads file to a specific package version."
41
+ )
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)
50
+ self.args = parser.parse_args()
51
+
52
+ def _print_version(self, _args: argparse.Namespace) -> int:
53
+ """
54
+ A handler for printing the version of the tool to the console.
55
+
56
+ Parameters
57
+ ----------
58
+ _args : argparse.Namespace
59
+ Unused.
60
+
61
+ Return
62
+ ------
63
+ int
64
+ Zero when printing to console succeeded.
65
+ """
66
+ print(__version__)
67
+ return 0
68
+
69
+ def do_it(self) -> int:
70
+ """
71
+ Executes the requested command.
72
+
73
+ In case of error, prints to stderr.
74
+
75
+ Return
76
+ ------
77
+ int
78
+ Zero when everything went fine, non-zero otherwise.
79
+ """
80
+ ret = 1
81
+ try:
82
+ ret = self.args.action(self.args)
83
+ except urllib.error.HTTPError as e:
84
+ # GitLab API returns 404 when a resource is not found
85
+ # but also when the user has no access to the resource
86
+ print("Oops! Something did go wrong.", file=sys.stderr)
87
+ print(e, file=sys.stderr)
88
+ print(
89
+ "Note that Error 404 may also indicate authentication issues with GitLab API.",
90
+ file=sys.stderr,
91
+ )
92
+ print("Check your arguments and credentials.", file=sys.stderr)
93
+ return ret
94
+
95
+ def _register_common_arguments(self, parser: argparse.ArgumentParser) -> None:
96
+ """
97
+ Registers common arguments to the parser:
98
+ - host
99
+ - ci
100
+ - project
101
+ - name
102
+ - token
103
+ - netrc
104
+
105
+ Parameters
106
+ ----------
107
+ parser:
108
+ The argparser where to register the common arguments.
109
+ """
110
+ group = parser.add_mutually_exclusive_group()
111
+ group.add_argument(
112
+ "-H",
113
+ "--host",
114
+ default="gitlab.com",
115
+ type=str,
116
+ help="The host address of GitLab instance without scheme, "
117
+ "for example gitlab.com. Note that only https scheme is supported.",
118
+ )
119
+ group.add_argument(
120
+ "-c",
121
+ "--ci",
122
+ action="store_true",
123
+ help="Use this in GitLab jobs. In this case CI_SERVER_HOST, CI_PROJECT_ID, "
124
+ "and CI_JOB_TOKEN variables from the environment are used. --project and --token "
125
+ "can be used to override project ID and the CI_JOB_TOKEN to a personal or project "
126
+ "access token.",
127
+ )
128
+ parser.add_argument(
129
+ "-p",
130
+ "--project",
131
+ type=str,
132
+ help="The project ID or path. For example 123456 or namespace/project.",
133
+ )
134
+ parser.add_argument("-n", "--name", type=str, help="The package name.")
135
+ group2 = parser.add_mutually_exclusive_group()
136
+ group2.add_argument(
137
+ "-t",
138
+ "--token",
139
+ type=str,
140
+ help="Private or project access token that is used to authenticate with "
141
+ "the package registry. Leave empty if the registry is public. The token "
142
+ "must have 'read API' or 'API' scope.",
143
+ )
144
+ group2.add_argument(
145
+ "--netrc",
146
+ action="store_true",
147
+ help="Set to use a token from .netrc file (~/.netrc) for the host. The "
148
+ ".netrc username is ignored due to API restrictions. PRIVATE-TOKEN is used "
149
+ "instead. Note that .netrc file access rights must be correct.",
150
+ )
151
+
152
+ def _register_download_parser(self, parser: argparse.ArgumentParser):
153
+ """
154
+ Registers the download command related arguments to the parser:
155
+ - version
156
+ - file
157
+ - destination
158
+ - action
159
+
160
+ Additionally, registers the common args.
161
+
162
+ Parameters
163
+ ----------
164
+ parser:
165
+ The argparser where to register the download arguments.
166
+ """
167
+ self._register_common_arguments(parser)
168
+ parser.add_argument("-v", "--version", type=str, help="The package version.")
169
+ parser.add_argument(
170
+ "-f",
171
+ "--file",
172
+ type=str,
173
+ help="The file to download from the package. If not defined, all "
174
+ "files are downloaded.",
175
+ )
176
+ parser.add_argument(
177
+ "-d",
178
+ "--destination",
179
+ default="",
180
+ type=str,
181
+ help="The path where the file(s) are downloaded. If not defined, "
182
+ "the current working directory is used.",
183
+ )
184
+ parser.set_defaults(action=self._download_handler)
185
+
186
+ def _args(self, args) -> tuple[str, str, str, str, str]:
187
+ """
188
+ Returns the connection parameters according to the args
189
+
190
+ Parameters
191
+ ----------
192
+ args:
193
+ The args that are used to determined the connection parameters
194
+
195
+ Returns
196
+ -------
197
+ host : str
198
+ The GitLab host name
199
+ project : str
200
+ The Project ID or name to use
201
+ name : str
202
+ The package name
203
+ token_user : str
204
+ The token user according to the args. If ci is used, returns `JOB-TOKEN`, else
205
+ `PRIVATE-TOKEN`.
206
+ token : str
207
+ The token according to the args. If token is set, returns it. If netrc is set,
208
+ reads the token from the .netrc file. If ci is set, reads the environment
209
+ variable CI_JOB_TOKEN. Otherwise returns None.
210
+ """
211
+ if args.ci:
212
+ host = os.environ["CI_SERVER_HOST"]
213
+ project = os.environ["CI_PROJECT_ID"]
214
+ token = os.environ["CI_JOB_TOKEN"]
215
+ token_user = "JOB-TOKEN"
216
+ if args.project:
217
+ project = args.project
218
+ if args.token:
219
+ token = args.token
220
+ token_user = "PRIVATE-TOKEN"
221
+ else:
222
+ host = args.host
223
+ project = args.project
224
+ token = args.token
225
+ token_user = "PRIVATE-TOKEN"
226
+ if args.netrc:
227
+ _, _, token = netrc().authenticators(host)
228
+ token_user = "PRIVATE-TOKEN"
229
+ name = args.name
230
+ return host, project, name, token_user, token
231
+
232
+ def _download_handler(self, args: argparse.Namespace) -> int:
233
+ """
234
+ Downloads package file(s) from GitLab package registry.
235
+
236
+ Parameters
237
+ ----------
238
+ args : argparse.Namespace
239
+ The parsed arguments
240
+
241
+ Returns
242
+ -------
243
+ int
244
+ Zero if everything goes well, non-zero otherwise
245
+ """
246
+ ret = 1
247
+ host, project, name, token_user, token = self._args(args)
248
+ version = args.version
249
+ destination = args.destination
250
+ packages = Packages(host, token_user, token)
251
+ package_id = packages.get_id(project, name, version)
252
+ if package_id:
253
+ files = []
254
+ if args.file:
255
+ files.append(args.file)
256
+ else:
257
+ files = packages.get_files(project, package_id).keys()
258
+ for file in files:
259
+ ret = packages.download_file(project, name, version, file, destination)
260
+ if ret:
261
+ print("Failed to download file " + file)
262
+ break
263
+ else:
264
+ print("No package " + name + " version " + version + " found!")
265
+ return ret
266
+
267
+ def _register_list_parser(self, parser: argparse.ArgumentParser):
268
+ """
269
+ Registers the list command related arguments to the parser:
270
+ - action
271
+
272
+ Additionally, registers the common args.
273
+
274
+ Parameters
275
+ ----------
276
+ parser:
277
+ The argparser where to register the list arguments.
278
+ """
279
+ self._register_common_arguments(parser)
280
+ parser.set_defaults(action=self._list_packages)
281
+
282
+ def _list_packages(self, args: argparse.Namespace) -> int:
283
+ """
284
+ List package versions from GitLab package registry.
285
+
286
+ Parameters
287
+ ----------
288
+ args : argparse.Namespace
289
+ The parsed arguments
290
+
291
+ Returns
292
+ -------
293
+ int
294
+ Zero if everything goes well, non-zero otherwise
295
+ """
296
+ host, project, name, token_user, token = self._args(args)
297
+ packages = Packages(host, token_user, token)
298
+ package_list = packages.get_versions(project, name)
299
+ print("Name" + "\t\t" + "Version")
300
+ for package in package_list:
301
+ print(package["name"] + "\t" + package["version"])
302
+
303
+ def _register_upload_parser(self, parser: argparse.ArgumentParser):
304
+ """
305
+ Registers the upload command related arguments to the parser:
306
+ - version
307
+ - file
308
+ - action
309
+
310
+ Additionally, registers the common args.
311
+
312
+ Parameters
313
+ ----------
314
+ parser:
315
+ The argparser where to register the upload arguments.
316
+ """
317
+ self._register_common_arguments(parser)
318
+ parser.add_argument("-v", "--version", type=str, help="The package version.")
319
+ parser.add_argument(
320
+ "-f",
321
+ "--file",
322
+ type=str,
323
+ help="The file to be uploaded, for example my_file.txt. Note that "
324
+ "only relative paths (to the source) are supported and the relative "
325
+ "path is preserved when uploading the file. If left undefined, all files "
326
+ "of the source directory are uploaded. For example --source=temp --file=myfile "
327
+ "will upload myfile to the GitLab generic package root. However using --source=. "
328
+ "(or omittinge source) --file=temp/myfile will upload the file to temp folder "
329
+ "in the GitLab package.",
330
+ )
331
+ parser.add_argument(
332
+ "-s",
333
+ "--source",
334
+ type=str,
335
+ default="",
336
+ help="The source directory of the uploaded file(s). Defaults to current"
337
+ "working directory.",
338
+ )
339
+ parser.set_defaults(action=self._upload)
340
+
341
+ def _upload(self, args: argparse.Namespace) -> int:
342
+ """
343
+ Uploads a file to a GitLab package registry
344
+
345
+ Parameters
346
+ ----------
347
+ args : argparse.Namespace
348
+ The arguments from command line
349
+
350
+ Returns
351
+ -------
352
+ int
353
+ Zero if everything went fine, non-zero otherwise.
354
+ """
355
+ ret = 0
356
+ host, project, name, token_user, token = self._args(args)
357
+ version = args.version
358
+ file = args.file
359
+ source = args.source
360
+ if file:
361
+ # Check if the uploaded file exists.
362
+ if not os.path.isfile(os.path.join(source, file)):
363
+ print("File " + file + " does not exist!")
364
+ ret = 1
365
+ if not 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)
419
+ return ret