]>
Commit | Line | Data |
---|---|---|
964aa579 | 1 | #!/usr/bin/python3 |
b792d887 MT |
2 | ############################################################################### |
3 | # # | |
4 | # Pakfire - The IPFire package management system # | |
5 | # Copyright (C) 2011 Pakfire development team # | |
6 | # # | |
7 | # This program is free software: you can redistribute it and/or modify # | |
8 | # it under the terms of the GNU General Public License as published by # | |
9 | # the Free Software Foundation, either version 3 of the License, or # | |
10 | # (at your option) any later version. # | |
11 | # # | |
12 | # This program is distributed in the hope that it will be useful, # | |
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # | |
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # | |
15 | # GNU General Public License for more details. # | |
16 | # # | |
17 | # You should have received a copy of the GNU General Public License # | |
18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. # | |
19 | # # | |
20 | ############################################################################### | |
47a4cb89 MT |
21 | |
22 | import argparse | |
68c0e769 | 23 | import datetime |
17563cd0 | 24 | import logging |
936f6b37 | 25 | import os |
b913e798 | 26 | import shutil |
17563cd0 | 27 | import signal |
47a4cb89 MT |
28 | import sys |
29 | ||
28d8677b | 30 | from . import _pakfire |
964aa579 | 31 | from . import client |
964aa579 | 32 | from . import daemon |
964aa579 | 33 | |
964aa579 MT |
34 | from .constants import * |
35 | from .i18n import _ | |
47a4cb89 | 36 | |
47a4cb89 | 37 | class Cli(object): |
e75d1e3c MT |
38 | default_path = "/" |
39 | ||
67b139fb MT |
40 | def parse_cli(self): |
41 | parser = argparse.ArgumentParser( | |
42 | description = _("Pakfire command line interface"), | |
43 | ) | |
44 | subparsers = parser.add_subparsers() | |
45 | ||
46 | # Add common arguments | |
47 | self._add_common_arguments(parser) | |
48 | ||
5f4c668a MT |
49 | parser.add_argument("--arch", "-a", nargs="?", |
50 | help=_("Run pakfire for the given architecture")) | |
67b139fb MT |
51 | parser.add_argument("--root", metavar="PATH", default="/", |
52 | help=_("The path where pakfire should operate in")) | |
53 | ||
54 | # check | |
55 | check = subparsers.add_parser("check", help=_("Check the system for any errors")) | |
56 | check.set_defaults(func=self.handle_check) | |
57 | ||
67b139fb MT |
58 | # clean |
59 | clean = subparsers.add_parser("clean", help=_("Cleanup all temporary files")) | |
60 | clean.set_defaults(func=self.handle_clean) | |
61 | ||
8d86a58f MT |
62 | # execute |
63 | execute = subparsers.add_parser("execute", | |
64 | help=_("Executes a command in the pakfire environment (useful for development)")) | |
1577db9b | 65 | execute.add_argument("--bind", action="append", default=[], dest="binds", |
8d86a58f MT |
66 | help=_("Bind-mounts the given directory")) |
67 | execute.add_argument("command", nargs=argparse.REMAINDER) | |
68 | execute.set_defaults(func=self.handle_execute) | |
69 | ||
67b139fb MT |
70 | # info |
71 | info = subparsers.add_parser("info", | |
72 | help=_("Print some information about the given package(s)")) | |
31ed921f MT |
73 | info.add_argument("--long", action="store_true", |
74 | help=_("Show more information")) | |
4952c631 | 75 | info.add_argument("package", help=_("Give at least the name of one package")) |
67b139fb MT |
76 | info.set_defaults(func=self.handle_info) |
77 | ||
78 | # install | |
79 | install = subparsers.add_parser("install", | |
80 | help=_("Install one or more packages to the system")) | |
81 | install.add_argument("package", nargs="+", | |
82 | help=_("Give name of at least one package to install")) | |
c572abee | 83 | install.add_argument("--without-recommended", action="store_true", |
67b139fb | 84 | help=_("Don't install recommended packages")) |
a005d132 MT |
85 | install.add_argument("--allow-uninstall", action="store_true", |
86 | help=_("Allow uninstalling packages")) | |
87 | install.add_argument("--allow-downgrade", action="store_true", | |
88 | help=_("Allow downgrading packages")) | |
67b139fb MT |
89 | install.set_defaults(func=self.handle_install) |
90 | ||
91 | # provides | |
92 | provides = subparsers.add_parser("provides", | |
93 | help=_("Get a list of packages that provide a given file or feature")) | |
54334355 | 94 | provides.add_argument("pattern", help=_("File or feature to search for")) |
67b139fb MT |
95 | provides.set_defaults(func=self.handle_provides) |
96 | ||
8201cef2 MT |
97 | # requires |
98 | requires = subparsers.add_parser("requires", | |
99 | help=_("Get a list of packages that require this dependency")) | |
e1bcca48 | 100 | requires.add_argument("pattern", help=_("File or feature to search for")) |
8201cef2 MT |
101 | requires.set_defaults(func=self.handle_requires) |
102 | ||
67b139fb MT |
103 | # reinstall |
104 | reinstall = subparsers.add_parser("reinstall", | |
105 | help=_("Reinstall one or more packages")) | |
106 | reinstall.add_argument("package", nargs="+", | |
107 | help=_("Give name of at least one package to reinstall")) | |
108 | reinstall.set_defaults(func=self.handle_reinstall) | |
109 | ||
110 | # remove | |
111 | remove = subparsers.add_parser("remove", | |
112 | help=_("Remove one or more packages from the system")) | |
113 | remove.add_argument("package", nargs="+", | |
114 | help=_("Give name of at least one package to remove")) | |
d798b956 MT |
115 | remove.add_argument("--keep-dependencies", action="store_true", |
116 | help=_("Keep dependencies installed")) | |
67b139fb MT |
117 | remove.set_defaults(func=self.handle_remove) |
118 | ||
119 | # repolist | |
120 | repolist = subparsers.add_parser("repolist", | |
121 | help=_("List all currently enabled repositories")) | |
122 | repolist.set_defaults(func=self.handle_repolist) | |
123 | ||
67b139fb MT |
124 | # search |
125 | search = subparsers.add_parser("search", help=_("Search for a given pattern")) | |
126 | search.add_argument("pattern", help=_("A pattern to search for")) | |
127 | search.set_defaults(func=self.handle_search) | |
128 | ||
7b6f9f7b MT |
129 | # sync |
130 | sync = subparsers.add_parser("sync", | |
131 | help=_("Sync all installed with the latest one in the distribution")) | |
550c7c1f MT |
132 | sync.add_argument("--keep-orphaned", action="store_true", |
133 | help=_("Keep orphaned packages")) | |
7b6f9f7b MT |
134 | sync.set_defaults(func=self.handle_sync) |
135 | ||
67b139fb MT |
136 | # update |
137 | update = subparsers.add_parser("update", | |
138 | help=_("Update the whole system or one specific package")) | |
139 | update.add_argument("package", nargs="*", | |
140 | help=_("Give a name of a package to update or leave emtpy for all")) | |
c9b18fbd MT |
141 | update.add_argument("--exclude", "-x", nargs="+", default=[], |
142 | help=_("Exclude package from update")) | |
a005d132 MT |
143 | update.add_argument("--allow-uninstall", action="store_true", |
144 | help=_("Allow uninstalling packages")) | |
145 | update.add_argument("--allow-downgrade", action="store_true", | |
146 | help=_("Allow downgrading packages")) | |
67b139fb | 147 | update.set_defaults(func=self.handle_update) |
685cb819 | 148 | |
67b139fb | 149 | return parser.parse_args() |
685cb819 | 150 | |
95a7e4d9 MT |
151 | def _add_common_arguments(self, parser, offline_switch=True): |
152 | parser.add_argument("--version", action="version", | |
50381f5c MT |
153 | version="%(prog)s " + PAKFIRE_VERSION) |
154 | ||
95a7e4d9 | 155 | parser.add_argument("-v", "--verbose", action="store_true", |
47a4cb89 MT |
156 | help=_("Enable verbose output.")) |
157 | ||
95a7e4d9 | 158 | parser.add_argument("-c", "--config", nargs="?", |
47a4cb89 MT |
159 | help=_("Path to a configuration file to load.")) |
160 | ||
95a7e4d9 | 161 | parser.add_argument("--disable-repo", nargs="*", metavar="REPO", |
685cb819 | 162 | help=_("Disable a repository temporarily."), default=[]) |
f781b1ab | 163 | |
95a7e4d9 | 164 | parser.add_argument("--enable-repo", nargs="*", metavar="REPO", |
685cb819 | 165 | help=_("Enable a repository temporarily."), default=[]) |
f9a012a8 | 166 | |
c62d93f1 | 167 | if offline_switch: |
95a7e4d9 | 168 | parser.add_argument("--offline", action="store_true", |
c62d93f1 | 169 | help=_("Run pakfire in offline mode.")) |
6a509182 | 170 | |
67b139fb | 171 | def pakfire(self, ns): |
28d8677b | 172 | p = _pakfire.Pakfire( |
5f4c668a | 173 | arch=ns.arch, |
8a36f1f2 | 174 | conf=ns.config, |
e75d1e3c MT |
175 | path=ns.root if "root" in ns else self.default_path, |
176 | offline=ns.offline, | |
177 | ) | |
05fb1da0 | 178 | |
67b139fb | 179 | # Disable repositories. |
8ef6c388 MT |
180 | for repo_name in ns.disable_repo: |
181 | repo = p.get_repo(repo_name) | |
182 | repo.enabled = False | |
67b139fb MT |
183 | |
184 | # Enable repositories. | |
8ef6c388 MT |
185 | for repo_name in ns.enable_repo: |
186 | repo = p.get_repo(repo_name) | |
187 | repo.enabled = True | |
67b139fb MT |
188 | |
189 | return p | |
995ed2dd | 190 | |
8d86a58f MT |
191 | def handle_execute(self, ns): |
192 | pakfire = self.pakfire(ns) | |
193 | ||
194 | # Bind-mount everything | |
195 | for bind in ns.binds: | |
196 | pakfire.bind(bind) | |
197 | ||
59b9d1c2 MT |
198 | # Log everything to the console |
199 | def logging_callback(level, line): | |
200 | if level >= logging.ERROR: | |
201 | sys.stderr.write("%s\n" % line) | |
202 | else: | |
203 | sys.stdout.write("%s\n" % line) | |
204 | ||
205 | return pakfire.execute(ns.command, logging_callback=logging_callback) | |
8d86a58f | 206 | |
47a4cb89 | 207 | def run(self): |
95a7e4d9 | 208 | args = self.parse_cli() |
74b61c5c | 209 | assert args.func, "Argument function not defined" |
47a4cb89 | 210 | |
17563cd0 MT |
211 | try: |
212 | return args.func(args) | |
213 | ||
214 | except KeyboardInterrupt: | |
17563cd0 MT |
215 | return 128 + signal.SIGINT |
216 | ||
27160734 | 217 | except DependencyError as e: |
9b64412f MT |
218 | print(_("One or more dependencies could not been resolved")) |
219 | print("") # empty line | |
27160734 MT |
220 | |
221 | # This exception provides a list of all problems | |
222 | problems, = e.args | |
223 | ||
224 | # List all problems | |
225 | for problem in problems: | |
9b64412f | 226 | print(" * %s" % problem) |
27160734 | 227 | |
9b64412f | 228 | print(" %s" % _("Possible solutions are:")) |
27160734 | 229 | for solution in problem.solutions: |
9b64412f | 230 | print(" * %s" % solution) |
27160734 MT |
231 | |
232 | # Add another empty line | |
9b64412f | 233 | print("") |
27160734 MT |
234 | |
235 | return 4 | |
236 | ||
17563cd0 MT |
237 | # Catch all errors and show a user-friendly error message. |
238 | except Error as e: | |
9b64412f MT |
239 | print(_("An error has occured when running Pakfire")) |
240 | print(_("%s: %s") % (e.__class__.__name__, e.message)) | |
17563cd0 MT |
241 | |
242 | return e.exit_code | |
47a4cb89 | 243 | |
145f61fe | 244 | def handle_info(self, ns): |
74fe39d3 MT |
245 | p = self.pakfire(ns) |
246 | ||
4952c631 | 247 | for pkg in p.search(ns.package, name_only=True): |
74fe39d3 MT |
248 | s = pkg.dump(long=ns.long) |
249 | print(s) | |
47a4cb89 | 250 | |
279509bc | 251 | def handle_search(self, ns): |
74fe39d3 | 252 | p = self.pakfire(ns) |
2b2d5b1b | 253 | |
74fe39d3 MT |
254 | for pkg in p.search(ns.pattern): |
255 | # Skip any -debuginfo packages | |
256 | if pkg.name.endswith("-debuginfo"): | |
257 | continue | |
258 | ||
259 | print("%-24s: %s" % (pkg.name, pkg.summary)) | |
47a4cb89 | 260 | |
b7e72fe2 MT |
261 | def handle_update(self, ns): |
262 | p = self.pakfire(ns) | |
263 | p.update( | |
264 | ns.package, | |
c9b18fbd | 265 | excludes=ns.exclude, |
a005d132 MT |
266 | allow_uninstall=ns.allow_uninstall, |
267 | allow_downgrade=ns.allow_downgrade, | |
b7e72fe2 | 268 | ) |
d314d72b | 269 | |
7b6f9f7b | 270 | def handle_sync(self, ns): |
550c7c1f | 271 | self.pakfire(ns).sync(keep_orphaned=ns.keep_orphaned) |
279509bc | 272 | |
279509bc | 273 | def handle_install(self, ns): |
b7e72fe2 | 274 | p = self.pakfire(ns) |
a005d132 MT |
275 | p.install( |
276 | ns.package, | |
277 | without_recommended=ns.without_recommended, | |
278 | allow_uninstall=ns.allow_uninstall, | |
279 | allow_downgrade=ns.allow_downgrade, | |
280 | ) | |
279509bc MT |
281 | |
282 | def handle_reinstall(self, ns): | |
283 | with self.pakfire(ns) as p: | |
b546c786 MT |
284 | transaction = p.reinstall(ns.package) |
285 | ||
286 | # Execute the transaction | |
287 | self._execute_transaction(transaction) | |
279509bc MT |
288 | |
289 | def handle_remove(self, ns): | |
b7e72fe2 | 290 | p = self.pakfire(ns) |
d798b956 | 291 | p.erase(ns.package, keep_dependencies=ns.keep_dependencies) |
279509bc MT |
292 | |
293 | def handle_provides(self, ns, long=False): | |
74fe39d3 MT |
294 | for pkg in self.pakfire(ns).whatprovides(ns.pattern): |
295 | s = pkg.dump(long=long) | |
296 | print(s) | |
279509bc | 297 | |
8201cef2 MT |
298 | def handle_requires(self, ns): |
299 | for pkg in self.pakfire(ns).whatrequires(ns.pattern): | |
300 | s = pkg.dump(long=True) | |
301 | print(s) | |
302 | ||
279509bc | 303 | def handle_repolist(self, ns): |
74fe39d3 MT |
304 | p = self.pakfire(ns) |
305 | ||
306 | FORMAT = " %-20s %8s %12s %12s " | |
307 | title = FORMAT % (_("Repository"), _("Enabled"), _("Priority"), _("Packages")) | |
308 | print(title) | |
309 | print("=" * len(title)) # spacing line | |
279509bc | 310 | |
74fe39d3 MT |
311 | for repo in p.repos: |
312 | print(FORMAT % (repo.name, repo.enabled, repo.priority, len(repo))) | |
279509bc MT |
313 | |
314 | def handle_clean(self, ns): | |
9b64412f | 315 | print(_("Cleaning up everything...")) |
31267a64 | 316 | |
cb6d631c MT |
317 | p = self.pakfire(ns) |
318 | p.clean() | |
c901e1f8 | 319 | |
279509bc | 320 | def handle_check(self, ns): |
d37d8d56 | 321 | self.pakfire(ns).check() |
995ed2dd | 322 | |
47a4cb89 | 323 | |
68c0e769 | 324 | class CliKey(Cli): |
0a89bb8a MT |
325 | def parse_cli(self): |
326 | parser = argparse.ArgumentParser( | |
327 | description = _("Pakfire key command line interface"), | |
68c0e769 | 328 | ) |
0a89bb8a | 329 | subparsers = parser.add_subparsers() |
68c0e769 | 330 | |
0a89bb8a MT |
331 | # Add common arguments |
332 | self._add_common_arguments(parser) | |
68c0e769 | 333 | |
b9b6a13e MT |
334 | # delete |
335 | delete = subparsers.add_parser("delete", help=_("Delete a key from the local keyring")) | |
336 | delete.add_argument("fingerprint", nargs="+", help=_("The fingerprint of the key to delete")) | |
337 | delete.set_defaults(func=self.handle_delete) | |
338 | ||
7b526dd2 MT |
339 | # export |
340 | export = subparsers.add_parser("export", help=_("Export a key to a file")) | |
341 | export.add_argument("fingerprint", nargs=1, | |
342 | help=_("The fingerprint of the key to export")) | |
343 | export.add_argument("--filename", nargs="*", help=_("Write the key to this file")) | |
344 | export.add_argument("--secret", action="store_true", | |
345 | help=_("Export the secret key")) | |
346 | export.set_defaults(func=self.handle_export) | |
347 | ||
d36b1022 | 348 | # import |
7b526dd2 MT |
349 | _import = subparsers.add_parser("import", help=_("Import a key from file")) |
350 | _import.add_argument("filename", nargs="+", help=_("Filename of that key to import")) | |
351 | _import.set_defaults(func=self.handle_import) | |
352 | ||
0a89bb8a MT |
353 | # generate |
354 | generate = subparsers.add_parser("generate", help=_("Import a key from file")) | |
355 | generate.add_argument("--realname", nargs=1, | |
356 | help=_("The real name of the owner of this key")) | |
357 | generate.add_argument("--email", nargs=1, | |
358 | help=_("The email address of the owner of this key")) | |
359 | generate.set_defaults(func=self.handle_generate) | |
360 | ||
361 | # list | |
362 | list = subparsers.add_parser("list", help=_("List all imported keys")) | |
363 | list.set_defaults(func=self.handle_list) | |
364 | ||
0a89bb8a | 365 | return parser.parse_args() |
68c0e769 | 366 | |
68c0e769 MT |
367 | def parse_command_sign(self): |
368 | # Implement the "sign" command. | |
369 | sub_sign = self.sub_commands.add_parser("sign", | |
370 | help=_("Sign one or more packages.")) | |
371 | sub_sign.add_argument("--key", "-k", nargs=1, | |
372 | help=_("Key that is used sign the package(s).")) | |
373 | sub_sign.add_argument("package", nargs="+", | |
374 | help=_("Package(s) to sign.")) | |
375 | sub_sign.add_argument("action", action="store_const", const="sign") | |
376 | ||
377 | def parse_command_verify(self): | |
378 | # Implement the "verify" command. | |
379 | sub_verify = self.sub_commands.add_parser("verify", | |
380 | help=_("Verify one or more packages.")) | |
381 | #sub_verify.add_argument("--key", "-k", nargs=1, | |
382 | # help=_("Key that is used verify the package(s).")) | |
383 | sub_verify.add_argument("package", nargs="+", | |
384 | help=_("Package(s) to verify.")) | |
385 | sub_verify.add_argument("action", action="store_const", const="verify") | |
386 | ||
0a89bb8a MT |
387 | def handle_generate(self, ns): |
388 | p = self.pakfire(ns) | |
68c0e769 | 389 | |
964aa579 MT |
390 | print(_("Generating the key may take a moment...")) |
391 | print() | |
68c0e769 | 392 | |
ad62bb07 | 393 | key = p.generate_key("%s <%s>" % (ns.realname.pop(), ns.email.pop())) |
0a89bb8a MT |
394 | |
395 | def handle_list(self, ns): | |
396 | p = self.pakfire(ns) | |
397 | ||
ad62bb07 | 398 | for key in p.keys: |
0a89bb8a MT |
399 | print(key) |
400 | ||
401 | def handle_export(self, ns): | |
402 | p = self.pakfire(ns) | |
403 | ||
404 | for fingerprint in ns.fingerprint: | |
ad62bb07 | 405 | key = p.get_key(fingerprint) |
0a89bb8a MT |
406 | if not key: |
407 | print(_("Could not find key with fingerprint %s") % fingerprint) | |
408 | continue | |
409 | ||
410 | data = key.export(ns.secret) | |
411 | ||
412 | for file in ns.filename: | |
413 | with open(file, "w") as f: | |
414 | f.write(data) | |
415 | else: | |
416 | print(data) | |
68c0e769 | 417 | |
7b526dd2 MT |
418 | def handle_import(self, ns): |
419 | p = self.pakfire(ns) | |
68c0e769 | 420 | |
7b526dd2 MT |
421 | for filename in ns.filename: |
422 | with open(filename) as f: | |
423 | data = f.read() | |
424 | ||
91f40bc5 MT |
425 | keys = p.import_key(data) |
426 | for key in keys: | |
427 | print(key) | |
68c0e769 | 428 | |
b9b6a13e MT |
429 | def handle_delete(self, ns): |
430 | p = self.pakfire(ns) | |
eaf999ef | 431 | |
b9b6a13e MT |
432 | for fingerprint in ns.fingerprint: |
433 | key = p.get_key(fingerprint) | |
434 | if key: | |
435 | key.delete() | |
eaf999ef | 436 | |
68c0e769 MT |
437 | def handle_sign(self): |
438 | # Get the files from the command line options | |
439 | files = [] | |
440 | ||
441 | for file in self.args.package: | |
442 | # Check, if we got a regular file | |
443 | if os.path.exists(file): | |
444 | file = os.path.abspath(file) | |
445 | files.append(file) | |
446 | ||
447 | else: | |
964aa579 | 448 | raise FileNotFoundError(file) |
68c0e769 MT |
449 | |
450 | key = self.args.key[0] | |
451 | ||
36b328f2 | 452 | # Create pakfire instance. |
3a1ddabb | 453 | p = self.create_pakfire() |
36b328f2 | 454 | |
68c0e769 MT |
455 | for file in files: |
456 | # Open the package. | |
36b328f2 | 457 | pkg = packages.open(p, None, file) |
68c0e769 | 458 | |
964aa579 | 459 | print(_("Signing %s...") % pkg.friendly_name) |
68c0e769 MT |
460 | pkg.sign(key) |
461 | ||
462 | def handle_verify(self): | |
b4f722d1 MT |
463 | # TODO needs to use new key code from libpakfire |
464 | ||
68c0e769 MT |
465 | # Get the files from the command line options |
466 | files = [] | |
467 | ||
468 | for file in self.args.package: | |
469 | # Check, if we got a regular file | |
470 | if os.path.exists(file) and not os.path.isdir(file): | |
471 | file = os.path.abspath(file) | |
472 | files.append(file) | |
473 | ||
36b328f2 | 474 | # Create pakfire instance. |
3a1ddabb | 475 | p = self.create_pakfire() |
36b328f2 | 476 | |
68c0e769 MT |
477 | for file in files: |
478 | # Open the package. | |
36b328f2 | 479 | pkg = packages.open(p, None, file) |
68c0e769 | 480 | |
964aa579 | 481 | print(_("Verifying %s...") % pkg.friendly_name) |
68c0e769 MT |
482 | sigs = pkg.verify() |
483 | ||
484 | for sig in sigs: | |
3a1ddabb | 485 | key = p.keyring.get_key(sig.fpr) |
68c0e769 MT |
486 | if key: |
487 | subkey = key.subkeys[0] | |
488 | ||
964aa579 | 489 | print(" %s %s" % (subkey.fpr[-16:], key.uids[0].uid)) |
68c0e769 | 490 | if sig.validity: |
964aa579 | 491 | print(" %s" % _("This signature is valid.")) |
68c0e769 MT |
492 | |
493 | else: | |
964aa579 MT |
494 | print(" %s <%s>" % (sig.fpr, _("Unknown key"))) |
495 | print(" %s" % _("Could not check if this signature is valid.")) | |
68c0e769 MT |
496 | |
497 | created = datetime.datetime.fromtimestamp(sig.timestamp) | |
964aa579 | 498 | print(" %s" % _("Created: %s") % created) |
68c0e769 MT |
499 | |
500 | if sig.exp_timestamp: | |
501 | expires = datetime.datetime.fromtimestamp(sig.exp_timestamp) | |
964aa579 | 502 | print(" %s" % _("Expires: %s") % expires) |
68c0e769 | 503 | |
964aa579 | 504 | print() # Empty line |