]> git.ipfire.org Git - people/stevee/pakfire.git/blob - src/pakfire/cli.py
transaction: Add a simple confirmation before proceeding
[people/stevee/pakfire.git] / src / pakfire / cli.py
1 #!/usr/bin/python3
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 ###############################################################################
21
22 import argparse
23 import datetime
24 import logging
25 import os
26 import shutil
27 import signal
28 import sys
29 import tempfile
30 import time
31
32 from . import base
33 from . import builder
34 from . import client
35 from . import config
36 from . import daemon
37 from . import ui
38 from . import util
39
40 from .system import system
41 from .constants import *
42 from .i18n import _
43
44 class Cli(object):
45 default_path = "/"
46
47 def __init__(self):
48 self.ui = ui.cli.CliUI()
49
50 def parse_cli(self):
51 parser = argparse.ArgumentParser(
52 description = _("Pakfire command line interface"),
53 )
54 subparsers = parser.add_subparsers()
55
56 # Add common arguments
57 self._add_common_arguments(parser)
58
59 parser.add_argument("--arch", "-a", nargs="?",
60 help=_("Run pakfire for the given architecture"))
61 parser.add_argument("--root", metavar="PATH", default="/",
62 help=_("The path where pakfire should operate in"))
63
64 # check
65 check = subparsers.add_parser("check", help=_("Check the system for any errors"))
66 check.set_defaults(func=self.handle_check)
67
68 # check-update
69 check_update = subparsers.add_parser("check-update",
70 help=_("Check, if there are any updates available"))
71 check_update.set_defaults(func=self.handle_check_update)
72 check_update.add_argument("--exclude", "-x", nargs="+",
73 help=_("Exclude package from update"))
74 check_update.add_argument("--allow-archchange", action="store_true",
75 help=_("Allow changing the architecture of packages"))
76 check_update.add_argument("--allow-downgrade", action="store_true",
77 help=_("Allow downgrading of packages"))
78 check_update.add_argument("--allow-vendorchange", action="store_true",
79 help=_("Allow changing the vendor of packages"))
80
81 # clean
82 clean = subparsers.add_parser("clean", help=_("Cleanup all temporary files"))
83 clean.set_defaults(func=self.handle_clean)
84
85 # downgrade
86 downgrade = subparsers.add_parser("downgrade", help=_("Downgrade one or more packages"))
87 downgrade.add_argument("package", nargs="*",
88 help=_("Give a name of a package to downgrade"))
89 downgrade.add_argument("--allow-vendorchange", action="store_true",
90 help=_("Allow changing the vendor of packages"))
91 downgrade.add_argument("--disallow-archchange", action="store_true",
92 help=_("Disallow changing the architecture of packages"))
93 downgrade.set_defaults(func=self.handle_downgrade)
94
95 # execute
96 execute = subparsers.add_parser("execute",
97 help=_("Executes a command in the pakfire environment (useful for development)"))
98 execute.add_argument("--bind", action="append", default=[], dest="binds",
99 help=_("Bind-mounts the given directory"))
100 execute.add_argument("command", nargs=argparse.REMAINDER)
101 execute.set_defaults(func=self.handle_execute)
102
103 # extract
104 extract = subparsers.add_parser("extract",
105 help=_("Extract a package to a directory"))
106 extract.add_argument("package", nargs="+",
107 help=_("Give name of the file to extract"))
108 extract.add_argument("--target", nargs="?",
109 help=_("Target directory where to extract to"))
110 extract.set_defaults(func=self.handle_extract)
111
112 # info
113 info = subparsers.add_parser("info",
114 help=_("Print some information about the given package(s)"))
115 info.add_argument("--long", action="store_true",
116 help=_("Show more information"))
117 info.add_argument("package", nargs="+",
118 help=_("Give at least the name of one package"))
119 info.set_defaults(func=self.handle_info)
120
121 # install
122 install = subparsers.add_parser("install",
123 help=_("Install one or more packages to the system"))
124 install.add_argument("package", nargs="+",
125 help=_("Give name of at least one package to install"))
126 install.add_argument("--without-recommends", action="store_true",
127 help=_("Don't install recommended packages"))
128 install.set_defaults(func=self.handle_install)
129
130 # provides
131 provides = subparsers.add_parser("provides",
132 help=_("Get a list of packages that provide a given file or feature"))
133 provides.add_argument("pattern", nargs="+", help=_("File or feature to search for"))
134 provides.set_defaults(func=self.handle_provides)
135
136 # reinstall
137 reinstall = subparsers.add_parser("reinstall",
138 help=_("Reinstall one or more packages"))
139 reinstall.add_argument("package", nargs="+",
140 help=_("Give name of at least one package to reinstall"))
141 reinstall.set_defaults(func=self.handle_reinstall)
142
143 # remove
144 remove = subparsers.add_parser("remove",
145 help=_("Remove one or more packages from the system"))
146 remove.add_argument("package", nargs="+",
147 help=_("Give name of at least one package to remove"))
148 remove.set_defaults(func=self.handle_remove)
149
150 # repolist
151 repolist = subparsers.add_parser("repolist",
152 help=_("List all currently enabled repositories"))
153 repolist.set_defaults(func=self.handle_repolist)
154
155 # search
156 search = subparsers.add_parser("search", help=_("Search for a given pattern"))
157 search.add_argument("pattern", help=_("A pattern to search for"))
158 search.set_defaults(func=self.handle_search)
159
160 # sync
161 sync = subparsers.add_parser("sync",
162 help=_("Sync all installed with the latest one in the distribution"))
163 sync.set_defaults(func=self.handle_sync)
164
165 # update
166 update = subparsers.add_parser("update",
167 help=_("Update the whole system or one specific package"))
168 update.add_argument("package", nargs="*",
169 help=_("Give a name of a package to update or leave emtpy for all"))
170 update.add_argument("--exclude", "-x", nargs="+",
171 help=_("Exclude package from update"))
172 update.add_argument("--allow-archchange", action="store_true",
173 help=_("Allow changing the architecture of packages"))
174 update.add_argument("--allow-downgrade", action="store_true",
175 help=_("Allow downgrading of packages"))
176 update.add_argument("--allow-vendorchange", action="store_true",
177 help=_("Allow changing the vendor of packages"))
178 update.set_defaults(func=self.handle_update)
179
180 return parser.parse_args()
181
182 def _add_common_arguments(self, parser, offline_switch=True):
183 parser.add_argument("--version", action="version",
184 version="%(prog)s " + PAKFIRE_VERSION)
185
186 parser.add_argument("-v", "--verbose", action="store_true",
187 help=_("Enable verbose output."))
188
189 parser.add_argument("-c", "--config", nargs="?",
190 help=_("Path to a configuration file to load."))
191
192 parser.add_argument("--disable-repo", nargs="*", metavar="REPO",
193 help=_("Disable a repository temporarily."), default=[])
194
195 parser.add_argument("--enable-repo", nargs="*", metavar="REPO",
196 help=_("Enable a repository temporarily."), default=[])
197
198 if offline_switch:
199 parser.add_argument("--offline", action="store_true",
200 help=_("Run pakfire in offline mode."))
201
202 def pakfire(self, ns):
203 p = base.Pakfire(
204 arch=ns.arch,
205 conf=ns.config,
206 path=ns.root if "root" in ns else self.default_path,
207 offline=ns.offline,
208 )
209
210 # Disable repositories.
211 for repo_name in ns.disable_repo:
212 repo = p.get_repo(repo_name)
213 repo.enabled = False
214
215 # Enable repositories.
216 for repo_name in ns.enable_repo:
217 repo = p.get_repo(repo_name)
218 repo.enabled = True
219
220 return p
221
222 def handle_execute(self, ns):
223 pakfire = self.pakfire(ns)
224
225 # Bind-mount everything
226 for bind in ns.binds:
227 pakfire.bind(bind)
228
229 # Log everything to the console
230 def logging_callback(level, line):
231 if level >= logging.ERROR:
232 sys.stderr.write("%s\n" % line)
233 else:
234 sys.stdout.write("%s\n" % line)
235
236 return pakfire.execute(ns.command, logging_callback=logging_callback)
237
238 def run(self):
239 args = self.parse_cli()
240 assert args.func, "Argument function not defined"
241
242 try:
243 return args.func(args)
244
245 except KeyboardInterrupt:
246 self.ui.message(_("Received keyboard interupt (Ctrl-C). Exiting."),
247 level=logging.CRITICAL)
248
249 return 128 + signal.SIGINT
250
251 except DependencyError as e:
252 self.ui.message(_("One or more dependencies could not been resolved"))
253 self.ui.message("") # empty line
254
255 # This exception provides a list of all problems
256 problems, = e.args
257
258 # List all problems
259 for problem in problems:
260 self.ui.message(" * %s" % problem)
261
262 self.ui.message(" %s" % _("Possible solutions are:"))
263 for solution in problem.solutions:
264 self.ui.message(" * %s" % solution)
265
266 # Add another empty line
267 self.ui.message("")
268
269 return 4
270
271 # Catch all errors and show a user-friendly error message.
272 except Error as e:
273 self.ui.message(_("An error has occured when running Pakfire"), level=logging.CRITICAL)
274
275 self.ui.message(_("%s: %s") % (e.__class__.__name__, e.message),
276 level=logging.ERROR)
277
278 return e.exit_code
279
280 def _dump_transaction(self, transaction):
281 """
282 Dumps the transaction
283 """
284 t = transaction.dump()
285
286 for line in t.splitlines():
287 self.ui.message(line)
288
289 def _execute_transaction(self, transaction):
290 transaction.run()
291
292 def handle_info(self, ns):
293 with self.pakfire(ns) as p:
294 for pkg in p.info(ns.package):
295 s = pkg.dump(long=ns.long)
296 self.ui.message(s)
297
298 def handle_search(self, ns):
299 with self.pakfire(ns) as p:
300 for pkg in p.search(ns.pattern):
301 # Skip any -debuginfo packages
302 if pkg.name.endswith("-debuginfo"):
303 continue
304
305 self.ui.message("%-24s: %s" % (pkg.name, pkg.summary))
306
307 def handle_update(self, ns, check=False):
308 with self.pakfire(ns) as p:
309 transaction = p.update(
310 ns.package, excludes=ns.exclude,
311 allow_archchange=ns.allow_archchange,
312 allow_vendorchange=ns.allow_vendorchange,
313 )
314
315 # If we are only checking for updates,
316 # we dump the transaction and exit here.
317 if check:
318 self._dump_transaction(transaction)
319 return
320
321 # Otherwise we execute the transaction
322 self._execute_transaction(transaction)
323
324 def handle_sync(self, ns):
325 with self.pakfire(ns) as p:
326 transaction = p.update(
327 allow_archchange=True,
328 allow_vendorchange=True,
329 )
330
331 self._execute_transaction(transaction)
332
333 def handle_check_update(self, ns):
334 self.handle_update(ns, check=True)
335
336 def handle_downgrade(self, ns, **args):
337 with self.pakfire(ns) as p:
338 p.downgrade(
339 self.args.package,
340 allow_vendorchange=self.args.allow_vendorchange,
341 allow_archchange=not self.args.disallow_archchange,
342 **args
343 )
344
345 def handle_install(self, ns):
346 with self.pakfire(ns) as p:
347 transaction = p.install(ns.package, without_recommends=ns.without_recommends)
348
349 # Execute the transaction
350 self._execute_transaction(transaction)
351
352 def handle_reinstall(self, ns):
353 with self.pakfire(ns) as p:
354 transaction = p.reinstall(ns.package)
355
356 # Execute the transaction
357 self._execute_transaction(transaction)
358
359 def handle_remove(self, ns):
360 with self.pakfire(ns) as p:
361 transaction = p.erase(ns.package)
362
363 self._execute_transaction(transaction)
364
365 def handle_provides(self, ns, long=False):
366 with self.pakfire(ns) as p:
367 for pkg in p.provides(ns.pattern):
368 s = pkg.dump(long=long)
369 print(s)
370
371 def handle_repolist(self, ns):
372 with self.pakfire(ns) as p:
373 FORMAT = " %-20s %8s %12s %12s "
374 title = FORMAT % (_("Repository"), _("Enabled"), _("Priority"), _("Packages"))
375 print(title)
376 print("=" * len(title)) # spacing line
377
378 for repo in p.repos:
379 print(FORMAT % (repo.name, repo.enabled, repo.priority, len(repo)))
380
381 def handle_clean(self, ns):
382 self.ui.message(_("Cleaning up everything..."))
383
384 p = self.pakfire(ns)
385 p.clean()
386
387 def handle_check(self, ns):
388 with self.pakfire(ns) as p:
389 # This will throw an exception when there are errors
390 transaction = p.check()
391
392 self.ui.message(_("Everything okay"))
393
394 def handle_extract(self, ns):
395 with self.pakfire(ns) as p:
396 p.extract(ns.package, target=ns.target)
397
398
399 class CliBuilder(Cli):
400 default_path = None
401
402 def parse_cli(self):
403 parser = argparse.ArgumentParser(
404 description = _("Pakfire builder command line interface"),
405 )
406 subparsers = parser.add_subparsers()
407
408 # Add common arguments
409 self._add_common_arguments(parser)
410
411 # Add additional arguments
412 parser.add_argument("--arch", "-a", nargs="?",
413 help=_("Run pakfire for the given architecture"))
414 parser.add_argument("--distro", nargs="?", default="ipfire3", # XXX for now
415 help=_("Choose the distribution configuration to use for build"))
416 parser.add_argument("--disable-snapshot", action="store_true",
417 help=_("Disable using snapshots"))
418
419 # build
420 build = subparsers.add_parser("build", help=_("Build one or more packages"))
421 build.add_argument("package", nargs=1,
422 help=_("Give name of at least one package to build"))
423 build.set_defaults(func=self.handle_build)
424
425 build.add_argument("--resultdir", nargs="?",
426 help=_("Path were the output files should be copied to"))
427 build.add_argument("-m", "--mode", nargs="?", default="development",
428 help=_("Mode to run in. Is either 'release' or 'development' (default)"))
429 build.add_argument("--after-shell", action="store_true",
430 help=_("Run a shell after a successful build"))
431 build.add_argument("--skip-install-test", action="store_true",
432 help=_("Do not perform the install test"))
433 build.add_argument("--private-network", action="store_true",
434 help=_("Disable network in container"))
435
436 # clean
437 clean = subparsers.add_parser("clean", help=_("Cleanup all temporary files"))
438 clean.set_defaults(func=self.handle_clean)
439
440 # dist
441 dist = subparsers.add_parser("dist", help=_("Generate a source package"))
442 dist.add_argument("package", nargs="+", help=_("Give name(s) of a package(s)"))
443 dist.set_defaults(func=self.handle_dist)
444
445 dist.add_argument("--resultdir", nargs="?",
446 help=_("Path were the output files should be copied to"))
447
448 # extract
449 extract = subparsers.add_parser("extract", help=_("Extract a package to a directory"))
450 extract.add_argument("package", nargs="+",
451 help=_("Give name of the file to extract"))
452 extract.add_argument("--target", nargs="?",
453 help=_("Target directory where to extract to"))
454 extract.set_defaults(func=self.handle_extract)
455
456 # info
457 info = subparsers.add_parser("info",
458 help=_("Print some information about the given package(s)"))
459 info.add_argument("--filelist", action="store_true",
460 help=_("Show filelist"))
461 info.add_argument("package", nargs="+",
462 help=_("Give at least the name of one package."))
463 info.set_defaults(func=self.handle_info)
464
465 # provides
466 provides = subparsers.add_parser("provides",
467 help=_("Get a list of packages that provide a given file or feature"))
468 provides.add_argument("pattern", nargs="+",
469 help=_("File or feature to search for"))
470 provides.set_defaults(func=self.handle_provides)
471
472 # repolist
473 repolist = subparsers.add_parser("repolist",
474 help=_("List all currently enabled repositories"))
475 repolist.set_defaults(func=self.handle_repolist)
476
477 # search
478 search = subparsers.add_parser("search", help=_("Search for a given pattern"))
479 search.add_argument("pattern", help=_("A pattern to search for"))
480 search.set_defaults(func=self.handle_search)
481
482 # shell
483 shell = subparsers.add_parser("shell", help=_("Go into a build shell"))
484 shell.add_argument("package", nargs="*", help=_("Give name of a package"))
485 shell.add_argument("--install", nargs="*",
486 help=_("Packages that should be installed in the shell"))
487 shell.set_defaults(func=self.handle_shell)
488
489 shell.add_argument("-m", "--mode", nargs="?", default="development",
490 help=_("Mode to run in. Is either 'release' or 'development' (default)."))
491 shell.add_argument("--private-network", action="store_true",
492 help=_("Disable network in container"))
493
494 # update
495 update = subparsers.add_parser("update", help=_("Update the package indexes"))
496 update.set_defaults(func=self.handle_update)
497
498 return parser.parse_args()
499
500 def builder(self, ns):
501 # Find distro configuration file
502 conf = os.path.join(CONFIG_DISTRO_DIR, "%s.conf" % ns.distro)
503
504 return builder.Builder(
505 conf=conf,
506 arch=ns.arch,
507 enable_snapshot=not ns.disable_snapshot
508 )
509
510 def handle_build(self, ns):
511 package, = ns.package
512
513 # Initialise a builder instance and build this package
514 with self.builder(ns) as b:
515 b.build(package)
516
517 def handle_info(self, ns):
518 with self.builder(ns) as b:
519 with b.pakfire as p:
520 for pkg in p.info(ns.package):
521 s = pkg.dump(long=True, filelist=ns.filelist)
522 self.ui.message(s)
523
524 def handle_repolist(self, ns):
525 with self.builder(ns) as b:
526 with b.pakfire as p:
527 FORMAT = " %-20s %8s %12s %12s "
528 title = FORMAT % (_("Repository"), _("Enabled"), _("Priority"), _("Packages"))
529 print(title)
530 print("=" * len(title)) # spacing line
531
532 for repo in p.repos:
533 print(FORMAT % (repo.name, repo.enabled, repo.priority, len(repo)))
534
535 def handle_shell(self, ns):
536 with self.builder(ns) as b:
537 b.shell(packages=ns.package, install=ns.install)
538
539 def handle_dist(self, ns):
540 # Get the packages from the command line options
541 pkgs = []
542
543 for pkg in ns.package:
544 # Check, if we got a regular file
545 if os.path.exists(pkg):
546 pkg = os.path.abspath(pkg)
547 pkgs.append(pkg)
548
549 else:
550 raise FileNotFoundError(pkg)
551
552 # Put packages to where the user said or our
553 # current working directory.
554 resultdir = ns.resultdir or os.getcwd()
555
556 p = self.pakfire(ns)
557 for pkg in pkgs:
558 p.dist(pkg, resultdir)
559
560
561 class CliClient(Cli):
562 def __init__(self):
563 # Create connection to pakfire hub
564 self.client = client.Client()
565
566 def parse_cli(self):
567 parser = argparse.ArgumentParser(
568 description = _("Pakfire client command line interface"),
569 )
570 subparsers = parser.add_subparsers(help=_("sub-command help"))
571
572 # Add common arguments
573 self._add_common_arguments(parser, offline_switch=False)
574
575 # build
576 build = subparsers.add_parser("build", help=_("Build a package remote"))
577 build.add_argument("packages", nargs="+", help=_("Package(s) to build"))
578 build.set_defaults(func=self.handle_build)
579
580 build.add_argument("-a", "--arch",
581 help=_("Build the package(s) for the given architecture only"))
582
583 # check-connection
584 check_connection = subparsers.add_parser("check-connection",
585 help=_("Check the connection to the hub"))
586 check_connection.set_defaults(func=self.handle_check_connection)
587
588 # upload
589 upload = subparsers.add_parser("upload", help=_("Upload a file to the build service"))
590 upload.add_argument("file", nargs=1, help=_("Filename"))
591 upload.set_defaults(func=self.handle_upload)
592
593 # watch-build
594 watch_build = subparsers.add_parser("watch-build", help=_("Watch the status of a build"))
595 watch_build.add_argument("id", nargs=1, help=_("Build ID"))
596 watch_build.set_defaults(func=self.handle_watch_build)
597
598 # watch-job
599 watch_job = subparsers.add_parser("watch-job", help=_("Watch the status of a job"))
600 watch_job.add_argument("id", nargs=1, help=_("Job ID"))
601 watch_job.set_defaults(func=self.handle_watch_job)
602
603 return parser.parse_args()
604
605 def handle_build(self, ns):
606 # Create a temporary directory.
607 temp_dir = tempfile.mkdtemp()
608
609 # Format arches.
610 if ns.arch:
611 arches = self.args.arch.split(",")
612 else:
613 arches = None
614
615 try:
616 # Package all packages first and save the actual filenames
617 packages = []
618
619 for package in ns.packages:
620 if package.endswith(".%s" % MAKEFILE_EXTENSION):
621 # Create a source package from the makefile.
622 p = self.pakfire()
623 package = p.dist(package, temp_dir)
624 packages.append(package)
625
626 elif package.endswith(".%s" % PACKAGE_EXTENSION):
627 packages.append(package)
628
629 else:
630 raise Exception("Unknown filetype: %s" % package)
631
632 assert packages
633
634 # Upload the packages to the build service
635 for package in packages:
636 build = self.client.create_build(package, type="scratch", arches=arches)
637
638 # Show information about the uploaded build
639 summary = build.dump()
640 for line in summary.splitlines():
641 print(" %s" % line)
642
643 finally:
644 # Cleanup the temporary directory and all files.
645 if os.path.exists(temp_dir):
646 shutil.rmtree(temp_dir, ignore_errors=True)
647
648 def handle_check_connection(self, ns):
649 success = self.client.check_connection()
650
651 if success:
652 print("%s: %s" % (_("Connection OK"), success))
653
654 def handle_upload(self, ns):
655 for path in ns.file:
656 self.client.upload_file(path)
657
658 def handle_watch_build(self, ns):
659 build = self.client.get_build(ns.id[0])
660
661 return self._watch_something(build)
662
663 def handle_watch_job(self, ns):
664 job = self.client.get_job(ns.id[0])
665
666 return self._watch_something(job)
667
668 def _watch_something(self, o):
669 while True:
670 s = o.dump()
671 print(s)
672
673 # Break the loop if the build/job is not active any more
674 # (since we don't expect any changes)
675 if not o.is_active():
676 break
677
678 time.sleep(60)
679
680 # Update data before the next loop is shown
681 o.refresh()
682
683
684 class CliDaemon(Cli):
685 def parse_cli(self):
686 parser = argparse.ArgumentParser(
687 description = _("Pakfire daemon command line interface"),
688 )
689 self._add_common_arguments(parser, offline_switch=False)
690
691 # There is only one default action
692 parser.set_defaults(func=self.handle_run)
693
694 return parser.parse_args()
695
696 def handle_run(self, ns):
697 """
698 Runs the pakfire daemon
699 """
700 d = daemon.PakfireDaemon()
701
702 try:
703 d.run()
704
705 # We cannot just kill the daemon, it needs a smooth shutdown
706 except (SystemExit, KeyboardInterrupt):
707 d.shutdown()
708
709
710 class CliKey(Cli):
711 def parse_cli(self):
712 parser = argparse.ArgumentParser(
713 description = _("Pakfire key command line interface"),
714 )
715 subparsers = parser.add_subparsers()
716
717 # Add common arguments
718 self._add_common_arguments(parser)
719
720 # delete
721 delete = subparsers.add_parser("delete", help=_("Delete a key from the local keyring"))
722 delete.add_argument("fingerprint", nargs="+", help=_("The fingerprint of the key to delete"))
723 delete.set_defaults(func=self.handle_delete)
724
725 # export
726 export = subparsers.add_parser("export", help=_("Export a key to a file"))
727 export.add_argument("fingerprint", nargs=1,
728 help=_("The fingerprint of the key to export"))
729 export.add_argument("--filename", nargs="*", help=_("Write the key to this file"))
730 export.add_argument("--secret", action="store_true",
731 help=_("Export the secret key"))
732 export.set_defaults(func=self.handle_export)
733
734 # import
735 _import = subparsers.add_parser("import", help=_("Import a key from file"))
736 _import.add_argument("filename", nargs="+", help=_("Filename of that key to import"))
737 _import.set_defaults(func=self.handle_import)
738
739 # generate
740 generate = subparsers.add_parser("generate", help=_("Import a key from file"))
741 generate.add_argument("--realname", nargs=1,
742 help=_("The real name of the owner of this key"))
743 generate.add_argument("--email", nargs=1,
744 help=_("The email address of the owner of this key"))
745 generate.set_defaults(func=self.handle_generate)
746
747 # list
748 list = subparsers.add_parser("list", help=_("List all imported keys"))
749 list.set_defaults(func=self.handle_list)
750
751 return parser.parse_args()
752
753 def parse_command_sign(self):
754 # Implement the "sign" command.
755 sub_sign = self.sub_commands.add_parser("sign",
756 help=_("Sign one or more packages."))
757 sub_sign.add_argument("--key", "-k", nargs=1,
758 help=_("Key that is used sign the package(s)."))
759 sub_sign.add_argument("package", nargs="+",
760 help=_("Package(s) to sign."))
761 sub_sign.add_argument("action", action="store_const", const="sign")
762
763 def parse_command_verify(self):
764 # Implement the "verify" command.
765 sub_verify = self.sub_commands.add_parser("verify",
766 help=_("Verify one or more packages."))
767 #sub_verify.add_argument("--key", "-k", nargs=1,
768 # help=_("Key that is used verify the package(s)."))
769 sub_verify.add_argument("package", nargs="+",
770 help=_("Package(s) to verify."))
771 sub_verify.add_argument("action", action="store_const", const="verify")
772
773 def handle_generate(self, ns):
774 p = self.pakfire(ns)
775
776 print(_("Generating the key may take a moment..."))
777 print()
778
779 key = p.generate_key("%s <%s>" % (ns.realname.pop(), ns.email.pop()))
780
781 def handle_list(self, ns):
782 p = self.pakfire(ns)
783
784 for key in p.keys:
785 print(key)
786
787 def handle_export(self, ns):
788 p = self.pakfire(ns)
789
790 for fingerprint in ns.fingerprint:
791 key = p.get_key(fingerprint)
792 if not key:
793 print(_("Could not find key with fingerprint %s") % fingerprint)
794 continue
795
796 data = key.export(ns.secret)
797
798 for file in ns.filename:
799 with open(file, "w") as f:
800 f.write(data)
801 else:
802 print(data)
803
804 def handle_import(self, ns):
805 p = self.pakfire(ns)
806
807 for filename in ns.filename:
808 with open(filename) as f:
809 data = f.read()
810
811 keys = p.import_key(data)
812 for key in keys:
813 print(key)
814
815 def handle_delete(self, ns):
816 p = self.pakfire(ns)
817
818 for fingerprint in ns.fingerprint:
819 key = p.get_key(fingerprint)
820 if key:
821 key.delete()
822
823 def handle_sign(self):
824 # Get the files from the command line options
825 files = []
826
827 for file in self.args.package:
828 # Check, if we got a regular file
829 if os.path.exists(file):
830 file = os.path.abspath(file)
831 files.append(file)
832
833 else:
834 raise FileNotFoundError(file)
835
836 key = self.args.key[0]
837
838 # Create pakfire instance.
839 p = self.create_pakfire()
840
841 for file in files:
842 # Open the package.
843 pkg = packages.open(p, None, file)
844
845 print(_("Signing %s...") % pkg.friendly_name)
846 pkg.sign(key)
847
848 def handle_verify(self):
849 # TODO needs to use new key code from libpakfire
850
851 # Get the files from the command line options
852 files = []
853
854 for file in self.args.package:
855 # Check, if we got a regular file
856 if os.path.exists(file) and not os.path.isdir(file):
857 file = os.path.abspath(file)
858 files.append(file)
859
860 # Create pakfire instance.
861 p = self.create_pakfire()
862
863 for file in files:
864 # Open the package.
865 pkg = packages.open(p, None, file)
866
867 print(_("Verifying %s...") % pkg.friendly_name)
868 sigs = pkg.verify()
869
870 for sig in sigs:
871 key = p.keyring.get_key(sig.fpr)
872 if key:
873 subkey = key.subkeys[0]
874
875 print(" %s %s" % (subkey.fpr[-16:], key.uids[0].uid))
876 if sig.validity:
877 print(" %s" % _("This signature is valid."))
878
879 else:
880 print(" %s <%s>" % (sig.fpr, _("Unknown key")))
881 print(" %s" % _("Could not check if this signature is valid."))
882
883 created = datetime.datetime.fromtimestamp(sig.timestamp)
884 print(" %s" % _("Created: %s") % created)
885
886 if sig.exp_timestamp:
887 expires = datetime.datetime.fromtimestamp(sig.exp_timestamp)
888 print(" %s" % _("Expires: %s") % expires)
889
890 print() # Empty line